Skip to content

Commit 021872c

Browse files
authored
feat: generalize drag and drop support (#1727)
* feat: generalize drag and drop support * feat: disable flicking for list and grid views * feat: implement drag and drop for some root items * feat: add proper drag icon * feat: add dragContent prop for list/grid items
1 parent 70482f1 commit 021872c

43 files changed

Lines changed: 487 additions & 146 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

figura/tsapi.fig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ service WindowManagement {
188188
struct ClipboardContent {
189189
text?: string;
190190
html?: string;
191-
path?: string;
191+
urls?: string[];
192192
};
193193

194194
struct ClipboardOptions {

src/server/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ set(SRCS
162162
src/color-formatter.cpp
163163

164164
src/services/clipboard/clipboard-server.hpp
165+
src/services/clipboard/clipboard-content.hpp
165166
src/services/clipboard/clipboard-mime.hpp
166167
src/services/clipboard/clipboard-mime.cpp
167168
src/services/clipboard/clipboard-db.hpp
@@ -474,6 +475,8 @@ set(SRCS
474475
src/qml/root-search-model.cpp
475476
src/qml/root-search-sources.hpp
476477
src/qml/root-search-sources.cpp
478+
src/qml/drag-utils.hpp
479+
src/qml/drag-utils.cpp
477480
src/qml/section-source.hpp
478481
src/qml/section-list-model.hpp
479482
src/qml/section-list-model.cpp
@@ -957,6 +960,7 @@ set(VICINAE_QML_FILES
957960
src/qml/qml/ListAccessory.qml
958961
src/qml/qml/ListAccessoryRow.qml
959962
src/qml/qml/SelectableDelegate.qml
963+
src/qml/qml/DraggableMouseArea.qml
960964
src/qml/qml/SectionHeader.qml
961965
src/qml/qml/Footer.qml
962966
src/qml/qml/FooterButton.qml

src/server/src/extend/grid-model.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "extend/pagination-model.hpp"
77
#include "extend/dropdown-model.hpp"
88
#include "ui/image/url.hpp"
9+
#include "services/clipboard/clipboard-content.hpp"
910

1011
enum GridFit { GridContain, GridFill };
1112

@@ -34,6 +35,7 @@ struct GridItemViewModel {
3435
std::optional<std::string> tooltip;
3536
std::vector<std::string> keywords;
3637
std::optional<ActionPannelModel> actionPannel;
38+
std::optional<Clipboard::Content> dragContent;
3739
};
3840

3941
struct GridSectionModel {

src/server/src/extend/list-model.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "extend/image-model.hpp"
88
#include "extend/dropdown-model.hpp"
99
#include "extend/pagination-model.hpp"
10+
#include "services/clipboard/clipboard-content.hpp"
1011

1112
struct ListItemViewModel {
1213
bool changed = false;
@@ -18,6 +19,7 @@ struct ListItemViewModel {
1819
std::optional<ActionPannelModel> actionPannel;
1920
std::vector<AccessoryModel> accessories;
2021
std::vector<std::string> keywords;
22+
std::optional<Clipboard::Content> dragContent;
2123
};
2224

2325
struct ListSectionModel {

src/server/src/extend/model-deser.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,13 +297,20 @@ template <> struct glz::meta<ListItemChild> {
297297
static constexpr auto ids = std::array{"action-panel", "list-item-detail"};
298298
};
299299

300+
struct ClipboardContentWire {
301+
std::optional<std::string> text;
302+
std::optional<std::string> html;
303+
std::vector<std::string> urls;
304+
};
305+
300306
struct ListItemViewModelWire {
301307
std::string id;
302308
FlexString title;
303309
FlexString subtitle;
304310
std::optional<ImageWrappedWire> icon;
305311
std::vector<AccessoryWire> accessories;
306312
std::vector<std::string> keywords;
313+
std::optional<ClipboardContentWire> dragContent;
307314
std::vector<ListItemChild> children;
308315
};
309316

@@ -357,6 +364,7 @@ struct GridItemViewModelWire {
357364
GridContentWire content;
358365
std::optional<std::string> tooltip;
359366
std::vector<std::string> keywords;
367+
std::optional<ClipboardContentWire> dragContent;
360368
std::vector<SingleAPChild> children;
361369
};
362370

@@ -769,6 +777,24 @@ static EmptyViewModel toEmptyViewModel(EmptyViewModelWire w) {
769777
return m;
770778
}
771779

780+
static std::optional<Clipboard::Content> toClipboardContent(std::optional<ClipboardContentWire> wire) {
781+
if (!wire) return std::nullopt;
782+
if (wire->html) {
783+
return Clipboard::Html{QString::fromStdString(std::move(*wire->html)),
784+
wire->text ? std::optional(QString::fromStdString(std::move(*wire->text)))
785+
: std::nullopt};
786+
}
787+
if (!wire->urls.empty()) {
788+
std::vector<QUrl> urls;
789+
urls.reserve(wire->urls.size());
790+
for (auto &url : wire->urls)
791+
urls.emplace_back(QString::fromStdString(std::move(url)));
792+
return Clipboard::Urls{std::move(urls)};
793+
}
794+
if (wire->text) return Clipboard::Text{QString::fromStdString(std::move(*wire->text))};
795+
return std::nullopt;
796+
}
797+
772798
static ListItemViewModel toListItemViewModel(ListItemViewModelWire w) {
773799
ListItemViewModel m;
774800
m.changed = true;
@@ -780,6 +806,7 @@ static ListItemViewModel toListItemViewModel(ListItemViewModelWire w) {
780806
for (auto &a : w.accessories)
781807
m.accessories.emplace_back(toAccessoryModel(std::move(a)));
782808
m.keywords = std::move(w.keywords);
809+
m.dragContent = toClipboardContent(std::move(w.dragContent));
783810
for (auto &child : w.children) {
784811
match(
785812
child, [&](ActionPannelModelWire &c) { m.actionPannel = toActionPannelModel(std::move(c)); },
@@ -846,6 +873,7 @@ static GridItemViewModel toGridItemViewModel(GridItemViewModelWire w) {
846873
m.tooltip = std::move(w.tooltip);
847874
m.content = toGridContent(std::move(w.content), m.tooltip);
848875
m.keywords = std::move(w.keywords);
876+
m.dragContent = toClipboardContent(std::move(w.dragContent));
849877
for (auto &child : w.children) {
850878
if (auto *ap = std::get_if<ActionPannelModelWire>(&child))
851879
m.actionPannel = toActionPannelModel(std::move(*ap));

src/server/src/extension/services/clipboard-service.hpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,30 @@ class ExtClipboardService : public tsapi::AbstractClipboard {
3131

3232
result.text = rc.text.toStdString();
3333
if (rc.html) result.html = rc.html->toStdString();
34-
if (rc.file) result.path = rc.file->toStdString();
34+
if (!rc.urls.empty()) {
35+
result.urls.emplace();
36+
result.urls->reserve(rc.urls.size());
37+
for (const auto &url : rc.urls)
38+
result.urls->emplace_back(url.toString().toStdString());
39+
}
3540

3641
return tsapi::Result<tsapi::ClipboardContent>::ok(std::move(result));
3742
}
3843

3944
private:
4045
static Clipboard::Content parseContent(const tsapi::ClipboardContent &content) {
41-
if (content.html) return Clipboard::Html({QString::fromStdString(*content.html)});
42-
if (content.path) return Clipboard::File(std::string{*content.path});
46+
if (content.html) {
47+
return Clipboard::Html{.html = QString::fromStdString(*content.html),
48+
.text = content.text ? std::optional(QString::fromStdString(*content.text))
49+
: std::nullopt};
50+
}
51+
if (content.urls && !content.urls->empty()) {
52+
std::vector<QUrl> urls;
53+
urls.reserve(content.urls->size());
54+
for (const auto &url : *content.urls)
55+
urls.emplace_back(QString::fromStdString(url));
56+
return Clipboard::Urls{std::move(urls)};
57+
}
4358
if (content.text) return Clipboard::Text(QString::fromStdString(*content.text));
4459
return {};
4560
}

src/server/src/qml/drag-utils.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include <QDrag>
2+
#include <QGuiApplication>
3+
#include <QPixmap>
4+
#include <utility>
5+
6+
#include "drag-utils.hpp"
7+
#include "ui/image/image-renderer.hpp"
8+
#include "ui/image/url.hpp"
9+
10+
namespace DragUtils {
11+
12+
void startDrag(QObject *source, std::unique_ptr<QMimeData> mimeData, const QString &iconSource) {
13+
if (!source || !mimeData) return;
14+
15+
auto *drag = new QDrag(source);
16+
drag->setMimeData(mimeData.release());
17+
18+
if (auto frame = ImageRendering::cachedFrame(ImageURL(iconSource))) {
19+
constexpr int DRAG_ICON_SIZE = 40;
20+
auto const dpr = qGuiApp->devicePixelRatio();
21+
auto const physicalSize = qRound(DRAG_ICON_SIZE * dpr);
22+
auto image = frame->scaled(physicalSize, physicalSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
23+
auto pixmap = QPixmap::fromImage(std::move(image));
24+
pixmap.setDevicePixelRatio(dpr);
25+
drag->setPixmap(pixmap);
26+
drag->setHotSpot({DRAG_ICON_SIZE / 2, DRAG_ICON_SIZE / 2});
27+
}
28+
29+
drag->exec(Qt::CopyAction);
30+
drag->deleteLater();
31+
}
32+
33+
} // namespace DragUtils

src/server/src/qml/drag-utils.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
#include <QMimeData>
3+
4+
#include <memory>
5+
6+
class QObject;
7+
class QString;
8+
9+
namespace DragUtils {
10+
11+
void startDrag(QObject *source, std::unique_ptr<QMimeData> mimeData, const QString &iconSource);
12+
13+
}

src/server/src/qml/extension-grid-model.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <utility>
44
#include "common/types.hpp"
55
#include "fuzzy/fuzzy-searchable.hpp"
6+
#include "services/clipboard/clipboard-mime.hpp"
67
#include "theme.hpp"
78
#include "theme/theme-file.hpp"
89
#include "ui/image/url.hpp"
@@ -59,6 +60,24 @@ const GridItemViewModel *ExtensionGridSection::itemAt(int i) const {
5960
return &m_items[i];
6061
}
6162

63+
QString ExtensionGridSection::itemIconSource(int i) const {
64+
if (auto *item = itemAt(i)) {
65+
if (auto *image = std::get_if<ImageLikeModel>(&item->content))
66+
return qml::imageSourceFor(ImageURL(*image));
67+
}
68+
return {};
69+
}
70+
71+
bool ExtensionGridSection::isDraggable(int i) const {
72+
auto *item = itemAt(i);
73+
return item && item->dragContent.has_value();
74+
}
75+
76+
std::unique_ptr<QMimeData> ExtensionGridSection::dragMimeData(int i) const {
77+
auto *item = itemAt(i);
78+
return item && item->dragContent ? Clipboard::mimeDataForContent(*item->dragContent) : nullptr;
79+
}
80+
6281
std::unique_ptr<ActionPanelState> ExtensionGridSection::actionPanel(int i) const {
6382
if (auto *it = itemAt(i); it && it->actionPannel) {
6483
return ExtensionActionPanelBuilder::build(*it->actionPannel, m_notify,

src/server/src/qml/extension-grid-model.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class ExtensionGridSection : public GridSource {
3232
const GridItemViewModel *itemAt(int i) const;
3333

3434
std::unique_ptr<ActionPanelState> actionPanel(int i) const override;
35+
QString itemIconSource(int i) const override;
36+
bool isDraggable(int i) const override;
37+
std::unique_ptr<QMimeData> dragMimeData(int i) const override;
3538

3639
private:
3740
std::string m_name;

0 commit comments

Comments
 (0)