diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 90f11fd83..e18fc7836 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -55,6 +55,7 @@ set(SOURCES src/app/Database.cpp src/app/DefaultTranslator.cpp src/app/Logger.cpp + src/components/camera/Camera.cpp src/components/chat/ChatModel.cpp src/components/chat/ChatProxyModel.cpp src/components/contacts/ContactModel.cpp @@ -76,6 +77,7 @@ set(HEADERS src/app/Database.hpp src/app/DefaultTranslator.hpp src/app/Logger.hpp + src/components/camera/Camera.hpp src/components/chat/ChatModel.hpp src/components/chat/ChatProxyModel.hpp src/components/contacts/ContactModel.hpp diff --git a/tests/src/app/App.cpp b/tests/src/app/App.cpp index 219a2eac3..b96a153df 100644 --- a/tests/src/app/App.cpp +++ b/tests/src/app/App.cpp @@ -4,6 +4,7 @@ #include #include +#include "../components/camera/Camera.hpp" #include "../components/chat/ChatProxyModel.hpp" #include "../components/contacts/ContactsListModel.hpp" #include "../components/contacts/ContactsListProxyModel.hpp" @@ -21,7 +22,7 @@ #define QML_VIEW_MAIN_WINDOW "qrc:/ui/views/App/MainWindow/MainWindow.qml" #define QML_VIEW_CALL_WINDOW "qrc:/ui/views/App/Calls/Calls.qml" -// =================================================================== +// ============================================================================= App *App::m_instance = nullptr; @@ -102,6 +103,8 @@ void App::registerTypes () { ); // Register models. + qmlRegisterType("Linphone", 1, 0, "Camera"); + qmlRegisterUncreatableType( "Linphone", 1, 0, "ContactModel", "ContactModel is uncreatable" ); @@ -163,14 +166,17 @@ void App::setTrayIcon () { root->connect(restore_action, &QAction::triggered, root, &QQuickWindow::showNormal); // trayIcon: Left click actions. - root->connect(m_system_tray_icon, &QSystemTrayIcon::activated, [root](QSystemTrayIcon::ActivationReason reason) { - if (reason == QSystemTrayIcon::Trigger) { - if (root->visibility() == QWindow::Hidden) - root->showNormal(); - else - root->hide(); + root->connect( + m_system_tray_icon, &QSystemTrayIcon::activated, [root]( + QSystemTrayIcon::ActivationReason reason) { + if (reason == QSystemTrayIcon::Trigger) { + if (root->visibility() == QWindow::Hidden) + root->showNormal(); + else + root->hide(); + } } - }); + ); // Build trayIcon menu. menu->addAction(restore_action); diff --git a/tests/src/components/camera/Camera.cpp b/tests/src/components/camera/Camera.cpp new file mode 100644 index 000000000..b7a53eb0d --- /dev/null +++ b/tests/src/components/camera/Camera.cpp @@ -0,0 +1,140 @@ +#include + +#include "Camera.hpp" + +#define ATTRIBUTE_VERTEX 0 + +// ============================================================================= + +static const char *_vertex_shader = "attribute vec2 vertex;" + "uniform mat4 projection;" + "void main() {" + " gl_Position = projection * vec4(vertex.xy, 0, 1);" + "}"; + +static const char *_fragment_shader = "void main() {" + " gl_FragColor = vec4(vec3(1.0, 0.0, 0.0), 1.0);" + "}"; + +static const GLfloat _camera_vertices[] = { + 0.0f, 0.0f, + 50.0f, 0.0f, + 0.0f, 50.0f, + 50.0f, 50.0f +}; + +// ----------------------------------------------------------------------------- + +struct CameraStateBinder { + CameraStateBinder (CameraRenderer *renderer) : m_renderer(renderer) { + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + + f->glEnable(GL_DEPTH_TEST); + f->glEnable(GL_CULL_FACE); + f->glDepthMask(GL_TRUE); + f->glDepthFunc(GL_LESS); + f->glFrontFace(GL_CCW); + f->glCullFace(GL_BACK); + + m_renderer->m_program->bind(); + } + + ~CameraStateBinder () { + m_renderer->m_program->release(); + + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glDisable(GL_CULL_FACE); + f->glDisable(GL_DEPTH_TEST); + } + + CameraRenderer *m_renderer; +}; + +// ----------------------------------------------------------------------------- + +QOpenGLFramebufferObject *CameraRenderer::createFramebufferObject (const QSize &size) { + m_projection.setToIdentity(); + m_projection.ortho( + 0, size.width(), + 0, size.height(), + -1, 1 + ); + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + format.setSamples(4); + + return new QOpenGLFramebufferObject(size, format); +} + +void CameraRenderer::render () { + init(); + + m_vao.bind(); + + CameraStateBinder state(this); + + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClearColor(0.f, 0.f, 0.f, 1.f); + f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + m_program->setUniformValue(m_projection_loc, m_projection); + + // Draw. + f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + m_vao.release(); +} + +void CameraRenderer::init () { + if (m_inited) + return; + + m_inited = true; + + initProgram(); + initBuffer(); +} + +void CameraRenderer::initBuffer () { + QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); + + m_vbo.create(); + m_vbo.bind(); + m_vbo.allocate(&_camera_vertices, sizeof _camera_vertices); + + m_vbo.bind(); + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glEnableVertexAttribArray(ATTRIBUTE_VERTEX); + f->glVertexAttribPointer(ATTRIBUTE_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, 0); + m_vbo.release(); +} + +void CameraRenderer::initProgram () { + m_program.reset(new QOpenGLShaderProgram()); + m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, _vertex_shader); + m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, _fragment_shader); + m_program->bindAttributeLocation("vertex", ATTRIBUTE_VERTEX); + m_program->link(); + + m_projection_loc = m_program->uniformLocation("projection"); +} + +// ----------------------------------------------------------------------------- + +Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { + setAcceptHoverEvents(true); + setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); +} + +QQuickFramebufferObject::Renderer *Camera::createRenderer () const { + return new CameraRenderer(); +} + +void Camera::hoverMoveEvent (QHoverEvent *) {} + +void Camera::mousePressEvent (QMouseEvent *) { + setFocus(true); +} + +void Camera::keyPressEvent (QKeyEvent *) {} diff --git a/tests/src/components/camera/Camera.hpp b/tests/src/components/camera/Camera.hpp new file mode 100644 index 000000000..8d717ae37 --- /dev/null +++ b/tests/src/components/camera/Camera.hpp @@ -0,0 +1,53 @@ +#ifndef CAMERA_H_ +#define CAMERA_H_ + +#include +#include +#include +#include +#include + +// ============================================================================= + +class CameraRenderer : public QQuickFramebufferObject::Renderer { + friend struct CameraStateBinder; + +public: + QOpenGLFramebufferObject *createFramebufferObject (const QSize &size) override; + + void render () override; + +private: + void init (); + void initBuffer (); + void initProgram (); + + bool m_inited = false; + + QMatrix4x4 m_projection; + int m_projection_loc; + + QOpenGLVertexArrayObject m_vao; + QOpenGLBuffer m_vbo; + QScopedPointer m_program; +}; + +// ----------------------------------------------------------------------------- + +class Camera : public QQuickFramebufferObject { + Q_OBJECT; + +public: + Camera (QQuickItem *parent = Q_NULLPTR); + ~Camera () = default; + + QQuickFramebufferObject::Renderer *createRenderer () const override; + +protected: + void hoverMoveEvent (QHoverEvent *event) override; + void mousePressEvent (QMouseEvent *event) override; + + void keyPressEvent (QKeyEvent *event) override; +}; + +#endif // CAMERA_H_ diff --git a/tests/ui/views/App/Calls/Incall.qml b/tests/ui/views/App/Calls/Incall.qml index f940b3cec..ac7a5f1ca 100644 --- a/tests/ui/views/App/Calls/Incall.qml +++ b/tests/ui/views/App/Calls/Incall.qml @@ -8,7 +8,7 @@ import LinphoneUtils 1.0 import App.Styles 1.0 -// =================================================================== +// ============================================================================= Rectangle { id: call @@ -20,7 +20,7 @@ Rectangle { sipAddress ) || sipAddress - // ----------------------------------------------------------------- + // --------------------------------------------------------------------------- color: StartingCallStyle.backgroundColor @@ -32,11 +32,11 @@ Rectangle { spacing: 0 - // --------------------------------------------------------------- + // ------------------------------------------------------------------------- // Call info. - // --------------------------------------------------------------- + // ------------------------------------------------------------------------- - RowLayout { + Item { id: info Layout.fillWidth: true @@ -45,15 +45,29 @@ Rectangle { Layout.preferredHeight: StartingCallStyle.contactDescriptionHeight Icon { - iconSize: 40 + id: callQuality + + anchors.left: parent.left icon: 'call_quality_' + 2 + iconSize: 40 } - Item { - Layout.fillWidth: true + ContactDescription { + id: contactDescription + + anchors.centerIn: parent + horizontalTextAlignment: Text.AlignHCenter + sipAddress: call.sipAddress + username: LinphoneUtils.getContactUsername(_contact) + + height: parent.height + width: parent.width - cameraActions.width - callQuality.width - 150 } ActionBar { + id: cameraActions + + anchors.right: parent.right iconSize: 40 ActionButton { @@ -70,18 +84,9 @@ Rectangle { } } - ContactDescription { - id: contactDescription - - anchors.fill: info - username: LinphoneUtils.getContactUsername(_contact) - sipAddress: call.sipAddress - horizontalTextAlignment: Text.AlignHCenter - } - - // --------------------------------------------------------------- + // ------------------------------------------------------------------------- // Contact visual. - // --------------------------------------------------------------- + // ------------------------------------------------------------------------- Item { id: container @@ -90,26 +95,41 @@ Rectangle { Layout.fillHeight: true Layout.margins: StartingCallStyle.containerMargins - Avatar { + Component { id: avatar - function _computeAvatarSize () { - var height = container.height - var width = container.width + Avatar { + function _computeAvatarSize () { + var height = container.height + var width = container.width - var size = height < StartingCallStyle.avatar.maxSize && height > 0 - ? height - : StartingCallStyle.avatar.maxSize - return size < width ? size : width + var size = height < StartingCallStyle.avatar.maxSize && height > 0 + ? height + : StartingCallStyle.avatar.maxSize + return size < width ? size : width + } + + backgroundColor: StartingCallStyle.avatar.backgroundColor + image: _contact.avatar + username: contactDescription.username + + height: _computeAvatarSize() + width: height } + } + Component { + id: camera + + Camera { + height: container.height + width: container.width + } + } + + Loader { anchors.centerIn: parent - backgroundColor: StartingCallStyle.avatar.backgroundColor - image: _contact.avatar - username: contactDescription.username - - height: _computeAvatarSize() - width: height + sourceComponent: isVideoCall ? camera : avatar } }