Allow reordering widgets in Ui::VerticalLayout.
This commit is contained in:
parent
ec6744022c
commit
b051948e69
5 changed files with 315 additions and 9 deletions
|
|
@ -151,6 +151,8 @@ PRIVATE
|
||||||
ui/wrap/slide_wrap.h
|
ui/wrap/slide_wrap.h
|
||||||
ui/wrap/vertical_layout.cpp
|
ui/wrap/vertical_layout.cpp
|
||||||
ui/wrap/vertical_layout.h
|
ui/wrap/vertical_layout.h
|
||||||
|
ui/wrap/vertical_layout_reorder.cpp
|
||||||
|
ui/wrap/vertical_layout_reorder.h
|
||||||
ui/wrap/wrap.h
|
ui/wrap/wrap.h
|
||||||
ui/abstract_button.cpp
|
ui/abstract_button.cpp
|
||||||
ui/abstract_button.h
|
ui/abstract_button.h
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,25 @@ int VerticalLayout::naturalWidth() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VerticalLayout::setVerticalShift(int index, int shift) {
|
||||||
|
Expects(index >= 0 && index < _rows.size());
|
||||||
|
|
||||||
|
auto &row = _rows[index];
|
||||||
|
if (const auto delta = shift - row.verticalShift) {
|
||||||
|
row.verticalShift = shift;
|
||||||
|
row.widget->move(row.widget->x(), row.widget->y() + delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayout::reorderRows(int oldIndex, int newIndex) {
|
||||||
|
Expects(oldIndex >= 0 && oldIndex < _rows.size());
|
||||||
|
Expects(newIndex >= 0 && newIndex < _rows.size());
|
||||||
|
Expects(!_inResize);
|
||||||
|
|
||||||
|
base::reorder(_rows, oldIndex, newIndex);
|
||||||
|
resizeToWidth(width());
|
||||||
|
}
|
||||||
|
|
||||||
int VerticalLayout::resizeGetHeight(int newWidth) {
|
int VerticalLayout::resizeGetHeight(int newWidth) {
|
||||||
_inResize = true;
|
_inResize = true;
|
||||||
auto guard = gsl::finally([&] { _inResize = false; });
|
auto guard = gsl::finally([&] { _inResize = false; });
|
||||||
|
|
@ -60,7 +79,7 @@ int VerticalLayout::resizeGetHeight(int newWidth) {
|
||||||
row.widget,
|
row.widget,
|
||||||
row.margin,
|
row.margin,
|
||||||
newWidth,
|
newWidth,
|
||||||
result);
|
result + row.verticalShift);
|
||||||
result += row.margin.top()
|
result += row.margin.top()
|
||||||
+ row.widget->heightNoMargins()
|
+ row.widget->heightNoMargins()
|
||||||
+ row.margin.bottom();
|
+ row.margin.bottom();
|
||||||
|
|
@ -100,18 +119,12 @@ RpWidget *VerticalLayout::insertChild(
|
||||||
object_ptr<RpWidget> child,
|
object_ptr<RpWidget> child,
|
||||||
const style::margins &margin) {
|
const style::margins &margin) {
|
||||||
Expects(atPosition >= 0 && atPosition <= _rows.size());
|
Expects(atPosition >= 0 && atPosition <= _rows.size());
|
||||||
|
Expects(!_inResize);
|
||||||
|
|
||||||
if (const auto weak = AttachParentChild(this, child)) {
|
if (const auto weak = AttachParentChild(this, child)) {
|
||||||
_rows.insert(
|
_rows.insert(
|
||||||
begin(_rows) + atPosition,
|
begin(_rows) + atPosition,
|
||||||
{ std::move(child), margin });
|
{ std::move(child), margin });
|
||||||
const auto margins = getMargins();
|
|
||||||
updateChildGeometry(
|
|
||||||
margins,
|
|
||||||
weak,
|
|
||||||
margin,
|
|
||||||
width() - margins.left() - margins.right(),
|
|
||||||
height() - margins.top() - margins.bottom());
|
|
||||||
weak->heightValue(
|
weak->heightValue(
|
||||||
) | rpl::start_with_next_done([=] {
|
) | rpl::start_with_next_done([=] {
|
||||||
if (!_inResize) {
|
if (!_inResize) {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,14 @@ class VerticalLayout : public RpWidget {
|
||||||
public:
|
public:
|
||||||
using RpWidget::RpWidget;
|
using RpWidget::RpWidget;
|
||||||
|
|
||||||
int count() const {
|
[[nodiscard]] int count() const {
|
||||||
return _rows.size();
|
return _rows.size();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] not_null<RpWidget*> widgetAt(int index) const {
|
||||||
|
Expects(index >= 0 && index < count());
|
||||||
|
|
||||||
|
return _rows[index].widget.data();
|
||||||
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
typename Widget,
|
typename Widget,
|
||||||
|
|
@ -46,6 +51,9 @@ public:
|
||||||
QMargins getMargins() const override;
|
QMargins getMargins() const override;
|
||||||
int naturalWidth() const override;
|
int naturalWidth() const override;
|
||||||
|
|
||||||
|
void setVerticalShift(int index, int shift);
|
||||||
|
void reorderRows(int oldIndex, int newIndex);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
void visibleTopBottomUpdated(
|
void visibleTopBottomUpdated(
|
||||||
|
|
@ -69,6 +77,7 @@ private:
|
||||||
struct Row {
|
struct Row {
|
||||||
object_ptr<RpWidget> widget;
|
object_ptr<RpWidget> widget;
|
||||||
style::margins margin;
|
style::margins margin;
|
||||||
|
int verticalShift = 0;
|
||||||
};
|
};
|
||||||
std::vector<Row> _rows;
|
std::vector<Row> _rows;
|
||||||
bool _inResize = false;
|
bool _inResize = false;
|
||||||
|
|
|
||||||
214
ui/wrap/vertical_layout_reorder.cpp
Normal file
214
ui/wrap/vertical_layout_reorder.cpp
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#include "ui/wrap/vertical_layout_reorder.h"
|
||||||
|
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
|
||||||
|
#include <QtGui/QtEvents>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
VerticalLayoutReorder::VerticalLayoutReorder(
|
||||||
|
not_null<VerticalLayout*> layout)
|
||||||
|
: _layout(layout) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::cancel() {
|
||||||
|
if (_currentWidget) {
|
||||||
|
cancelCurrent(indexOf(_currentWidget));
|
||||||
|
}
|
||||||
|
_lifetime.destroy();
|
||||||
|
for (auto i = 0, count = _layout->count(); i != count; ++i) {
|
||||||
|
_layout->setVerticalShift(i, 0);
|
||||||
|
}
|
||||||
|
_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::start() {
|
||||||
|
const auto count = _layout->count();
|
||||||
|
if (count < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
const auto widget = _layout->widgetAt(i);
|
||||||
|
widget->events(
|
||||||
|
) | rpl::start_with_next_done([=](not_null<QEvent*> e) {
|
||||||
|
switch (e->type()) {
|
||||||
|
case QEvent::MouseMove:
|
||||||
|
mouseMove(
|
||||||
|
widget,
|
||||||
|
static_cast<QMouseEvent*>(e.get())->globalPos());
|
||||||
|
break;
|
||||||
|
case QEvent::MouseButtonPress:
|
||||||
|
mousePress(
|
||||||
|
widget,
|
||||||
|
static_cast<QMouseEvent*>(e.get())->button(),
|
||||||
|
static_cast<QMouseEvent*>(e.get())->globalPos());
|
||||||
|
break;
|
||||||
|
case QEvent::MouseButtonRelease:
|
||||||
|
mouseRelease(
|
||||||
|
widget,
|
||||||
|
static_cast<QMouseEvent*>(e.get())->button());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [=] {
|
||||||
|
cancel();
|
||||||
|
}, _lifetime);
|
||||||
|
_entries.push_back({ widget });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::mouseMove(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
QPoint position) {
|
||||||
|
if (_currentWidget != widget) {
|
||||||
|
return;
|
||||||
|
} else if (_currentState != State::Started) {
|
||||||
|
checkForStart(position);
|
||||||
|
} else {
|
||||||
|
updateOrder(indexOf(_currentWidget), position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::checkForStart(QPoint position) {
|
||||||
|
const auto shift = position.y() - _currentStart;
|
||||||
|
const auto delta = QApplication::startDragDistance();
|
||||||
|
if (std::abs(shift) <= delta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_currentWidget->raise();
|
||||||
|
_currentState = State::Started;
|
||||||
|
_currentStart += (shift > 0) ? delta : -delta;
|
||||||
|
|
||||||
|
const auto index = indexOf(_currentWidget);
|
||||||
|
_currentDesiredIndex = index;
|
||||||
|
_updates.fire({ _currentWidget, index, index, _currentState });
|
||||||
|
|
||||||
|
updateOrder(index, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::updateOrder(int index, QPoint position) {
|
||||||
|
const auto shift = position.y() - _currentStart;
|
||||||
|
auto ¤t = _entries[index];
|
||||||
|
current.shift = shift;
|
||||||
|
_layout->setVerticalShift(index, shift);
|
||||||
|
|
||||||
|
const auto count = _entries.size();
|
||||||
|
const auto currentHeight = current.widget->height();
|
||||||
|
const auto currentMiddle = current.widget->y() + currentHeight / 2;
|
||||||
|
_currentDesiredIndex = index;
|
||||||
|
if (shift > 0) {
|
||||||
|
auto top = current.widget->y() - shift;
|
||||||
|
for (auto next = index + 1; next != count; ++next) {
|
||||||
|
const auto &entry = _entries[next];
|
||||||
|
top += entry.widget->height();
|
||||||
|
if (currentMiddle < top) {
|
||||||
|
moveToShift(next, 0);
|
||||||
|
} else {
|
||||||
|
_currentDesiredIndex = next;
|
||||||
|
moveToShift(next, -currentHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto prev = index - 1; prev >= 0; --prev) {
|
||||||
|
moveToShift(prev, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto next = index + 1; next != count; ++next) {
|
||||||
|
moveToShift(next, 0);
|
||||||
|
}
|
||||||
|
for (auto prev = index - 1; prev >= 0; --prev) {
|
||||||
|
const auto &entry = _entries[prev];
|
||||||
|
if (currentMiddle >= entry.widget->y() - entry.shift + currentHeight) {
|
||||||
|
moveToShift(prev, 0);
|
||||||
|
} else {
|
||||||
|
_currentDesiredIndex = prev;
|
||||||
|
moveToShift(prev, currentHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::mousePress(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
Qt::MouseButton button,
|
||||||
|
QPoint position) {
|
||||||
|
if (button != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelCurrent();
|
||||||
|
_currentWidget = widget;
|
||||||
|
_currentStart = position.y();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::mouseRelease(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
Qt::MouseButton button) {
|
||||||
|
if (button != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finishCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::cancelCurrent() {
|
||||||
|
if (_currentWidget) {
|
||||||
|
cancelCurrent(indexOf(_currentWidget));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::cancelCurrent(int index) {
|
||||||
|
Expects(_currentWidget != nullptr);
|
||||||
|
|
||||||
|
if (_currentState == State::Started) {
|
||||||
|
_currentState = State::Cancelled;
|
||||||
|
_updates.fire({ _currentWidget, index, index, _currentState });
|
||||||
|
}
|
||||||
|
_currentWidget = nullptr;
|
||||||
|
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||||
|
moveToShift(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::finishCurrent() {
|
||||||
|
if (!_currentWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto index = indexOf(_currentWidget);
|
||||||
|
if (_currentDesiredIndex == index || _currentState != State::Started) {
|
||||||
|
cancelCurrent(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto result = _currentDesiredIndex;
|
||||||
|
const auto widget = _currentWidget;
|
||||||
|
_currentState = State::Cancelled;
|
||||||
|
_currentWidget = nullptr;
|
||||||
|
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||||
|
moveToShift(i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_layout->reorderRows(index, _currentDesiredIndex);
|
||||||
|
base::reorder(_entries, index, result);
|
||||||
|
|
||||||
|
_updates.fire({ widget, index, result, State::Applied });
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::moveToShift(int index, int shift) {
|
||||||
|
_layout->setVerticalShift(index, shift);
|
||||||
|
_entries[index].shift = shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VerticalLayoutReorder::indexOf(not_null<RpWidget*> widget) const {
|
||||||
|
const auto i = ranges::find(_entries, widget, &Entry::widget);
|
||||||
|
Assert(i != end(_entries));
|
||||||
|
return i - begin(_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto VerticalLayoutReorder::updates() const -> rpl::producer<Single> {
|
||||||
|
return _updates.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
68
ui/wrap/vertical_layout_reorder.h
Normal file
68
ui/wrap/vertical_layout_reorder.h
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class RpWidget;
|
||||||
|
class VerticalLayout;
|
||||||
|
|
||||||
|
class VerticalLayoutReorder final {
|
||||||
|
public:
|
||||||
|
enum class State : uchar {
|
||||||
|
Started,
|
||||||
|
Applied,
|
||||||
|
Cancelled,
|
||||||
|
};
|
||||||
|
struct Single {
|
||||||
|
not_null<RpWidget*> widget;
|
||||||
|
int oldPosition = 0;
|
||||||
|
int newPosition = 0;
|
||||||
|
State state = State::Started;
|
||||||
|
};
|
||||||
|
|
||||||
|
VerticalLayoutReorder(not_null<VerticalLayout*> layout);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void cancel();
|
||||||
|
[[nodiscard]] rpl::producer<Single> updates() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Entry {
|
||||||
|
not_null<RpWidget*> widget;
|
||||||
|
int shift = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void mouseMove(not_null<RpWidget*> widget, QPoint position);
|
||||||
|
void mousePress(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
Qt::MouseButton button,
|
||||||
|
QPoint position);
|
||||||
|
void mouseRelease(not_null<RpWidget*> widget, Qt::MouseButton button);
|
||||||
|
|
||||||
|
void checkForStart(QPoint position);
|
||||||
|
void updateOrder(int index, QPoint position);
|
||||||
|
void cancelCurrent();
|
||||||
|
void finishCurrent();
|
||||||
|
void cancelCurrent(int index);
|
||||||
|
|
||||||
|
[[nodiscard]] int indexOf(not_null<RpWidget*> widget) const;
|
||||||
|
void moveToShift(int index, int shift);
|
||||||
|
|
||||||
|
const not_null<Ui::VerticalLayout*> _layout;
|
||||||
|
|
||||||
|
RpWidget *_currentWidget = nullptr;
|
||||||
|
int _currentStart = 0;
|
||||||
|
int _currentDesiredIndex = 0;
|
||||||
|
State _currentState = State::Cancelled;
|
||||||
|
std::vector<Entry> _entries;
|
||||||
|
rpl::event_stream<Single> _updates;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
Loading…
Add table
Reference in a new issue