mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-03-28 14:56:19 +01:00
callbuttons: create alternate layouts
GitLab: #729 Change-Id: Ice67d8649c1ad2a92eba7c02cebc446eac5ac90e
This commit is contained in:
parent
28c2f8cb69
commit
824ba581c8
12 changed files with 668 additions and 259 deletions
2
qml.qrc
2
qml.qrc
|
@ -136,6 +136,8 @@
|
|||
<file>src/app/mainview/components/SmartListItemDelegate.qml</file>
|
||||
<file>src/app/mainview/components/BadgeNotifier.qml</file>
|
||||
<file>src/app/mainview/components/ParticipantsLayer.qml</file>
|
||||
<file>src/app/mainview/components/ParticipantsLayoutVertical.qml</file>
|
||||
<file>src/app/mainview/components/ParticipantsLayoutHorizontal.qml</file>
|
||||
<file>src/app/mainview/components/MainOverlay.qml</file>
|
||||
<file>src/app/mainview/components/CallButtonDelegate.qml</file>
|
||||
<file>src/app/mainview/components/CallActionBar.qml</file>
|
||||
|
|
8
resources/icons/ontheside_black_24dp.svg
Normal file
8
resources/icons/ontheside_black_24dp.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||
<path d="M18.3,2H5.8C3.7,2,2,3.7,2,5.8v12.4C2,20.3,3.7,22,5.8,22h12.4c2.1,0,3.8-1.7,3.8-3.7V5.8C22,3.7,20.3,2,18.3,2z M20.6,5.8
|
||||
v2.7h-3.7v-5h1.4C19.6,3.5,20.6,4.5,20.6,5.8z M16.9,10h3.7v4.3h-3.7V10z M3.5,18.2V5.7c0-1.2,1-2.2,2.3-2.2h9.6v17H5.8
|
||||
C4.6,20.5,3.5,19.5,3.5,18.2z M18.3,20.5h-1.4v-4.8h3.7v2.5C20.6,19.5,19.6,20.5,18.3,20.5z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 690 B |
8
resources/icons/onthetop_black_24dp.svg
Normal file
8
resources/icons/onthetop_black_24dp.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||
<path d="M18.3,2H5.8C3.7,2,2,3.7,2,5.8v12.4C2,20.3,3.7,22,5.8,22h12.4c2.1,0,3.8-1.7,3.8-3.7V5.8C22,3.7,20.3,2,18.3,2z M20.6,5.8
|
||||
V7h-4.8V3.5h2.5C19.6,3.5,20.6,4.5,20.6,5.8z M10.1,3.5h4.3V7h-4.3V3.5z M5.8,3.5h2.8V7H3.5V5.7C3.5,4.5,4.5,3.5,5.8,3.5z
|
||||
M18.3,20.5H5.8c-1.2,0-2.3-1-2.3-2.3V8.5h17.1v9.7C20.6,19.5,19.6,20.5,18.3,20.5z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 684 B |
|
@ -46,6 +46,7 @@ extern const QString defaultDownloadPath;
|
|||
X(EnableExperimentalSwarm, false) \
|
||||
X(EnableDarkTheme, false) \
|
||||
X(BaseZoom, 1.0) \
|
||||
X(ParticipantsSide, false) \
|
||||
X(AutoUpdate, true) \
|
||||
X(StartMinimized, false) \
|
||||
X(ShowChatviewHorizontally, true) \
|
||||
|
|
|
@ -679,7 +679,6 @@ CallAdapter::sipInputPanelPlayDTMF(const QString& key)
|
|||
void
|
||||
CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo)
|
||||
{
|
||||
qWarning() << "CallAdapter::updateCallOverlay";
|
||||
auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_);
|
||||
auto* callModel = accInfo.callModel.get();
|
||||
|
||||
|
|
|
@ -250,6 +250,8 @@ Item {
|
|||
property string bothMuted: qsTr("Local and Moderator muted")
|
||||
property string moderatorMuted: qsTr("Moderator muted")
|
||||
property string notMuted: qsTr("Not muted")
|
||||
property string participantsSide: qsTr("On the side")
|
||||
property string participantsTop: qsTr("On the top")
|
||||
|
||||
// LineEditContextMenu
|
||||
property string copy: qsTr("Copy")
|
||||
|
|
|
@ -23,6 +23,7 @@ import QtQuick.Layouts
|
|||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
|
@ -163,6 +164,16 @@ Control {
|
|||
if (!isGrid)
|
||||
CallAdapter.showGridConferenceLayout()
|
||||
break
|
||||
case JamiStrings.participantsSide:
|
||||
var onTheSide = UtilsAdapter.getAppValue(Settings.ParticipantsSide)
|
||||
UtilsAdapter.setAppValue(Settings.ParticipantsSide, !onTheSide)
|
||||
participantsSide = !onTheSide
|
||||
break
|
||||
case JamiStrings.participantsTop:
|
||||
var onTheSide = UtilsAdapter.getAppValue(Settings.ParticipantsSide)
|
||||
UtilsAdapter.setAppValue(Settings.ParticipantsSide, !onTheSide)
|
||||
participantsSide = !onTheSide
|
||||
break
|
||||
}
|
||||
}
|
||||
onTriggered: {
|
||||
|
@ -172,6 +183,11 @@ Control {
|
|||
"ActiveSetting": layoutManager.isCallFullscreen})
|
||||
if (isConference) {
|
||||
layoutModel.append({})
|
||||
var onTheSide = UtilsAdapter.getAppValue(Settings.ParticipantsSide)
|
||||
layoutModel.append({"Name": onTheSide ? JamiStrings.participantsSide : JamiStrings.participantsTop,
|
||||
"IconSource": onTheSide ? JamiResources.ontheside_black_24dp_svg : JamiResources.onthetop_black_24dp_svg,
|
||||
"ActiveSetting": true})
|
||||
layoutModel.append({})
|
||||
layoutModel.append({"Name": JamiStrings.mosaic,
|
||||
"IconSource": JamiResources.mosaic_black_24dp_svg,
|
||||
"ActiveSetting": isGrid})
|
||||
|
|
|
@ -23,6 +23,7 @@ import QtQuick
|
|||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
|
||||
import "../js/contactpickercreation.js" as ContactPickerCreation
|
||||
import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation
|
||||
|
@ -44,6 +45,7 @@ Item {
|
|||
property bool isModerator
|
||||
property bool isConference
|
||||
property bool isGrid
|
||||
property bool participantsSide: UtilsAdapter.getAppValue(Settings.ParticipantsSide)
|
||||
property bool localHandRaised
|
||||
property bool sharingActive: AvAdapter.isSharing()
|
||||
property string callId: ""
|
||||
|
|
|
@ -177,6 +177,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
anchors.margins: 3
|
||||
visible: participantsLayer.count !== 0
|
||||
participantsSide: callOverlay.participantsSide
|
||||
|
||||
onCountChanged: {
|
||||
callOverlay.isConference = participantsLayer.count > 0
|
||||
|
|
|
@ -25,12 +25,15 @@ import QtQuick.Controls 2.15
|
|||
import net.jami.Adapters 1.1
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import "../../commoncomponents"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int count: commonParticipants.count + activeParticipants.count
|
||||
property int count: 0
|
||||
property bool inLine: CallParticipantsModel.conferenceLayout === CallParticipantsModel.ONE_WITH_SMALL
|
||||
property bool participantsSide
|
||||
|
||||
Component {
|
||||
id: callVideoMedia
|
||||
|
@ -70,265 +73,18 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
SplitView {
|
||||
ParticipantsLayoutVertical {
|
||||
anchors.fill: parent
|
||||
participantComponent: callVideoMedia
|
||||
visible: !participantsSide
|
||||
|
||||
orientation: Qt.Vertical
|
||||
handle: Rectangle {
|
||||
implicitWidth: root.width
|
||||
implicitHeight: 11
|
||||
color: "transparent"
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
height: 1
|
||||
width: parent.implicitWidth - 40
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
onLayoutCountChanged: root.count = layoutCount
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 45
|
||||
anchors.centerIn: parent
|
||||
height: 1
|
||||
color: "black"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
height: 11
|
||||
width: 45
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
height: 2
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
height: 2
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: genericParticipantsRect
|
||||
|
||||
TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton }
|
||||
|
||||
SplitView.preferredHeight: (parent.height / 4)
|
||||
SplitView.minimumHeight: parent.height / 6
|
||||
SplitView.maximumHeight: inLine? parent.height / 2 : parent.height
|
||||
|
||||
visible: inLine || CallParticipantsModel.conferenceLayout === CallParticipantsModel.GRID
|
||||
color: "transparent"
|
||||
|
||||
property int lowLimit: 0
|
||||
property int topLimit: commonParticipants.count
|
||||
property int currentPos: 0
|
||||
property int showable: {
|
||||
if (!inLine)
|
||||
return commonParticipants.count
|
||||
if (commonParticipantsFlow.componentWidth === 0)
|
||||
return 1
|
||||
var placeableElements = Math.floor((width * 0.9)/commonParticipantsFlow.componentWidth)
|
||||
if (commonParticipants.count - placeableElements < currentPos)
|
||||
currentPos = Math.max(commonParticipants.count - placeableElements, 0)
|
||||
return Math.max(1, placeableElements)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
RoundButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width : 30
|
||||
height : 30
|
||||
radius: 10
|
||||
text: "<"
|
||||
visible: genericParticipantsRect.currentPos > 0
|
||||
&& activeParticipantsFlow.visible
|
||||
onClicked: {
|
||||
if (genericParticipantsRect.currentPos > 0)
|
||||
genericParticipantsRect.currentPos--
|
||||
}
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.lightGrey_
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: centerItem
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 4
|
||||
|
||||
// GENERIC
|
||||
Flow {
|
||||
id: commonParticipantsFlow
|
||||
anchors.fill: parent
|
||||
|
||||
spacing: 4
|
||||
property int columns: {
|
||||
if (inLine)
|
||||
return commonParticipants.count
|
||||
var ratio = Math.floor(root.width / root.height)
|
||||
// If ratio is 2 we can have 2 times more elements on each columns
|
||||
var wantedCol = Math.max(1, Math.round(Math.sqrt(commonParticipants.count) * ratio))
|
||||
var cols = Math.min(commonParticipants.count, wantedCol)
|
||||
// Optimize with the rows (eg 7 with ratio 2 should have 4 and 3 items, not 6 and 1)
|
||||
var rows = Math.max(1, Math.ceil(commonParticipants.count/cols))
|
||||
return Math.min(Math.ceil(commonParticipants.count / rows), cols)
|
||||
}
|
||||
property int rows: Math.max(1, Math.ceil(commonParticipants.count/columns))
|
||||
property int componentWidth: {
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.columns
|
||||
var w = Math.floor((commonParticipantsFlow.width - totalSpacing)/ commonParticipantsFlow.columns)
|
||||
if (inLine) {
|
||||
w = Math.max(w, height)
|
||||
w = Math.min(w, height * 4 / 3) // Avoid too wide elements
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
Item {
|
||||
height: parent.height
|
||||
width: {
|
||||
if (!inLine)
|
||||
return 0
|
||||
var showed = Math.min(genericParticipantsRect.showable, commonParticipantsFlow.columns)
|
||||
return Math.max(0, Math.ceil((centerItem.width - commonParticipantsFlow.componentWidth * showed) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: commonParticipants
|
||||
|
||||
model: GenericParticipantsFilterModel
|
||||
delegate: Loader {
|
||||
sourceComponent: callVideoMedia
|
||||
active: root.visible
|
||||
asynchronous: true
|
||||
visible: {
|
||||
if (status !== Loader.Ready)
|
||||
return false
|
||||
if (inLine)
|
||||
return index >= genericParticipantsRect.currentPos
|
||||
&& index < genericParticipantsRect.currentPos + genericParticipantsRect.showable
|
||||
return true
|
||||
}
|
||||
width: commonParticipantsFlow.componentWidth + leftMargin_
|
||||
height: {
|
||||
if (inLine || commonParticipantsFlow.rows === 1)
|
||||
return genericParticipantsRect.height
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.rows
|
||||
return Math.floor((genericParticipantsRect.height - totalSpacing)/ commonParticipantsFlow.rows)
|
||||
}
|
||||
|
||||
property int leftMargin_: {
|
||||
if (inLine || commonParticipantsFlow.rows === 1)
|
||||
return 0
|
||||
var lastParticipants = (commonParticipants.count % commonParticipantsFlow.columns)
|
||||
if (lastParticipants !== 0 && index === commonParticipants.count - lastParticipants) {
|
||||
var compW = commonParticipantsFlow.componentWidth + commonParticipantsFlow.spacing
|
||||
var lastLineW = lastParticipants * compW
|
||||
return Math.floor((commonParticipantsFlow.width - lastLineW) / 2)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
property string uri_: Uri
|
||||
property string deviceId_: Device
|
||||
property string bestName_: BestName
|
||||
property string avatar_: Avatar ? Avatar : ""
|
||||
property string sinkId_: SinkId ? SinkId : ""
|
||||
property bool isLocal_: IsLocal
|
||||
property bool active_: Active
|
||||
property bool videoMuted_: VideoMuted
|
||||
property bool isContact_: IsContact
|
||||
property bool isModerator_: IsModerator
|
||||
property bool audioLocalMuted_: AudioLocalMuted
|
||||
property bool audioModeratorMuted_: AudioModeratorMuted
|
||||
property bool isHandRaised_: HandRaised
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RoundButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width : 30
|
||||
height : 30
|
||||
radius: 10
|
||||
text: ">"
|
||||
visible: genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos
|
||||
&& activeParticipantsFlow.visible
|
||||
onClicked: {
|
||||
if (genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos)
|
||||
genericParticipantsRect.currentPos++
|
||||
}
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.lightGrey_
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACTIVE
|
||||
Flow {
|
||||
id: activeParticipantsFlow
|
||||
|
||||
TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton }
|
||||
|
||||
SplitView.minimumHeight: parent.height / 4
|
||||
SplitView.maximumHeight: parent.height
|
||||
SplitView.fillHeight: true
|
||||
|
||||
spacing: 8
|
||||
property int columns: Math.max(1, Math.ceil(Math.sqrt(activeParticipants.count)))
|
||||
property int rows: Math.max(1, Math.ceil(activeParticipants.count/columns))
|
||||
property int columnsSpacing: 5 * (columns - 1)
|
||||
property int rowsSpacing: 5 * (rows - 1)
|
||||
|
||||
visible: inLine || CallParticipantsModel.conferenceLayout === CallParticipantsModel.ONE
|
||||
|
||||
Repeater {
|
||||
id: activeParticipants
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
|
||||
model: ActiveParticipantsFilterModel
|
||||
delegate: Loader {
|
||||
active: root.visible
|
||||
asynchronous: true
|
||||
sourceComponent: callVideoMedia
|
||||
visible: status == Loader.Ready
|
||||
|
||||
width: Math.ceil(activeParticipantsFlow.width / activeParticipantsFlow.columns) - activeParticipantsFlow.columnsSpacing
|
||||
height: Math.ceil(activeParticipantsFlow.height / activeParticipantsFlow.rows) - activeParticipantsFlow.rowsSpacing
|
||||
|
||||
property string uri_: Uri
|
||||
property string bestName_: BestName
|
||||
property string avatar_: Avatar ? Avatar : ""
|
||||
property string sinkId_: SinkId ? SinkId : ""
|
||||
property string deviceId_: Device
|
||||
property int leftMargin_: 0
|
||||
property bool isLocal_: IsLocal
|
||||
property bool active_: Active
|
||||
property bool videoMuted_: VideoMuted
|
||||
property bool isContact_: IsContact
|
||||
property bool isModerator_: IsModerator
|
||||
property bool audioLocalMuted_: AudioLocalMuted
|
||||
property bool audioModeratorMuted_: AudioModeratorMuted
|
||||
property bool isHandRaised_: HandRaised
|
||||
}
|
||||
}
|
||||
}
|
||||
ParticipantsLayoutHorizontal {
|
||||
anchors.fill: parent
|
||||
participantComponent: callVideoMedia
|
||||
visible: participantsSide
|
||||
onLayoutCountChanged: root.count = layoutCount
|
||||
}
|
||||
}
|
||||
|
|
321
src/app/mainview/components/ParticipantsLayoutHorizontal.qml
Normal file
321
src/app/mainview/components/ParticipantsLayoutHorizontal.qml
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Savoir-faire Linux Inc.
|
||||
* Authors: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
* Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
property int layoutCount: commonParticipants.count + activeParticipants.count
|
||||
property var participantComponent
|
||||
|
||||
orientation: Qt.Horizontal
|
||||
handle: Rectangle {
|
||||
implicitHeight: root.height
|
||||
implicitWidth: 11
|
||||
color: "transparent"
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 1
|
||||
height: parent.implicitHeight - 40
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 45
|
||||
anchors.centerIn: parent
|
||||
width: 1
|
||||
color: "black"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
height: 45
|
||||
width: 11
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
width: 2
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
width: 2
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACTIVE
|
||||
Flow {
|
||||
id: activeParticipantsFlow
|
||||
|
||||
TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton }
|
||||
|
||||
SplitView.minimumWidth: parent.width / 4
|
||||
SplitView.maximumWidth: parent.width
|
||||
SplitView.fillWidth: true
|
||||
|
||||
spacing: 8
|
||||
property int columns: Math.max(1, Math.ceil(Math.sqrt(activeParticipants.count)))
|
||||
property int rows: Math.max(1, Math.ceil(activeParticipants.count/columns))
|
||||
property int columnsSpacing: 5 * (columns - 1)
|
||||
property int rowsSpacing: 5 * (rows - 1)
|
||||
|
||||
visible: inLine || CallParticipantsModel.conferenceLayout === CallParticipantsModel.ONE
|
||||
|
||||
Repeater {
|
||||
id: activeParticipants
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
|
||||
model: ActiveParticipantsFilterModel
|
||||
delegate: Loader {
|
||||
active: root.visible
|
||||
asynchronous: true
|
||||
sourceComponent: callVideoMedia
|
||||
visible: status == Loader.Ready
|
||||
|
||||
width: Math.ceil(activeParticipantsFlow.width / activeParticipantsFlow.columns) - activeParticipantsFlow.columnsSpacing
|
||||
height: Math.ceil(activeParticipantsFlow.height / activeParticipantsFlow.rows) - activeParticipantsFlow.rowsSpacing
|
||||
|
||||
property string uri_: Uri
|
||||
property string bestName_: BestName
|
||||
property string avatar_: Avatar ? Avatar : ""
|
||||
property string sinkId_: SinkId ? SinkId : ""
|
||||
property string deviceId_: Device
|
||||
property int leftMargin_: 0
|
||||
property bool isLocal_: IsLocal
|
||||
property bool active_: Active
|
||||
property bool videoMuted_: VideoMuted
|
||||
property bool isContact_: IsContact
|
||||
property bool isModerator_: IsModerator
|
||||
property bool audioLocalMuted_: AudioLocalMuted
|
||||
property bool audioModeratorMuted_: AudioModeratorMuted
|
||||
property bool isHandRaised_: HandRaised
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: genericParticipantsRect
|
||||
|
||||
TapHandler { acceptedButtons: Qt.TopButton | Qt.BottomButton }
|
||||
|
||||
SplitView.preferredWidth: (parent.width / 4)
|
||||
SplitView.minimumWidth: parent.width / 6
|
||||
SplitView.maximumWidth: inLine? parent.width / 2 : parent.width
|
||||
|
||||
visible: inLine || CallParticipantsModel.conferenceLayout === CallParticipantsModel.GRID
|
||||
color: "transparent"
|
||||
|
||||
property int lowLimit: 0
|
||||
property int topLimit: commonParticipants.count
|
||||
property int currentPos: 0
|
||||
property int showable: {
|
||||
if (!inLine)
|
||||
return commonParticipants.count
|
||||
if (commonParticipantsFlow.componentHeight === 0)
|
||||
return 1
|
||||
var placeableElements = Math.floor((height * 0.9)/commonParticipantsFlow.componentHeight)
|
||||
if (commonParticipants.count - placeableElements < currentPos)
|
||||
currentPos = Math.max(commonParticipants.count - placeableElements, 0)
|
||||
return Math.max(1, placeableElements)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
width: parent.width
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: parent.width
|
||||
height: 30
|
||||
Layout.bottomMargin: 16
|
||||
Layout.topMargin: 16
|
||||
spacing: 8
|
||||
visible: (genericParticipantsRect.currentPos > 0 && activeParticipantsFlow.visible) ||
|
||||
(genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos && activeParticipantsFlow.visible)
|
||||
|
||||
RoundButton {
|
||||
width : 30
|
||||
height : 30
|
||||
radius: 10
|
||||
text: "^"
|
||||
visible: genericParticipantsRect.currentPos > 0
|
||||
&& activeParticipantsFlow.visible
|
||||
onClicked: {
|
||||
if (genericParticipantsRect.currentPos > 0)
|
||||
genericParticipantsRect.currentPos--
|
||||
}
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.lightGrey_
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
|
||||
RoundButton {
|
||||
width : 30
|
||||
height : 30
|
||||
radius: 10
|
||||
text: "v"
|
||||
visible: genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos
|
||||
&& activeParticipantsFlow.visible
|
||||
onClicked: {
|
||||
if (genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos)
|
||||
genericParticipantsRect.currentPos++
|
||||
}
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.lightGrey_
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: centerItem
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 4
|
||||
|
||||
// GENERIC
|
||||
Flow {
|
||||
id: commonParticipantsFlow
|
||||
anchors.fill: parent
|
||||
|
||||
spacing: 4
|
||||
property int columns: {
|
||||
if (inLine)
|
||||
return 1
|
||||
var ratio = Math.floor(root.width / root.height)
|
||||
// If ratio is 2 we can have 2 times more elements on each columns
|
||||
var wantedCol = Math.max(1, Math.round(Math.sqrt(commonParticipants.count) * ratio))
|
||||
var cols = Math.min(commonParticipants.count, wantedCol)
|
||||
// Optimize with the rows (eg 7 with ratio 2 should have 4 and 3 items, not 6 and 1)
|
||||
var rows = Math.max(1, Math.ceil(commonParticipants.count/cols))
|
||||
return Math.min(Math.ceil(commonParticipants.count / rows), cols)
|
||||
}
|
||||
property int rows: {
|
||||
if (inLine)
|
||||
return commonParticipants.count
|
||||
Math.max(1, Math.ceil(commonParticipants.count/columns))
|
||||
}
|
||||
property int componentHeight: {
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.rows
|
||||
var h = Math.floor((commonParticipantsFlow.height - totalSpacing)/ commonParticipantsFlow.rows)
|
||||
if (inLine) {
|
||||
h = Math.max(width, h)
|
||||
h = Math.min(width, h * 4 / 3) // Avoid too high elements
|
||||
}
|
||||
return h
|
||||
}
|
||||
property int componentWidth: {
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.columns
|
||||
var w = Math.floor((commonParticipantsFlow.width - totalSpacing)/ commonParticipantsFlow.columns)
|
||||
if (inLine) {
|
||||
w = commonParticipantsFlow.width
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: {
|
||||
if (!inLine)
|
||||
return 0
|
||||
var showed = Math.min(genericParticipantsRect.showable, commonParticipantsFlow.rows)
|
||||
return Math.max(0, Math.ceil((centerItem.height - commonParticipantsFlow.componentHeight * showed) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: commonParticipants
|
||||
|
||||
model: GenericParticipantsFilterModel
|
||||
delegate: Loader {
|
||||
sourceComponent: callVideoMedia
|
||||
active: root.visible
|
||||
asynchronous: true
|
||||
visible: {
|
||||
if (status !== Loader.Ready)
|
||||
return false
|
||||
if (inLine)
|
||||
return index >= genericParticipantsRect.currentPos
|
||||
&& index < genericParticipantsRect.currentPos + genericParticipantsRect.showable
|
||||
return true
|
||||
}
|
||||
width: commonParticipantsFlow.componentWidth + leftMargin_
|
||||
height: {
|
||||
if (inLine || commonParticipantsFlow.columns === 1)
|
||||
return commonParticipantsFlow.componentHeight
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.rows
|
||||
return Math.floor((genericParticipantsRect.height - totalSpacing) / commonParticipantsFlow.rows)
|
||||
}
|
||||
|
||||
property int leftMargin_: {
|
||||
if (inLine || commonParticipantsFlow.rows === 1)
|
||||
return 0
|
||||
var lastParticipants = (commonParticipants.count % commonParticipantsFlow.columns)
|
||||
if (lastParticipants !== 0 && index === commonParticipants.count - lastParticipants) {
|
||||
var compW = commonParticipantsFlow.componentWidth + commonParticipantsFlow.spacing
|
||||
var lastLineW = lastParticipants * compW
|
||||
return Math.floor((commonParticipantsFlow.width - lastLineW) / 2)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
property string uri_: Uri
|
||||
property string deviceId_: Device
|
||||
property string bestName_: BestName
|
||||
property string avatar_: Avatar ? Avatar : ""
|
||||
property string sinkId_: SinkId ? SinkId : ""
|
||||
property bool isLocal_: IsLocal
|
||||
property bool active_: Active
|
||||
property bool videoMuted_: VideoMuted
|
||||
property bool isContact_: IsContact
|
||||
property bool isModerator_: IsModerator
|
||||
property bool audioLocalMuted_: AudioLocalMuted
|
||||
property bool audioModeratorMuted_: AudioModeratorMuted
|
||||
property bool isHandRaised_: HandRaised
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: parent.width
|
||||
height : 30
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
293
src/app/mainview/components/ParticipantsLayoutVertical.qml
Normal file
293
src/app/mainview/components/ParticipantsLayoutVertical.qml
Normal file
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Savoir-faire Linux Inc.
|
||||
* Authors: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
* Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
|
||||
SplitView {
|
||||
id: root
|
||||
|
||||
property int layoutCount: commonParticipants.count + activeParticipants.count
|
||||
property var participantComponent
|
||||
|
||||
orientation: Qt.Vertical
|
||||
handle: Rectangle {
|
||||
implicitWidth: root.width
|
||||
implicitHeight: 11
|
||||
color: "transparent"
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
height: 1
|
||||
width: parent.implicitWidth - 40
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 45
|
||||
anchors.centerIn: parent
|
||||
height: 1
|
||||
color: "black"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
height: 11
|
||||
width: 45
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
height: 2
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
height: 2
|
||||
color: JamiTheme.darkGreyColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: genericParticipantsRect
|
||||
|
||||
TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton }
|
||||
|
||||
SplitView.preferredHeight: (parent.height / 4)
|
||||
SplitView.minimumHeight: parent.height / 6
|
||||
SplitView.maximumHeight: inLine? parent.height / 2 : parent.height
|
||||
|
||||
visible: inLine || CallParticipantsModel.conferenceLayout === CallParticipantsModel.GRID
|
||||
color: "transparent"
|
||||
|
||||
property int lowLimit: 0
|
||||
property int topLimit: commonParticipants.count
|
||||
property int currentPos: 0
|
||||
property int showable: {
|
||||
if (!inLine)
|
||||
return commonParticipants.count
|
||||
if (commonParticipantsFlow.componentWidth === 0)
|
||||
return 1
|
||||
var placeableElements = Math.floor((width * 0.9)/commonParticipantsFlow.componentWidth)
|
||||
if (commonParticipants.count - placeableElements < currentPos)
|
||||
currentPos = Math.max(commonParticipants.count - placeableElements, 0)
|
||||
return Math.max(1, placeableElements)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
RoundButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width : 30
|
||||
height : 30
|
||||
radius: 10
|
||||
text: "<"
|
||||
visible: genericParticipantsRect.currentPos > 0
|
||||
&& activeParticipantsFlow.visible
|
||||
onClicked: {
|
||||
if (genericParticipantsRect.currentPos > 0)
|
||||
genericParticipantsRect.currentPos--
|
||||
}
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.lightGrey_
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: centerItem
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 4
|
||||
|
||||
// GENERIC
|
||||
Flow {
|
||||
id: commonParticipantsFlow
|
||||
anchors.fill: parent
|
||||
|
||||
spacing: 4
|
||||
property int columns: {
|
||||
if (inLine)
|
||||
return commonParticipants.count
|
||||
var ratio = Math.floor(root.width / root.height)
|
||||
// If ratio is 2 we can have 2 times more elements on each columns
|
||||
var wantedCol = Math.max(1, Math.round(Math.sqrt(commonParticipants.count) * ratio))
|
||||
var cols = Math.min(commonParticipants.count, wantedCol)
|
||||
// Optimize with the rows (eg 7 with ratio 2 should have 4 and 3 items, not 6 and 1)
|
||||
var rows = Math.max(1, Math.ceil(commonParticipants.count/cols))
|
||||
return Math.min(Math.ceil(commonParticipants.count / rows), cols)
|
||||
}
|
||||
property int rows: Math.max(1, Math.ceil(commonParticipants.count/columns))
|
||||
property int componentWidth: {
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.columns
|
||||
var w = Math.floor((commonParticipantsFlow.width - totalSpacing)/ commonParticipantsFlow.columns)
|
||||
if (inLine) {
|
||||
w = Math.max(w, height)
|
||||
w = Math.min(w, height * 4 / 3) // Avoid too wide elements
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
Item {
|
||||
height: parent.height
|
||||
width: {
|
||||
if (!inLine)
|
||||
return 0
|
||||
var showed = Math.min(genericParticipantsRect.showable, commonParticipantsFlow.columns)
|
||||
return Math.max(0, Math.ceil((centerItem.width - commonParticipantsFlow.componentWidth * showed) / 2))
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: commonParticipants
|
||||
|
||||
model: GenericParticipantsFilterModel
|
||||
delegate: Loader {
|
||||
sourceComponent: callVideoMedia
|
||||
active: root.visible
|
||||
asynchronous: true
|
||||
visible: {
|
||||
if (status !== Loader.Ready)
|
||||
return false
|
||||
if (inLine)
|
||||
return index >= genericParticipantsRect.currentPos
|
||||
&& index < genericParticipantsRect.currentPos + genericParticipantsRect.showable
|
||||
return true
|
||||
}
|
||||
width: commonParticipantsFlow.componentWidth + leftMargin_
|
||||
height: {
|
||||
if (inLine || commonParticipantsFlow.rows === 1)
|
||||
return genericParticipantsRect.height
|
||||
var totalSpacing = commonParticipantsFlow.spacing * commonParticipantsFlow.rows
|
||||
return Math.floor((genericParticipantsRect.height - totalSpacing)/ commonParticipantsFlow.rows)
|
||||
}
|
||||
|
||||
property int leftMargin_: {
|
||||
if (inLine || commonParticipantsFlow.rows === 1)
|
||||
return 0
|
||||
var lastParticipants = (commonParticipants.count % commonParticipantsFlow.columns)
|
||||
if (lastParticipants !== 0 && index === commonParticipants.count - lastParticipants) {
|
||||
var compW = commonParticipantsFlow.componentWidth + commonParticipantsFlow.spacing
|
||||
var lastLineW = lastParticipants * compW
|
||||
return Math.floor((commonParticipantsFlow.width - lastLineW) / 2)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
property string uri_: Uri
|
||||
property string deviceId_: Device
|
||||
property string bestName_: BestName
|
||||
property string avatar_: Avatar ? Avatar : ""
|
||||
property string sinkId_: SinkId ? SinkId : ""
|
||||
property bool isLocal_: IsLocal
|
||||
property bool active_: Active
|
||||
property bool videoMuted_: VideoMuted
|
||||
property bool isContact_: IsContact
|
||||
property bool isModerator_: IsModerator
|
||||
property bool audioLocalMuted_: AudioLocalMuted
|
||||
property bool audioModeratorMuted_: AudioModeratorMuted
|
||||
property bool isHandRaised_: HandRaised
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RoundButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
width : 30
|
||||
height : 30
|
||||
radius: 10
|
||||
text: ">"
|
||||
visible: genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos
|
||||
&& activeParticipantsFlow.visible
|
||||
onClicked: {
|
||||
if (genericParticipantsRect.topLimit - genericParticipantsRect.showable > genericParticipantsRect.currentPos)
|
||||
genericParticipantsRect.currentPos++
|
||||
}
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: JamiTheme.lightGrey_
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ACTIVE
|
||||
Flow {
|
||||
id: activeParticipantsFlow
|
||||
|
||||
TapHandler { acceptedButtons: Qt.LeftButton | Qt.RightButton }
|
||||
|
||||
SplitView.minimumHeight: parent.height / 4
|
||||
SplitView.maximumHeight: parent.height
|
||||
SplitView.fillHeight: true
|
||||
|
||||
spacing: 8
|
||||
property int columns: Math.max(1, Math.ceil(Math.sqrt(activeParticipants.count)))
|
||||
property int rows: Math.max(1, Math.ceil(activeParticipants.count/columns))
|
||||
property int columnsSpacing: 5 * (columns - 1)
|
||||
property int rowsSpacing: 5 * (rows - 1)
|
||||
|
||||
visible: inLine || CallParticipantsModel.conferenceLayout === CallParticipantsModel.ONE
|
||||
|
||||
Repeater {
|
||||
id: activeParticipants
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
|
||||
model: ActiveParticipantsFilterModel
|
||||
delegate: Loader {
|
||||
active: root.visible
|
||||
asynchronous: true
|
||||
sourceComponent: callVideoMedia
|
||||
visible: status == Loader.Ready
|
||||
|
||||
width: Math.ceil(activeParticipantsFlow.width / activeParticipantsFlow.columns) - activeParticipantsFlow.columnsSpacing
|
||||
height: Math.ceil(activeParticipantsFlow.height / activeParticipantsFlow.rows) - activeParticipantsFlow.rowsSpacing
|
||||
|
||||
property string uri_: Uri
|
||||
property string bestName_: BestName
|
||||
property string avatar_: Avatar ? Avatar : ""
|
||||
property string sinkId_: SinkId ? SinkId : ""
|
||||
property string deviceId_: Device
|
||||
property int leftMargin_: 0
|
||||
property bool isLocal_: IsLocal
|
||||
property bool active_: Active
|
||||
property bool videoMuted_: VideoMuted
|
||||
property bool isContact_: IsContact
|
||||
property bool isModerator_: IsModerator
|
||||
property bool audioLocalMuted_: AudioLocalMuted
|
||||
property bool audioModeratorMuted_: AudioModeratorMuted
|
||||
property bool isHandRaised_: HandRaised
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue