first commit
This commit is contained in:
156
modern-qt/layout/flow.cc
Normal file
156
modern-qt/layout/flow.cc
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "flow.hh"
|
||||
#include <qstyle.h>
|
||||
|
||||
using namespace creeper::flow::internal;
|
||||
|
||||
struct Flow::Impl {
|
||||
QList<Item*> items;
|
||||
|
||||
int row_spacing = 10;
|
||||
int col_spacing = 10;
|
||||
|
||||
int row_limit = std::numeric_limits<int>::max();
|
||||
|
||||
Flow& self;
|
||||
|
||||
explicit Impl(Flow& self) noexcept
|
||||
: self { self } { }
|
||||
|
||||
auto calculate_spacing(QStyle::PixelMetric pm) const -> int {
|
||||
const auto parent = self.parent();
|
||||
if (!parent) {
|
||||
return -1;
|
||||
} else if (parent->isWidgetType()) {
|
||||
const auto pw = static_cast<QWidget*>(parent);
|
||||
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||
} else {
|
||||
return static_cast<QLayout*>(parent)->spacing();
|
||||
}
|
||||
}
|
||||
auto horizontal_spacing() const -> int {
|
||||
if (row_spacing >= 0) {
|
||||
return row_spacing;
|
||||
} else {
|
||||
return calculate_spacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||
}
|
||||
}
|
||||
auto vertical_spacing() const -> int {
|
||||
if (col_spacing >= 0) {
|
||||
return col_spacing;
|
||||
} else {
|
||||
return calculate_spacing(QStyle::PM_LayoutVerticalSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
auto update_items_geometry(Item* item, const QPoint& point) const {
|
||||
// TODO: 显然,这个接口未来可以拓展成带动画的位姿更新
|
||||
item->setGeometry({ point, item->sizeHint() });
|
||||
}
|
||||
|
||||
// Flow 理应是一个常用的布局,但 Qt 中没有对应的实现,
|
||||
// https://doc.qt.io/archives/qt-5.15/qtwidgets-layouts-flowlayout-example.html
|
||||
auto calculate_layout(const QRect& rect, bool apply) const -> int {
|
||||
|
||||
int left, top, right, bottom;
|
||||
self.getContentsMargins(&left, &top, &right, &bottom);
|
||||
|
||||
const auto effective_rect = rect.adjusted(+left, +top, -right, -bottom);
|
||||
|
||||
auto current_x = effective_rect.x();
|
||||
auto current_y = effective_rect.y();
|
||||
|
||||
auto line_height = int { 0 };
|
||||
auto line_length = int { 0 };
|
||||
for (auto item : std::as_const(items)) {
|
||||
const auto widget = item->widget();
|
||||
const auto spacing = [widget](Qt::Orientation o) {
|
||||
return widget->style()->layoutSpacing(
|
||||
QSizePolicy::PushButton, QSizePolicy::PushButton, o);
|
||||
};
|
||||
|
||||
auto space_x = row_spacing;
|
||||
if (space_x == -1) space_x = spacing(Qt::Horizontal);
|
||||
|
||||
auto space_y = col_spacing;
|
||||
if (space_y == -1) space_y = spacing(Qt::Vertical);
|
||||
|
||||
auto next_x = current_x + item->sizeHint().width() + space_x;
|
||||
|
||||
const auto area_flag = next_x - space_x > effective_rect.right();
|
||||
const auto size_flag = line_length > row_limit;
|
||||
if ((area_flag || size_flag) && line_height > 0) {
|
||||
current_x = effective_rect.x();
|
||||
current_y = current_y + line_height + space_y;
|
||||
next_x = current_x + item->sizeHint().width() + space_x;
|
||||
line_height = 0;
|
||||
line_length = 0;
|
||||
}
|
||||
|
||||
if (apply) {
|
||||
const auto point = QPoint { current_x, current_y };
|
||||
update_items_geometry(item, point);
|
||||
}
|
||||
|
||||
current_x = next_x;
|
||||
line_height = std::max(line_height, item->sizeHint().height());
|
||||
line_length = line_length + 1;
|
||||
}
|
||||
return current_y + line_height - rect.y() + bottom;
|
||||
}
|
||||
};
|
||||
|
||||
Flow::Flow()
|
||||
: pimpl { std::make_unique<Impl>(*this) } { }
|
||||
|
||||
Flow::~Flow() {
|
||||
while (auto item = Flow::takeAt(0))
|
||||
delete item;
|
||||
}
|
||||
|
||||
auto Flow::addItem(Item* item) -> void { pimpl->items.append(item); }
|
||||
|
||||
auto Flow::setGeometry(const QRect& rect) -> void {
|
||||
QLayout::setGeometry(rect);
|
||||
pimpl->calculate_layout(rect, true);
|
||||
}
|
||||
|
||||
auto Flow::takeAt(int index) -> Item* {
|
||||
auto& items = pimpl->items;
|
||||
return (index < 0 || index > items.size() - 1) ? nullptr : items.takeAt(index);
|
||||
}
|
||||
|
||||
auto Flow::expandingDirections() const -> Qt::Orientations { return {}; }
|
||||
|
||||
auto Flow::hasHeightForWidth() const -> bool { return true; }
|
||||
|
||||
auto Flow::heightForWidth(int width) const -> int {
|
||||
return pimpl->calculate_layout({ 0, 0, width, 0 }, false);
|
||||
}
|
||||
|
||||
auto Flow::itemAt(int index) const -> Item* { return pimpl->items.value(index); }
|
||||
|
||||
auto Flow::count() const -> int { return pimpl->items.size(); }
|
||||
|
||||
auto Flow::minimumSize() const -> QSize {
|
||||
auto result = QSize {};
|
||||
for (const auto item : std::as_const(pimpl->items))
|
||||
result = result.expandedTo(item->minimumSize());
|
||||
|
||||
const auto margins = contentsMargins();
|
||||
result += QSize {
|
||||
margins.left() + margins.right(),
|
||||
margins.top() + margins.bottom(),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
auto Flow::sizeHint() const -> QSize { return Flow::minimumSize(); }
|
||||
|
||||
auto Flow::set_row_spacing(int spacing) noexcept -> void { pimpl->row_spacing = spacing; }
|
||||
auto Flow::row_spacing() const noexcept -> int { return pimpl->row_spacing; }
|
||||
|
||||
auto Flow::set_col_spacing(int spacing) noexcept -> void { pimpl->col_spacing = spacing; }
|
||||
auto Flow::col_spacing() const noexcept -> int { return pimpl->col_spacing; }
|
||||
|
||||
auto Flow::set_row_limit(int limit) noexcept -> void { pimpl->row_limit = limit; }
|
||||
auto Flow::row_limit() const noexcept -> int { return pimpl->row_limit; }
|
||||
64
modern-qt/layout/flow.hh
Normal file
64
modern-qt/layout/flow.hh
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/wrapper/common.hh"
|
||||
#include "modern-qt/utility/wrapper/layout.hh"
|
||||
#include "modern-qt/utility/wrapper/pimpl.hh"
|
||||
#include <qlayout.h>
|
||||
|
||||
namespace creeper::flow::internal {
|
||||
|
||||
class Flow : public QLayout {
|
||||
CREEPER_PIMPL_DEFINITION(Flow)
|
||||
|
||||
public:
|
||||
using Item = QLayoutItem;
|
||||
|
||||
auto addItem(Item*) -> void override;
|
||||
auto takeAt(int) -> Item* override;
|
||||
auto setGeometry(const QRect&) -> void override;
|
||||
|
||||
auto expandingDirections() const -> Qt::Orientations override;
|
||||
auto hasHeightForWidth() const -> bool override;
|
||||
auto heightForWidth(int) const -> int override;
|
||||
|
||||
auto itemAt(int) const -> Item* override;
|
||||
auto count() const -> int override;
|
||||
auto minimumSize() const -> QSize override;
|
||||
auto sizeHint() const -> QSize override;
|
||||
|
||||
public:
|
||||
auto set_row_spacing(int) noexcept -> void;
|
||||
auto row_spacing() const noexcept -> int;
|
||||
|
||||
auto set_col_spacing(int) noexcept -> void;
|
||||
auto col_spacing() const noexcept -> int;
|
||||
|
||||
auto set_row_limit(int) noexcept -> void;
|
||||
auto row_limit() const noexcept -> int;
|
||||
};
|
||||
|
||||
}
|
||||
namespace creeper::flow::pro {
|
||||
|
||||
using Token = common::Token<internal::Flow>;
|
||||
|
||||
using RowSpacing = SetterProp<Token, int, [](auto& self, int v) { self.set_row_spacing(v); }>;
|
||||
|
||||
using ColSpacing = SetterProp<Token, int, [](auto& self, int v) { self.set_col_spacing(v); }>;
|
||||
|
||||
using RowLimit = SetterProp<Token, int, [](auto& self, int v) { self.set_row_limit(v); }>;
|
||||
|
||||
using MainAxisSpacing = RowSpacing;
|
||||
using CrossAxisSpacing = ColSpacing;
|
||||
using MaxItemsInEachRow = RowLimit;
|
||||
|
||||
template <class T>
|
||||
concept trait = std::derived_from<T, Token>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait);
|
||||
using namespace layout::pro;
|
||||
}
|
||||
namespace creeper {
|
||||
|
||||
using Flow = Declarative<flow::internal::Flow, CheckerOr<flow::pro::checker, layout::pro::checker>>;
|
||||
|
||||
}
|
||||
0
modern-qt/layout/form.hh
Normal file
0
modern-qt/layout/form.hh
Normal file
83
modern-qt/layout/grid.hh
Normal file
83
modern-qt/layout/grid.hh
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "modern-qt/utility/wrapper/common.hh"
|
||||
#include "modern-qt/utility/wrapper/property.hh"
|
||||
|
||||
#include <qgridlayout.h>
|
||||
|
||||
namespace creeper {
|
||||
namespace grid::internal {
|
||||
class Grid : public QGridLayout { };
|
||||
}
|
||||
namespace grid::pro {
|
||||
using Token = common::Token<QGridLayout>;
|
||||
|
||||
struct RowSpacing : Token { };
|
||||
|
||||
struct ColSpacing : Token { };
|
||||
|
||||
template <class T>
|
||||
requires std::is_convertible_v<T*, QWidget*> || std::is_convertible_v<T*, QLayout*>
|
||||
struct Item : Token {
|
||||
using Align = Qt::Alignment;
|
||||
|
||||
struct LayoutMethod {
|
||||
int row = 0, row_span = 0;
|
||||
int col = 0, col_span = 0;
|
||||
Align align;
|
||||
|
||||
explicit LayoutMethod(int row, int col, Align align = {})
|
||||
: row { row }
|
||||
, col { col }
|
||||
, align { align } { }
|
||||
|
||||
explicit LayoutMethod(int row, int row_span, int col, int col_span, Align align = {})
|
||||
: row { row }
|
||||
, col { col }
|
||||
, row_span { row_span }
|
||||
, col_span { col_span }
|
||||
, align { align } { }
|
||||
|
||||
} method;
|
||||
|
||||
T* item_pointer = nullptr;
|
||||
|
||||
explicit Item(const LayoutMethod& method, auto&&... args) noexcept
|
||||
requires std::constructible_from<T, decltype(args)...>
|
||||
: item_pointer { new T { std::forward<decltype(args)>(args)... } }
|
||||
, method(method) { }
|
||||
|
||||
explicit Item(const LayoutMethod& method, T* pointer) noexcept
|
||||
: item_pointer { pointer }
|
||||
, method { method } { }
|
||||
|
||||
void apply(QGridLayout& layout) const {
|
||||
if (method.col_span == 0) {
|
||||
if constexpr (std::is_convertible_v<T*, QWidget*>)
|
||||
layout.addWidget(item_pointer, method.row, method.col, method.align);
|
||||
if constexpr (std::is_convertible_v<T*, QLayout*>)
|
||||
layout.addLayout(item_pointer, method.row, method.col, method.align);
|
||||
} else {
|
||||
if constexpr (std::is_convertible_v<T*, QWidget*>)
|
||||
layout.addWidget(item_pointer, method.row, method.row_span, method.col,
|
||||
method.col_span, method.align);
|
||||
if constexpr (std::is_convertible_v<T*, QLayout*>)
|
||||
layout.addLayout(item_pointer, method.row, method.row_span, method.col,
|
||||
method.col_span, method.align);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Items : Token {
|
||||
explicit Items() { }
|
||||
void apply(QGridLayout& self) const { }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept trait = std::derived_from<T, Token>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait);
|
||||
|
||||
}
|
||||
using Grid = Declarative<grid::internal::Grid, grid::pro::checker>;
|
||||
}
|
||||
124
modern-qt/layout/group.hh
Normal file
124
modern-qt/layout/group.hh
Normal file
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/trait/widget.hh"
|
||||
#include "modern-qt/utility/wrapper/common.hh"
|
||||
#include "modern-qt/utility/wrapper/property.hh"
|
||||
|
||||
namespace creeper::group::internal {
|
||||
|
||||
template <typename F, typename T>
|
||||
concept foreach_invoke_item_trait = requires {
|
||||
{ std::invoke(std::declval<F>(), std::declval<T const&>()) } -> widget_pointer_trait;
|
||||
};
|
||||
|
||||
template <typename F, typename T>
|
||||
concept foreach_apply_item_trait = requires {
|
||||
{ std::apply(std::declval<F>(), std::declval<T const&>()) } -> widget_pointer_trait;
|
||||
};
|
||||
|
||||
template <typename F, typename T>
|
||||
concept foreach_item_trait = foreach_invoke_item_trait<F, T> || foreach_apply_item_trait<F, T>;
|
||||
|
||||
template <typename R, typename F>
|
||||
concept foreach_invoke_ranges_trait = foreach_item_trait<F, std::ranges::range_value_t<R>>;
|
||||
|
||||
template <layout_trait T, widget_trait W>
|
||||
struct Group : public T {
|
||||
using T::T;
|
||||
std::vector<W*> widgets;
|
||||
|
||||
template <std::ranges::range R, typename F>
|
||||
requires foreach_invoke_ranges_trait<R, F>
|
||||
constexpr auto compose(const R& ranges, F&& f, Qt::Alignment a = {}) noexcept -> void {
|
||||
for (const auto& item : ranges) {
|
||||
using ItemT = decltype(item);
|
||||
|
||||
auto widget_pointer = (W*) {};
|
||||
|
||||
if constexpr (foreach_invoke_item_trait<F, ItemT>)
|
||||
widget_pointer = std::invoke(f, item);
|
||||
|
||||
else if constexpr (foreach_apply_item_trait<F, ItemT>)
|
||||
widget_pointer = std::apply(f, item);
|
||||
|
||||
if (widget_pointer != nullptr) {
|
||||
T::addWidget(widget_pointer, 0, a);
|
||||
widgets.push_back(widget_pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto foreach_(this auto&& self, auto&& f) noexcept
|
||||
requires std::invocable<decltype(f), W&>
|
||||
{
|
||||
for (auto widget : self.widgets)
|
||||
std::invoke(f, *widget);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
namespace creeper::group::pro {
|
||||
|
||||
using Token = common::Token<internal::Group<QLayout, QWidget>>;
|
||||
|
||||
/// @note
|
||||
/// 一种典型的用法,委托构造时,所传函数只能接受常量引用,
|
||||
/// 放心使用 auto,类型是可以被推导出来的
|
||||
///
|
||||
/// group::pro::Compose {
|
||||
/// std::array {
|
||||
/// std::tuple(1, "xxxxxx"),
|
||||
/// ......
|
||||
/// },
|
||||
/// [](auto index, auto text) {
|
||||
/// return new TextButton { ... };
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
template <typename R, typename F>
|
||||
requires internal::foreach_invoke_ranges_trait<R, F>
|
||||
struct Compose : Token {
|
||||
const R& ranges;
|
||||
F method;
|
||||
Qt::Alignment alignment;
|
||||
|
||||
explicit Compose(const R& r, F f, Qt::Alignment a = {}) noexcept
|
||||
: ranges { r }
|
||||
, method { std::move(f) }
|
||||
, alignment { a } { }
|
||||
|
||||
auto apply(auto& self) noexcept -> void { //
|
||||
self.compose(ranges, std::move(method), alignment);
|
||||
}
|
||||
};
|
||||
|
||||
/// @note
|
||||
/// 函数参数是组件的引用:
|
||||
///
|
||||
/// group::pro::Foreach { [](Widget& button) { ... } },
|
||||
///
|
||||
template <typename F>
|
||||
requires(!std::invocable<F>)
|
||||
struct Foreach : Token {
|
||||
F function;
|
||||
|
||||
explicit Foreach(F&& f) noexcept
|
||||
: function { std::forward<F>(f) } { }
|
||||
|
||||
auto apply(auto& self) const noexcept {
|
||||
// 很遗憾,Qt 占用了 foreach 这个单词
|
||||
self.foreach_(std::move(function));
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
concept trait = std::derived_from<T, Token>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait)
|
||||
};
|
||||
namespace creeper {
|
||||
|
||||
template <layout_trait T, widget_trait W>
|
||||
using Group =
|
||||
Declarative<group::internal::Group<T, W>, CheckerOr<group::pro::checker, typename T::Checker>>;
|
||||
|
||||
}
|
||||
108
modern-qt/layout/linear.hh
Normal file
108
modern-qt/layout/linear.hh
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "modern-qt/utility/trait/widget.hh"
|
||||
#include "modern-qt/utility/wrapper/common.hh"
|
||||
#include "modern-qt/utility/wrapper/layout.hh"
|
||||
#include "modern-qt/utility/wrapper/property.hh"
|
||||
|
||||
#include <qboxlayout.h>
|
||||
|
||||
namespace creeper::linear::pro {
|
||||
|
||||
using Token = common::Token<QBoxLayout>;
|
||||
|
||||
struct SpacingItem : Token {
|
||||
int size;
|
||||
explicit SpacingItem(int p) { size = p; }
|
||||
void apply(QBoxLayout& self) const { self.addSpacing(size); }
|
||||
};
|
||||
|
||||
struct Stretch : Token {
|
||||
int stretch;
|
||||
explicit Stretch(int p) { stretch = p; }
|
||||
void apply(QBoxLayout& self) const { self.addStretch(stretch); }
|
||||
};
|
||||
|
||||
struct SpacerItem : Token {
|
||||
QSpacerItem* spacer_item;
|
||||
explicit SpacerItem(QSpacerItem* p) { spacer_item = p; }
|
||||
void apply(QBoxLayout& self) const { self.addSpacerItem(spacer_item); }
|
||||
};
|
||||
|
||||
/// @brief
|
||||
/// 布局项包装器,用于声明式地将 Widget 或 Layout 添加到布局中
|
||||
///
|
||||
/// @tparam T
|
||||
/// 被包装的组件类型,需满足可转换为 QWidget* 或 QLayout*,不需
|
||||
/// 要显式指定,由构造参数推倒
|
||||
///
|
||||
/// @note
|
||||
/// Item 提供统一的接口用于在布局中插入控件或子布局,
|
||||
/// 支持多种构造方式,包括直接传入指针或通过参数构造新对象。
|
||||
/// 通过 LayoutMethod 可指定拉伸因子和对齐方式,
|
||||
/// 在布局应用时自动选择 addWidget 或 addLayout,
|
||||
/// 实现非侵入式的布局声明式封装。
|
||||
///
|
||||
/// 示例用途:
|
||||
/// linear::pro::Item<Widget> {
|
||||
/// { 0, Qt::AlignHCenter } // stretch, and alignment, optional
|
||||
/// ...
|
||||
/// };
|
||||
///
|
||||
template <item_trait T>
|
||||
struct Item : Token {
|
||||
struct LayoutMethod {
|
||||
int stretch = 0;
|
||||
Qt::Alignment align = {};
|
||||
} method;
|
||||
|
||||
T* item_pointer = nullptr;
|
||||
|
||||
explicit Item(const LayoutMethod& method, T* pointer) noexcept
|
||||
: item_pointer { pointer }
|
||||
, method { method } { }
|
||||
|
||||
explicit Item(T* pointer) noexcept
|
||||
: item_pointer { pointer } { }
|
||||
|
||||
explicit Item(const LayoutMethod& method, auto&&... args) noexcept
|
||||
requires std::constructible_from<T, decltype(args)...>
|
||||
: item_pointer { new T { std::forward<decltype(args)>(args)... } }
|
||||
, method(method) { }
|
||||
|
||||
explicit Item(auto&&... args) noexcept
|
||||
requires std::constructible_from<T, decltype(args)...>
|
||||
: item_pointer { new T { std::forward<decltype(args)>(args)... } } { }
|
||||
|
||||
void apply(linear_trait auto& layout) const {
|
||||
if constexpr (widget_trait<T>) layout.addWidget(item_pointer, method.stretch, method.align);
|
||||
if constexpr (layout_trait<T>) layout.addLayout(item_pointer, method.stretch);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept trait = std::derived_from<T, Token>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait);
|
||||
using namespace layout::pro;
|
||||
}
|
||||
|
||||
namespace creeper {
|
||||
|
||||
template <class T>
|
||||
using BoxLayout = Declarative<T, CheckerOr<linear::pro::checker, layout::pro::checker>>;
|
||||
|
||||
using Row = BoxLayout<QHBoxLayout>;
|
||||
using Col = BoxLayout<QVBoxLayout>;
|
||||
|
||||
namespace row = linear;
|
||||
namespace col = linear;
|
||||
|
||||
namespace internal {
|
||||
inline auto use_the_namespace_alias_to_eliminate_warnings() {
|
||||
std::ignore = row::pro::Token {};
|
||||
std::ignore = col::pro::Token {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
3
modern-qt/layout/mixer.cc
Normal file
3
modern-qt/layout/mixer.cc
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "mixer.hh"
|
||||
|
||||
using namespace creeper::mixer::internal;
|
||||
112
modern-qt/layout/mixer.hh
Normal file
112
modern-qt/layout/mixer.hh
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <qpainter.h>
|
||||
#include <qpainterpath.h>
|
||||
|
||||
#include "modern-qt/utility/animation/animatable.hh"
|
||||
#include "modern-qt/utility/animation/state/pid.hh"
|
||||
#include "modern-qt/utility/animation/transition.hh"
|
||||
#include "modern-qt/utility/wrapper/widget.hh"
|
||||
|
||||
namespace creeper::mixer::internal {
|
||||
|
||||
class MixerMask : public QWidget {
|
||||
public:
|
||||
explicit MixerMask(auto* widget) noexcept
|
||||
: QWidget { widget }
|
||||
, animatable { *this } {
|
||||
|
||||
QWidget::setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
mask_frame.fill(Qt::transparent);
|
||||
{
|
||||
auto state = std::make_shared<PidState<double>>();
|
||||
|
||||
state->config.kp = 05.0;
|
||||
state->config.ki = 00.0;
|
||||
state->config.kd = 00.0;
|
||||
state->config.epsilon = 1e-3;
|
||||
|
||||
mask_radius = make_transition(animatable, std::move(state));
|
||||
}
|
||||
}
|
||||
|
||||
auto initiate_animation(QPoint const& point) noexcept {
|
||||
mask_frame.fill(Qt::transparent);
|
||||
|
||||
auto* widget = parentWidget();
|
||||
if (widget == nullptr) return;
|
||||
|
||||
mask_radius->snap_to(0.);
|
||||
mask_radius->transition_to(1.);
|
||||
|
||||
mask_point = point;
|
||||
mask_frame = widget->grab();
|
||||
|
||||
update_animation = true;
|
||||
QWidget::setFixedSize(widget->size());
|
||||
}
|
||||
auto initiate_animation(int x, int y) noexcept {
|
||||
// Forward Point
|
||||
initiate_animation(QPoint { x, y });
|
||||
}
|
||||
|
||||
protected:
|
||||
auto paintEvent(QPaintEvent* e) -> void override {
|
||||
if (!update_animation) return;
|
||||
|
||||
auto const w = QWidget::width();
|
||||
auto const h = QWidget::height();
|
||||
auto const x = std::sqrt(w * w + h * h);
|
||||
|
||||
auto painter = QPainter { this };
|
||||
|
||||
auto const radius = double { *mask_radius * x };
|
||||
auto const round = [&] {
|
||||
auto path = QPainterPath {};
|
||||
path.addRect(QWidget::rect());
|
||||
|
||||
auto inner = QPainterPath();
|
||||
inner.addEllipse(mask_point, radius, radius);
|
||||
|
||||
return path.subtracted(inner);
|
||||
}();
|
||||
painter.setClipPath(round);
|
||||
painter.setClipping(true);
|
||||
|
||||
painter.drawPixmap(QWidget::rect(), mask_frame);
|
||||
|
||||
if (std::abs(*mask_radius - 1.) < 1e-2) {
|
||||
update_animation = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QPixmap mask_frame;
|
||||
QPointF mask_point;
|
||||
|
||||
bool update_animation = false;
|
||||
|
||||
Animatable animatable;
|
||||
std::unique_ptr<TransitionValue<PidState<double>>> mask_radius;
|
||||
};
|
||||
|
||||
}
|
||||
namespace creeper::mixer::pro {
|
||||
|
||||
struct SetMixerMask : widget::pro::Token {
|
||||
internal::MixerMask*& mask;
|
||||
explicit SetMixerMask(auto*& mask)
|
||||
: mask { mask } { }
|
||||
auto apply(auto& self) noexcept {
|
||||
//
|
||||
mask = new internal::MixerMask { &self };
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
namespace creeper {
|
||||
|
||||
using MixerMask = mixer::internal::MixerMask;
|
||||
|
||||
}
|
||||
98
modern-qt/layout/mutual-exclusion-group.hh
Normal file
98
modern-qt/layout/mutual-exclusion-group.hh
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include <modern-qt/layout/group.hh>
|
||||
#include <modern-qt/utility/wrapper/layout.hh>
|
||||
#include <ranges>
|
||||
|
||||
namespace creeper::mutual_exclusion_group::internal {
|
||||
|
||||
template <auto f, typename W>
|
||||
concept switch_function_trait = std::invocable<decltype(f), W&, bool>;
|
||||
|
||||
template <layout_trait T, widget_trait W, auto switch_function>
|
||||
requires switch_function_trait<switch_function, W>
|
||||
struct MutualExclusionGroup : public Group<T, W> {
|
||||
using Group<T, W>::Group;
|
||||
|
||||
public:
|
||||
auto switch_widgets(std::size_t index) const noexcept {
|
||||
for (auto [index_, w] : std::views::enumerate(this->widgets)) {
|
||||
switch_function(*w, index_ == index);
|
||||
}
|
||||
}
|
||||
auto switch_widgets(W* widget) const noexcept {
|
||||
for (auto w_ : this->widgets) {
|
||||
switch_function(*w_, w_ == widget);
|
||||
}
|
||||
}
|
||||
|
||||
auto make_signal_injection(auto signal) const noexcept -> void {
|
||||
for (auto widget : this->widgets) {
|
||||
QObject::connect(widget, signal, [this, widget] { switch_widgets(widget); });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr inline auto checked_switch_function = [](auto& w, bool on) { w.set_checked(on); };
|
||||
constexpr inline auto opened_switch_function = [](auto& w, bool on) { w.set_opened(on); };
|
||||
constexpr inline auto selected_switch_function = [](auto& w, bool on) { w.set_selected(on); };
|
||||
|
||||
constexpr inline auto no_action_as_token = [](auto& w, bool on) { };
|
||||
|
||||
}
|
||||
namespace creeper::mutual_exclusion_group::pro {
|
||||
|
||||
struct TokenContext { };
|
||||
using Token = common::Token<TokenContext>;
|
||||
|
||||
template <typename Signal>
|
||||
struct SignalInjection : Token {
|
||||
Signal signal;
|
||||
|
||||
explicit SignalInjection(Signal signal) noexcept
|
||||
: signal { signal } { }
|
||||
|
||||
auto apply(auto& self) const noexcept -> void {
|
||||
self.make_signal_injection(signal); //
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept trait = std::derived_from<T, Token> || group::pro::trait<T>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait);
|
||||
using namespace group::pro;
|
||||
}
|
||||
namespace creeper {
|
||||
|
||||
template <layout_trait T, widget_trait W, auto switch_function>
|
||||
requires mutual_exclusion_group::internal::switch_function_trait<switch_function, W>
|
||||
using MutualExclusionGroup =
|
||||
mutual_exclusion_group::internal::MutualExclusionGroup<T, W, switch_function>;
|
||||
|
||||
template <layout_trait T, widget_trait W>
|
||||
using CheckGroup = Declarative<
|
||||
MutualExclusionGroup<T, W, mutual_exclusion_group::internal::checked_switch_function>,
|
||||
CheckerOr<mutual_exclusion_group::pro::checker, typename T::Checker>>;
|
||||
namespace check_group = mutual_exclusion_group;
|
||||
|
||||
template <layout_trait T, widget_trait W>
|
||||
using OpenGroup = Declarative<
|
||||
MutualExclusionGroup<T, W, mutual_exclusion_group::internal::opened_switch_function>,
|
||||
CheckerOr<mutual_exclusion_group::pro::checker, typename T::Checker>>;
|
||||
namespace open_group = mutual_exclusion_group;
|
||||
|
||||
template <layout_trait T, widget_trait W>
|
||||
using SelectGroup = Declarative<
|
||||
MutualExclusionGroup<T, W, mutual_exclusion_group::internal::selected_switch_function>,
|
||||
CheckerOr<mutual_exclusion_group::pro::checker, typename T::Checker>>;
|
||||
namespace select_group = mutual_exclusion_group;
|
||||
|
||||
namespace internal {
|
||||
inline auto use_mutual_exclusion_group_namespace() {
|
||||
std::ignore = check_group::pro::Token {};
|
||||
std::ignore = open_group::pro::Token {};
|
||||
std::ignore = select_group::pro::Token {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
135
modern-qt/layout/scroll.hh
Normal file
135
modern-qt/layout/scroll.hh
Normal file
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/theme/theme.hh"
|
||||
#include "modern-qt/utility/trait/widget.hh"
|
||||
#include "modern-qt/utility/wrapper/common.hh"
|
||||
#include "modern-qt/utility/wrapper/property.hh"
|
||||
#include "modern-qt/widget/widget.hh"
|
||||
#include <qscrollarea.h>
|
||||
#include <qscrollbar.h>
|
||||
|
||||
namespace creeper::scroll::internal {
|
||||
|
||||
/// NOTE: 先拿 qss 勉强用着吧,找时间完全重构
|
||||
class ScrollArea : public QScrollArea {
|
||||
public:
|
||||
explicit ScrollArea() noexcept {
|
||||
viewport()->setStyleSheet("background:transparent;");
|
||||
setStyleSheet("QScrollArea{background:transparent;}");
|
||||
|
||||
setWidgetResizable(true);
|
||||
}
|
||||
|
||||
void set_color_scheme(const ColorScheme& scheme) {
|
||||
constexpr auto q = [](const QColor& c, int a = 255) {
|
||||
return QString("rgba(%1,%2,%3,%4)").arg(c.red()).arg(c.green()).arg(c.blue()).arg(a);
|
||||
};
|
||||
|
||||
verticalScrollBar()->setStyleSheet(QString {
|
||||
"QScrollBar:vertical{background:transparent;width:8px;border-radius:4px;}"
|
||||
"QScrollBar::handle:vertical{background:%1;min-height:20px;border-radius:4px;}"
|
||||
"QScrollBar::handle:vertical:hover{background:%2;}"
|
||||
"QScrollBar::handle:vertical:pressed{background:%3;}"
|
||||
"QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,"
|
||||
"QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{height:0px;}",
|
||||
}
|
||||
.arg(q(scheme.primary, 235))
|
||||
.arg(q(scheme.primary))
|
||||
.arg(q(scheme.primary.darker(110))));
|
||||
|
||||
horizontalScrollBar()->setStyleSheet(QString {
|
||||
"QScrollBar:horizontal{background:transparent;height:8px;border-radius:4px;}"
|
||||
"QScrollBar::handle:horizontal{background:%1;min-width:20px;border-radius:4px;}"
|
||||
"QScrollBar::handle:horizontal:hover{background:%2;}"
|
||||
"QScrollBar::handle:horizontal:pressed{background:%3;}"
|
||||
"QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal,"
|
||||
"QScrollBar::add-page:horizontal,QScrollBar::sub-page:horizontal{width:0px;}",
|
||||
}
|
||||
.arg(q(scheme.primary, 235))
|
||||
.arg(q(scheme.primary))
|
||||
.arg(q(scheme.primary.darker(110))));
|
||||
}
|
||||
|
||||
void load_theme_manager(ThemeManager& manager) {
|
||||
manager.append_handler(this,
|
||||
[this](const ThemeManager& manager) { set_color_scheme(manager.color_scheme()); });
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
namespace creeper::scroll::pro {
|
||||
|
||||
using Token = common::Token<internal::ScrollArea>;
|
||||
|
||||
struct VerticalScrollBarPolicy : Token {
|
||||
Qt::ScrollBarPolicy policy;
|
||||
|
||||
explicit VerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) noexcept
|
||||
: policy { policy } { }
|
||||
|
||||
auto apply(auto& self) const noexcept -> void { //
|
||||
self.setVerticalScrollBarPolicy(policy);
|
||||
}
|
||||
};
|
||||
struct HorizontalScrollBarPolicy : Token {
|
||||
Qt::ScrollBarPolicy policy;
|
||||
|
||||
explicit HorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) noexcept
|
||||
: policy { policy } { }
|
||||
|
||||
auto apply(auto& self) const noexcept -> void { //
|
||||
self.setHorizontalScrollBarPolicy(policy);
|
||||
}
|
||||
};
|
||||
struct ScrollBarPolicy : Token {
|
||||
Qt::ScrollBarPolicy v;
|
||||
Qt::ScrollBarPolicy h;
|
||||
|
||||
explicit ScrollBarPolicy(Qt::ScrollBarPolicy v, Qt::ScrollBarPolicy h) noexcept
|
||||
: v { v }
|
||||
, h { h } { }
|
||||
|
||||
auto apply(auto& self) const noexcept -> void {
|
||||
self.setVerticalScrollBarPolicy(v);
|
||||
self.setHorizontalScrollBarPolicy(h);
|
||||
}
|
||||
};
|
||||
|
||||
template <item_trait T>
|
||||
struct Item : Token {
|
||||
T* item_pointer = nullptr;
|
||||
|
||||
explicit Item(auto&&... args) noexcept
|
||||
requires std::constructible_from<T, decltype(args)...>
|
||||
: item_pointer { new T { std::forward<decltype(args)>(args)... } } { }
|
||||
|
||||
explicit Item(T* pointer) noexcept
|
||||
: item_pointer { pointer } { }
|
||||
|
||||
auto apply(area_trait auto& layout) const noexcept -> void {
|
||||
if constexpr (widget_trait<T>) {
|
||||
layout.setWidget(item_pointer);
|
||||
}
|
||||
// NOTE: 这里可能有调整的空间,直接设置 Layout,
|
||||
// 布局 Size 行为是不正确的
|
||||
else if constexpr (layout_trait<T>) {
|
||||
const auto content = new Widget {
|
||||
widget::pro::Layout { item_pointer },
|
||||
};
|
||||
layout.setWidget(content);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
concept trait = std::derived_from<T, Token>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait);
|
||||
using namespace widget::pro;
|
||||
using namespace theme::pro;
|
||||
}
|
||||
namespace creeper {
|
||||
|
||||
using ScrollArea = Declarative<scroll::internal::ScrollArea,
|
||||
CheckerOr<scroll::pro::checker, widget::pro::checker, theme::pro::checker>>;
|
||||
|
||||
}
|
||||
26
modern-qt/layout/stacked.hh
Normal file
26
modern-qt/layout/stacked.hh
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "modern-qt/utility/wrapper/common.hh"
|
||||
#include "modern-qt/utility/wrapper/layout.hh"
|
||||
#include "modern-qt/utility/wrapper/property.hh"
|
||||
|
||||
#include <qstackedlayout.h>
|
||||
|
||||
namespace creeper::stacked::internal {
|
||||
class Stacked : public QStackedLayout { };
|
||||
}
|
||||
|
||||
namespace creeper::stacked::pro {
|
||||
|
||||
using Token = common::Token<internal::Stacked>;
|
||||
|
||||
template <typename T>
|
||||
concept trait = std::derived_from<T, Token> || layout::pro::trait<T>;
|
||||
|
||||
CREEPER_DEFINE_CHECKER(trait);
|
||||
using namespace layout::pro;
|
||||
}
|
||||
|
||||
namespace creeper {
|
||||
using Stacked = Declarative<stacked::internal::Stacked, stacked::pro::checker>;
|
||||
using NavHost = Stacked;
|
||||
}
|
||||
Reference in New Issue
Block a user