From 2496f6708ae5c14cb18837e04a5de052f15724e2 Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Fri, 18 Feb 2022 16:53:27 -0600 Subject: [PATCH] Making some more functional ui --- src/CMakeLists.txt | 4 +- src/main.cpp | 146 +----------------------------- src/{ => mpv}/mpvhelpers.h | 0 src/{ => mpv}/mpvobject.cpp | 0 src/{ => mpv}/mpvobject.h | 0 src/{ => mpv}/qthelper.hpp | 0 src/qml/presenter/DragHandle.qml | 120 ++++++++++++++++++++++++ src/qml/presenter/LeftDock.qml | 112 +++++++++-------------- src/qml/presenter/Library.qml | 29 ++++++ src/qml/presenter/MainWindow.qml | 3 +- src/qml/presenter/Slide.qml | 25 +++-- src/qml/presenter/SlideEditor.qml | 2 +- src/qml/presenter/SongEditor.qml | 10 +- src/resources.qrc | 1 + src/songlistmodel.cpp | 2 +- 15 files changed, 218 insertions(+), 236 deletions(-) rename src/{ => mpv}/mpvhelpers.h (100%) rename src/{ => mpv}/mpvobject.cpp (100%) rename src/{ => mpv}/mpvobject.h (100%) rename src/{ => mpv}/qthelper.hpp (100%) create mode 100644 src/qml/presenter/DragHandle.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a7250b8..5260e0d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,8 @@ target_sources(presenter PRIVATE main.cpp resources.qrc songlistmodel.cpp songlistmodel.h - mpvobject.h mpvobject.cpp - qthelper.hpp mpvhelpers.h + mpv/mpvobject.h mpv/mpvobject.cpp + mpv/qthelper.hpp mpv/mpvhelpers.h ) target_link_libraries(presenter diff --git a/src/main.cpp b/src/main.cpp index 5d6f8c5..010d11f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,7 @@ #include #include "songlistmodel.h" -#include "mpvobject.h" +#include "mpv/mpvobject.h" int main(int argc, char *argv[]) { @@ -59,147 +59,3 @@ int main(int argc, char *argv[]) return app.exec(); } -// namespace -// { -// void on_mpv_events(void *ctx) -// { -// Q_UNUSED(ctx) -// } - -// void on_mpv_redraw(void *ctx) -// { -// MpvObject::on_update(ctx); -// } - -// static void *get_proc_address_mpv(void *ctx, const char *name) -// { -// Q_UNUSED(ctx) - -// QOpenGLContext *glctx = QOpenGLContext::currentContext(); -// if (!glctx) return nullptr; - -// return reinterpret_cast(glctx->getProcAddress(QByteArray(name))); -// } - -// } - -// class MpvRenderer : public QQuickFramebufferObject::Renderer -// { -// MpvObject *obj; - -// public: -// MpvRenderer(MpvObject *new_obj) -// : obj{new_obj} -// { -// mpv_set_wakeup_callback(obj->mpv, on_mpv_events, nullptr); -// } - -// virtual ~MpvRenderer() -// {} - -// // This function is called when a new FBO is needed. -// // This happens on the initial frame. -// QOpenGLFramebufferObject * createFramebufferObject(const QSize &size) -// { -// // init mpv_gl: -// if (!obj->mpv_gl) -// { -// mpv_opengl_init_params gl_init_params{get_proc_address_mpv, nullptr, nullptr}; -// mpv_render_param params[]{ -// {MPV_RENDER_PARAM_API_TYPE, const_cast(MPV_RENDER_API_TYPE_OPENGL)}, -// {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, -// {MPV_RENDER_PARAM_INVALID, nullptr} -// }; - -// if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0) -// throw std::runtime_error("failed to initialize mpv GL context"); -// mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj); -// } - -// return QQuickFramebufferObject::Renderer::createFramebufferObject(size); -// } - -// void render() -// { -// obj->window()->resetOpenGLState(); - -// QOpenGLFramebufferObject *fbo = framebufferObject(); -// mpv_opengl_fbo mpfbo{.fbo = static_cast(fbo->handle()), .w = fbo->width(), .h = fbo->height(), .internal_format = 0}; -// int flip_y{0}; - -// mpv_render_param params[] = { -// // Specify the default framebuffer (0) as target. This will -// // render onto the entire screen. If you want to show the video -// // in a smaller rectangle or apply fancy transformations, you'll -// // need to render into a separate FBO and draw it manually. -// {MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo}, -// // Flip rendering (needed due to flipped GL coordinate system). -// {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, -// {MPV_RENDER_PARAM_INVALID, nullptr} -// }; -// // See render_gl.h on what OpenGL environment mpv expects, and -// // other API details. -// mpv_render_context_render(obj->mpv_gl, params); - -// obj->window()->resetOpenGLState(); -// } -// }; - - -// MpvObject::MpvObject(QQuickItem * parent) -// : QQuickFramebufferObject(parent), mpv{mpv_create()}, mpv_gl(nullptr) -// { -// if (!mpv) -// throw std::runtime_error("could not create mpv context"); - -// mpv_set_option_string(mpv, "terminal", "yes"); -// mpv_set_option_string(mpv, "msg-level", "all=v"); - -// if (mpv_initialize(mpv) < 0) -// throw std::runtime_error("could not initialize mpv context"); - -// // Request hw decoding, just for testing. -// mpv::qt::set_option_variant(mpv, "hwdec", "auto"); - -// connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate, -// Qt::QueuedConnection); -// } - -// MpvObject::~MpvObject() -// { -// if (mpv_gl) // only initialized if something got drawn -// { -// mpv_render_context_free(mpv_gl); -// } - -// mpv_terminate_destroy(mpv); -// } - -// void MpvObject::on_update(void *ctx) -// { -// MpvObject *self = (MpvObject *)ctx; -// emit self->onUpdate(); -// } - -// // connected to onUpdate(); signal makes sure it runs on the GUI thread -// void MpvObject::doUpdate() -// { -// update(); -// } - -// void MpvObject::command(const QVariant& params) -// { -// mpv::qt::command_variant(mpv, params); -// } - -// void MpvObject::setProperty(const QString& name, const QVariant& value) -// { -// mpv::qt::set_property_variant(mpv, name, value); -// } - -// QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const -// { -// window()->setPersistentOpenGLContext(true); -// window()->setPersistentSceneGraph(true); -// return new MpvRenderer(const_cast(this)); -// } diff --git a/src/mpvhelpers.h b/src/mpv/mpvhelpers.h similarity index 100% rename from src/mpvhelpers.h rename to src/mpv/mpvhelpers.h diff --git a/src/mpvobject.cpp b/src/mpv/mpvobject.cpp similarity index 100% rename from src/mpvobject.cpp rename to src/mpv/mpvobject.cpp diff --git a/src/mpvobject.h b/src/mpv/mpvobject.h similarity index 100% rename from src/mpvobject.h rename to src/mpv/mpvobject.h diff --git a/src/qthelper.hpp b/src/mpv/qthelper.hpp similarity index 100% rename from src/qthelper.hpp rename to src/mpv/qthelper.hpp diff --git a/src/qml/presenter/DragHandle.qml b/src/qml/presenter/DragHandle.qml new file mode 100644 index 0000000..693bb6d --- /dev/null +++ b/src/qml/presenter/DragHandle.qml @@ -0,0 +1,120 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.13 as Kirigami + +Item { + id: root + + /** + * listItem: Item + * The id of the delegate that we want to drag around, which *must* + * be a child of the actual ListView's delegate + */ + property Item listItem + + /** + * listView: Listview + * The id of the ListView the delegates belong to. + */ + property ListView listView + + /** + * Emitted when the drag handle wants to move the item in the model + * The following example does the move in the case a ListModel is used + * @code + * onMoveRequested: listModel.move(oldIndex, newIndex, 1) + * @endcode + * @param oldIndex the index the item is currently at + * @param newIndex the index we want to move the item to + */ + signal moveRequested(int oldIndex, int newIndex) + + /** + * Emitted when the drag operation is complete and the item has been + * dropped in the new final position + */ + signal dropped() + + MouseArea { + id: mouseArea + anchors.fill: parent + drag { + target: listItem + axis: Drag.YAxis + minimumY: 0 + maximumY: listView.height - listItem.height + } + /* cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor */ + + property int startY + property int mouseDownY + property Item originalParent + property int autoScrollThreshold: (listView.contentHeight > listView.height) ? listItem.height * 3 : 0 + opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6 + + function arrangeItem() { + var newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(listItem, 0, 0).y + mouseArea.mouseDownY); + + if (Math.abs(listItem.y - mouseArea.startY) > height && newIndex > -1 && newIndex !== index) { + root.moveRequested(index, newIndex); + } + } + + preventStealing: true + onPressed: { + mouseArea.originalParent = listItem.parent; + listItem.parent = listView; + listItem.y = mouseArea.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y; + mouseArea.originalParent.z = 99; + mouseArea.startY = listItem.y; + mouseArea.mouseDownY = mouse.y; + } + + onPositionChanged: { + if (!pressed) { + return; + } + mouseArea.arrangeItem(); + + scrollTimer.interval = 500 * Math.max(0.1, (1-Math.max(mouseArea.autoScrollThreshold - listItem.y, listItem.y - listView.height + mouseArea.autoScrollThreshold + listItem.height) / mouseArea.autoScrollThreshold)); + scrollTimer.running = (listItem.y < mouseArea.autoScrollThreshold || + listItem.y > listView.height - mouseArea.autoScrollThreshold); + } + onReleased: { + listItem.y = mouseArea.originalParent.mapFromItem(listItem, 0, 0).y; + listItem.parent = mouseArea.originalParent; + dropAnimation.running = true; + scrollTimer.running = false; + root.dropped(); + } + onCanceled: released() + SequentialAnimation { + id: dropAnimation + YAnimator { + target: listItem + from: listItem.y + to: 0 + duration: Kirigami.Units.longDuration + easing.type: Easing.InOutQuad + } + PropertyAction { + target: listItem.parent + property: "z" + value: 0 + } + } + Timer { + id: scrollTimer + interval: 500 + repeat: true + onTriggered: { + if (listItem.y < mouseArea.autoScrollThreshold) { + listView.contentY = Math.max(0, listView.contentY - Kirigami.Units.gridUnit) + } else { + listView.contentY = Math.min(listView.contentHeight - listView.height, listView.contentY + Kirigami.Units.gridUnit) + } + mouseArea.arrangeItem(); + } + } + } +} diff --git a/src/qml/presenter/LeftDock.qml b/src/qml/presenter/LeftDock.qml index 5256013..ed58b5c 100644 --- a/src/qml/presenter/LeftDock.qml +++ b/src/qml/presenter/LeftDock.qml @@ -6,10 +6,13 @@ import QtQuick.Layouts 1.2 import QtMultimedia 5.15 import QtAudioEngine 1.15 import org.kde.kirigami 2.13 as Kirigami +import "./" as Presenter ColumnLayout { id: root + property var selectedItem: serviceItemList.selected + Rectangle { id: headerBackground color: Kirigami.Theme.backgroundColor @@ -31,20 +34,58 @@ ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true model: listModel - delegate: itemDelegate + delegate: Kirigami.DelegateRecycler { + width: serviceItemList.width + sourceComponent: itemDelegate + } clip: true spacing: 2 - + addDisplaced: Transition { + NumberAnimation {properties: "x, y"; duration: 100} + } + moveDisplaced: Transition { + NumberAnimation { properties: "x, y"; duration: 100 } + } + remove: Transition { + NumberAnimation { properties: "x, y"; duration: 100 } + NumberAnimation { properties: "opacity"; duration: 100 } + } + + removeDisplaced: Transition { + NumberAnimation { properties: "x, y"; duration: 100 } + } + + displaced: Transition { + NumberAnimation {properties: "x, y"; duration: 100} + } + Component { id: itemDelegate Kirigami.BasicListItem { + id: serviceItem width: serviceItemList.width height:50 label: itemName subtitle: type hoverEnabled: true + backgroundColor: serviceDrop.containsDrag ? Kirigami.Theme.highlightColor : Kirigami.Theme.viewBackgroundColor onClicked: serviceItemList.currentIndex = index + Presenter.DragHandle { + listItem: serviceItem + listView: serviceItemList + onMoveRequested: listModel.move(oldIndex, newIndex, 1) + anchors.fill: parent + } + + DropArea { + id: serviceDrop + Layout.fillHeight: true + Layout.fillWidth: true + onEntered: showPassiveNotification(drag.source + " has entered") + onDropped: listModel.append(drag.source) + } + } } @@ -70,71 +111,8 @@ ColumnLayout { itemName: "Marvelous Light" type: "song" } - ListElement { - itemName: "10 reason to use church presenter" - type: "video" - } - ListElement { - itemName: "10,000 Reason" - type: "song" - } - ListElement { - itemName: "Marvelous Light" - type: "song" - } - ListElement { - itemName: "10 reason to use church presenter" - type: "video" - } - ListElement { - itemName: "10,000 Reason" - type: "song" - } - ListElement { - itemName: "Marvelous Light" - type: "song" - } - ListElement { - itemName: "10 reason to use church presenter" - type: "video" - } - ListElement { - itemName: "10,000 Reason" - type: "song" - } - ListElement { - itemName: "Marvelous Light" - type: "song" - } - ListElement { - itemName: "10 reason to use church presenter" - type: "video" - } - ListElement { - itemName: "10,000 Reason" - type: "song" - } - ListElement { - itemName: "Marvelous Light" - type: "song" - } - ListElement { - itemName: "10 reason to use church presenter" - type: "video" - } - ListElement { - itemName: "10,000 Reason" - type: "song" - } - ListElement { - itemName: "Marvelous Light" - type: "song" - } - ListElement { - itemName: "10 reason to use church presenter" - type: "video" - } } - } + } + } diff --git a/src/qml/presenter/Library.qml b/src/qml/presenter/Library.qml index b3a2b8b..e194b52 100644 --- a/src/qml/presenter/Library.qml +++ b/src/qml/presenter/Library.qml @@ -78,6 +78,7 @@ Item { Component { id: itemDelegate Kirigami.BasicListItem { + id: songListItem width: ListView.view.width height:40 label: title @@ -91,6 +92,34 @@ Item { songAuthor = author showPassiveNotification(songLyrics, 3000) } + + Drag.active: dragHandler.drag.active + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + + MouseArea { + id: dragHandler + anchors.fill: parent + drag { + target: songListItem + onActiveChanged: { + if (dragHandler.drag.active) { + draggedLibraryItem = songLibraryList.currentItem + showPassiveNotification(index) + } + } + } + } + + states: State { + name: "dragged" + when: songListItem.Drag.active + PropertyChanges { + target: songListItem + x: x + y: y + } + } } } diff --git a/src/qml/presenter/MainWindow.qml b/src/qml/presenter/MainWindow.qml index 2a4ae35..696c56e 100644 --- a/src/qml/presenter/MainWindow.qml +++ b/src/qml/presenter/MainWindow.qml @@ -18,6 +18,7 @@ Controls.Page { property string songLyrics: "" property string songAuthor: "" property int blurRadius: 0 + property var draggedLibraryItem Item { id: mainItem @@ -113,7 +114,7 @@ Controls.Page { nameFilters: ["Image files (*.jpg *.jpeg *.png *.JPG *.JPEG *.PNG)"] onAccepted: { videoBackground = "" - imageBackground = imageFileDialog.fileUrls + imageBackground = imageFileDialog.fileUrls[0] } onRejected: { print("Canceled") diff --git a/src/qml/presenter/Slide.qml b/src/qml/presenter/Slide.qml index 9893881..e94b561 100644 --- a/src/qml/presenter/Slide.qml +++ b/src/qml/presenter/Slide.qml @@ -12,7 +12,7 @@ Item { id: root anchors.fill: parent - property real textSize: 50 + property real textSize: 26 property bool editMode: false property bool dropShadow: false property url imageSource: imageBackground @@ -83,7 +83,7 @@ Item { source: imageSource fillMode: Image.PreserveAspectCrop clip: true - visible: false + visible: true } @@ -100,19 +100,18 @@ Item { font.family: chosenFont style: Text.Raised anchors.centerIn: parent + clip: true + + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: 5 + verticalOffset: 5 + radius: 11.0 + samples: 24 + color: "#80000000" + } } - DropShadow { - id: textDropShadow - source: lyrics - anchors.fill: lyrics - horizontalOffset: 3 - verticalOffset: 3 - radius: 8.0 - samples: 17 - color: "#80000000" - visible: true - } } } } diff --git a/src/qml/presenter/SlideEditor.qml b/src/qml/presenter/SlideEditor.qml index cbc486e..c44b057 100644 --- a/src/qml/presenter/SlideEditor.qml +++ b/src/qml/presenter/SlideEditor.qml @@ -10,7 +10,7 @@ import "./" as Presenter Item { id: root - + Presenter.Slide { id: representation editMode: true diff --git a/src/qml/presenter/SongEditor.qml b/src/qml/presenter/SongEditor.qml index d0970ed..ce0ea12 100644 --- a/src/qml/presenter/SongEditor.qml +++ b/src/qml/presenter/SongEditor.qml @@ -109,9 +109,8 @@ Item { id: slideBar color: Kirigami.Theme.highlightColor - Layout.preferredWidth: 400 + Layout.preferredWidth: 700 Layout.preferredHeight: songTitleField.height - Layout.fillWidth: true Layout.rightMargin: 20 } @@ -140,12 +139,11 @@ Item { Presenter.SlideEditor { id: slideEditor - Layout.preferredHeight: 800 - Layout.fillWidth: true - Layout.fillHeight: true + Layout.preferredWidth: 700 + Layout.preferredHeight: 394 Layout.bottomMargin: 30 Layout.rightMargin: 20 - Layout.rowSpan: 15 + Layout.rowSpan: 2 } Controls.TextField { diff --git a/src/resources.qrc b/src/resources.qrc index c26e9e9..8645259 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -14,6 +14,7 @@ qml/presenter/SongEditor.qml qml/presenter/Slide.qml qml/presenter/SlideEditor.qml + qml/presenter/DragHandle.qml assets/parallel.jpg diff --git a/src/songlistmodel.cpp b/src/songlistmodel.cpp index a79c30c..a0caef3 100644 --- a/src/songlistmodel.cpp +++ b/src/songlistmodel.cpp @@ -10,7 +10,7 @@ SongListModel::SongListModel(QObject *parent) << Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "") << Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "") << Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "") - << Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "") + << Data("River", "I'm going down to the river", "Jordan Feliz", "13470183", "") << Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "") << Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "") << Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")