1
0
Fork 0
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:
Aline Gondim Santos 2022-08-01 12:32:32 -03:00
parent 28c2f8cb69
commit 824ba581c8
12 changed files with 668 additions and 259 deletions

View file

@ -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>

View 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

View 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

View file

@ -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) \

View file

@ -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();

View file

@ -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")

View file

@ -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})

View file

@ -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: ""

View file

@ -177,6 +177,7 @@ Rectangle {
anchors.centerIn: parent
anchors.margins: 3
visible: participantsLayer.count !== 0
participantsSide: callOverlay.participantsSide
onCountChanged: {
callOverlay.isConference = participantsLayer.count > 0

View file

@ -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
}
}

View 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
}
}
}
}

View 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
}
}
}
}