From 91f32f2421b1949f9945c0338915ee204e5a39b1 Mon Sep 17 00:00:00 2001 From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> Date: Fri, 2 Feb 2024 18:38:51 -0500 Subject: [PATCH] ongoingcallpage: refactor local preview corner snapping Uses a more declarative approach to anchor the local preview. Change-Id: I2544428a0c2585a8629639566c808dfc2808fd14 --- .../mainview/components/OngoingCallPage.qml | 159 +++++++++--------- tests/qml/src/tst_OngoingCallPage.qml | 64 ++++++- 2 files changed, 138 insertions(+), 85 deletions(-) diff --git a/src/app/mainview/components/OngoingCallPage.qml b/src/app/mainview/components/OngoingCallPage.qml index 9da40a6a0..6eaeadefa 100644 --- a/src/app/mainview/components/OngoingCallPage.qml +++ b/src/app/mainview/components/OngoingCallPage.qml @@ -30,12 +30,11 @@ import "../../commoncomponents" Rectangle { id: root - property point clickPos + // Constraints for the preview component. property int previewMargin: 15 property int previewMarginYTop: previewMargin + 42 property int previewMarginYBottom: previewMargin + 84 - property int previewToX: 0 - property int previewToY: 0 + property alias chatViewContainer: chatViewContainer property string callPreviewId @@ -92,42 +91,6 @@ Rectangle { callOverlay.closeContextMenuAndRelatedWindows(); } - function previewMagneticSnap() { - // Calculate the position where the previewRenderer should attach to. - var previewRendererCenter = Qt.point(previewRenderer.x + previewRenderer.width / 2, previewRenderer.y + previewRenderer.height / 2); - var parentCenter = Qt.point(parent.x + parent.width / 2, parent.y + parent.height / 2); - if (previewRendererCenter.x >= parentCenter.x) { - if (previewRendererCenter.y >= parentCenter.y) { - // Bottom right. - previewToX = Qt.binding(function () { - return callPageMainRect.width - previewRenderer.width - previewMargin; - }); - previewToY = Qt.binding(function () { - return callPageMainRect.height - previewRenderer.height - previewMarginYBottom; - }); - } else { - // Top right. - previewToX = Qt.binding(function () { - return callPageMainRect.width - previewRenderer.width - previewMargin; - }); - previewToY = previewMarginYTop; - } - } else { - if (previewRendererCenter.y >= parentCenter.y) { - // Bottom left. - previewToX = previewMargin; - previewToY = Qt.binding(function () { - return callPageMainRect.height - previewRenderer.height - previewMarginYBottom; - }); - } else { - // Top left. - previewToX = previewMargin; - previewToY = previewMarginYTop; - } - } - previewRenderer.state = "geoChanging"; - } - onWidthChanged: { if (chatViewContainer.visible && root.width < JamiTheme.mainViewPaneMinWidth * 2) { callPageMainRect.visible = false; @@ -209,8 +172,9 @@ Rectangle { LocalVideo { id: previewRenderer - visible: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) && !CurrentCall.isConference + objectName: "localPreview" + visible: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) && !CurrentCall.isConference height: width * invAspectRatio width: Math.max(callPageMainRect.width / 5, JamiTheme.minimumPreviewWidth) x: callPageMainRect.width - previewRenderer.width - previewMargin @@ -241,54 +205,97 @@ Rectangle { controlPreview.start(); } + anchors.topMargin: previewMarginYTop + anchors.leftMargin: previewMargin + anchors.rightMargin: previewMargin + anchors.bottomMargin: previewMarginYBottom + + state: "anchor_top_right" + states: [ + // Not anchored + State { + name: "unanchored" + AnchorChanges { + target: previewRenderer + anchors.top: undefined + anchors.right: undefined + anchors.bottom: undefined + anchors.left: undefined + } + }, + State { + name: "anchor_top_left" + AnchorChanges { + target: previewRenderer + anchors.top: callPageMainRect.top + anchors.left: callPageMainRect.left + } + }, + State { + name: "anchor_top_right" + AnchorChanges { + target: previewRenderer + anchors.top: callPageMainRect.top + anchors.right: callPageMainRect.right + } + }, State { - name: "geoChanging" - PropertyChanges { + name: "anchor_bottom_right" + AnchorChanges { target: previewRenderer - x: previewToX - y: previewToY + anchors.bottom: callPageMainRect.bottom + anchors.right: callPageMainRect.right + } + }, + State { + name: "anchor_bottom_left" + AnchorChanges { + target: previewRenderer + anchors.bottom: callPageMainRect.bottom + anchors.left: callPageMainRect.left } } ] transitions: Transition { - PropertyAnimation { - properties: "x,y" - easing.type: Easing.OutExpo + AnchorAnimation { duration: 250 - - onStopped: { - previewRenderer.state = ""; - } + easing.type: Easing.OutBack + easing.overshoot: 1.5 } } - MouseArea { - id: dragMouseArea - - anchors.fill: previewRenderer - - onPressed: function (mouse) { - clickPos = Qt.point(mouse.x, mouse.y); - } - - onReleased: { - previewRenderer.state = ""; - previewMagneticSnap(); - } - - onPositionChanged: function (mouse) { - // Calculate mouse position relative change. - var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y); - var deltaW = previewRenderer.x + delta.x + previewRenderer.width; - var deltaH = previewRenderer.y + delta.y + previewRenderer.height; - - // Check if the previewRenderer exceeds the border of callPageMainRect. - if (deltaW < callPageMainRect.width && previewRenderer.x + delta.x > 1) - previewRenderer.x += delta.x; - if (deltaH < callPageMainRect.height && previewRenderer.y + delta.y > 1) - previewRenderer.y += delta.y; + DragHandler { + readonly property var container: callPageMainRect + target: parent + dragThreshold: 4 + xAxis.maximum: container.width - parent.width - previewMargin + xAxis.minimum: previewMargin + yAxis.maximum: container.height - parent.height - previewMarginYBottom + yAxis.minimum: previewMarginYTop + onActiveChanged: { + if (active) { + previewRenderer.state = "unanchored"; + } else { + const center = Qt.point(target.x + target.width / 2, + target.y + target.height / 2); + const containerCenter = Qt.point(container.x + container.width / 2, + container.y + container.height / 2); + if (center.x >= containerCenter.x) { + if (center.y >= containerCenter.y) { + previewRenderer.state = "anchor_bottom_right"; + } else { + previewRenderer.state = "anchor_top_right"; + } + } else { + if (center.y >= containerCenter.y) { + previewRenderer.state = "anchor_bottom_left"; + } else { + previewRenderer.state = "anchor_top_left"; + } + } + } } } diff --git a/tests/qml/src/tst_OngoingCallPage.qml b/tests/qml/src/tst_OngoingCallPage.qml index 0485d61c4..8d3e06b30 100644 --- a/tests/qml/src/tst_OngoingCallPage.qml +++ b/tests/qml/src/tst_OngoingCallPage.qml @@ -21,6 +21,7 @@ import QtTest import net.jami.Models 1.1 import net.jami.Constants 1.1 +import net.jami.Adapters 1.1 import "../../../src/app/" import "../../../src/app/mainview/components" @@ -33,15 +34,11 @@ TestWrapper { height: 600 TestCase { - name: "Check basic visibility of action bar during a call" + name: "Check various components within the OngoingCallPage" when: windowShown // Mouse events can only be handled // after the window has been shown. - property var mainOverlay - function initTestCase() { - mainOverlay = findChild(uut, "mainOverlay") - // The CallActionBar on the OngoingCallPage starts out invisible and // is made visible whenever the user moves their mouse. // This is implemented via an event filter in the CallOverlayModel @@ -49,12 +46,15 @@ TestWrapper { // visible. In the actual Jami application, this happens when a call // is started, but we need to toggle the visiblity manually here // because the MainOverlay is visible at the beginning of the test. - mainOverlay.visible = false - mainOverlay.visible = true + const mainOverlay = findChild(uut, "mainOverlay"); + mainOverlay.visible = false; + mainOverlay.visible = true; } - function test_checkCallActionBarVisibility() { - var callActionBar = findChild(mainOverlay, "callActionBar") + // The following test is labeled with "0" to make sure it runs first. + // This prevents having to wait for the CallActionBar to fade out. + function test_0_checkCallActionBarVisibility() { + var callActionBar = findChild(uut, "callActionBar") // The primary and secondary actions in the CallActionBar are currently being added // one by one (not using a loop) to CallOverlayModel in the Component.onCompleted @@ -78,6 +78,52 @@ TestWrapper { wait(waitTime) compare(callActionBar.visible, true) } + + function test_checkLocalPreviewAnchoring() { + const localPreview = findChild(uut, "localPreview"); + const container = localPreview.parent; + + // The preview should normally be invisible at first, but there is a bug + // in the current implementation that makes it visible. This will need to + // be adjusted once the bug is fixed. + compare(localPreview.visible, true); + + // Start a preview of a local resource. + const dummyImgFile = UtilsAdapter.createDummyImage(); + localPreview.startWithId(UtilsAdapter.urlFromLocalPath(dummyImgFile)); + + // First check that the preview is anchored. + verify(localPreview.state.indexOf("unanchored") === -1); + + const containerCenter = Qt.point(container.width / 2, container.height / 2); + function moveAndVerifyState(dx, dy, expectedState) { + const previewCenter = Qt.point(localPreview.x + localPreview.width / 2, + localPreview.y + localPreview.height / 2); + const destination = Qt.point(containerCenter.x + dx, + containerCenter.y + dy); + // Position the mouse at the center of the preview, then drag it until + // we reach the destination point. + mouseDrag(container, previewCenter.x, previewCenter.y, + destination.x - previewCenter.x, destination.y - previewCenter.y); + wait(250); + compare(localPreview.state, expectedState); + } + + const dx = 1; + const dy = 1; + moveAndVerifyState(-dx, -dy, "anchor_top_left"); + moveAndVerifyState(dx, -dy, "anchor_top_right"); + moveAndVerifyState(-dx, dy, "anchor_bottom_left"); + moveAndVerifyState(dx, dy, "anchor_bottom_right"); + + // Verify that during a drag process, the preview is unanchored. + mousePress(localPreview); + mouseMove(localPreview, 100, 100); + verify(localPreview.state.indexOf("unanchored") !== -1); + + // Stop the preview. + localPreview.startWithId(""); + } } } } -- GitLab