348 lines
12 KiB
C++
348 lines
12 KiB
C++
#pragma once
|
||
#include "modern-qt/utility/painter/common.hh"
|
||
|
||
namespace creeper::painter {
|
||
|
||
// 核心容器结构体,现在继承自 Impl,使其满足 drawable_trait (假设 Impl 继承了所需的属性)
|
||
template <class Impl, drawable_trait... Ts>
|
||
struct Container : public Impl {
|
||
std::tuple<std::decay_t<Ts>...> drawable;
|
||
|
||
// 唯一构造函数:接受 Impl 实例和可变参数包
|
||
constexpr explicit Container(const Impl& impl, Ts&&... drawable)
|
||
: Impl { impl }
|
||
, drawable { std::make_tuple(std::forward<Ts>(drawable)...) } { }
|
||
|
||
auto operator()(qt::painter& painter)
|
||
requires(std::invocable<Ts, qt::painter&> && ...)
|
||
{
|
||
render(painter);
|
||
}
|
||
auto render(qt::painter& painter) noexcept
|
||
requires(std::invocable<Ts, qt::painter&> && ...)
|
||
{
|
||
constexpr auto has_unique = requires { //
|
||
static_cast<Impl&>(*this).unique_render(painter, drawable);
|
||
};
|
||
if constexpr (has_unique) {
|
||
static_cast<Impl&>(*this).unique_render(painter, drawable);
|
||
} else {
|
||
Impl::make_layout(drawable);
|
||
|
||
auto f = [&](auto&... d) { (d(painter), ...); };
|
||
std::apply(std::move(f), drawable);
|
||
}
|
||
}
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// 布局实现基类
|
||
// ----------------------------------------------------------------------
|
||
|
||
struct MakeLayoutFunction {
|
||
template <drawable_trait... Ts>
|
||
auto make_layout(this auto& self, std::tuple<Ts...>& drawable) {
|
||
std::apply([&self](auto&... d) { ((self.make(d)), ...); }, drawable);
|
||
}
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// SurfaceImpl (仅平移)
|
||
// ----------------------------------------------------------------------
|
||
|
||
struct SurfaceImpl : public MakeLayoutFunction, ContainerProps {
|
||
constexpr explicit SurfaceImpl(const qt::size& size, const qt::point& origin = {})
|
||
: ContainerProps {
|
||
.size = size,
|
||
.origin = origin,
|
||
} { }
|
||
auto make(drawable_trait auto& drawable) {
|
||
const auto& container_origin = origin;
|
||
|
||
auto& drawable_origin = drawable.origin;
|
||
drawable_origin.setX(container_origin.x() + drawable_origin.x());
|
||
drawable_origin.setY(container_origin.y() + drawable_origin.y());
|
||
};
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// BufferImpl
|
||
// ----------------------------------------------------------------------
|
||
|
||
struct BufferImpl : public MakeLayoutFunction, ContainerProps {
|
||
mutable QPixmap buffer;
|
||
|
||
constexpr explicit BufferImpl(const qt::size& size, const qt::point& origin = { 0, 0 })
|
||
: ContainerProps {
|
||
.size = size,
|
||
.origin = origin,
|
||
} { }
|
||
|
||
auto make(drawable_trait auto& drawable) {
|
||
const auto& container_origin = origin;
|
||
drawable.origin.setX(container_origin.x() + drawable.origin.x());
|
||
drawable.origin.setY(container_origin.y() + drawable.origin.y());
|
||
};
|
||
|
||
template <drawable_trait... Ts>
|
||
auto unique_render(qt::painter& main_painter, std::tuple<Ts...>& drawable) noexcept {
|
||
make_layout(drawable);
|
||
|
||
if (buffer.size() != size || buffer.isNull()) {
|
||
buffer = QPixmap(size.width(), size.height());
|
||
buffer.fill(Qt::transparent);
|
||
}
|
||
buffer.fill(Qt::transparent);
|
||
|
||
auto buffer_painter = qt::painter { &buffer };
|
||
buffer_painter.translate(-origin.x(), -origin.y());
|
||
|
||
const auto f = [&](auto&... args) {
|
||
(
|
||
[&]() {
|
||
buffer_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||
args(buffer_painter);
|
||
}(),
|
||
...);
|
||
};
|
||
std::apply(std::move(f), drawable);
|
||
buffer_painter.end();
|
||
|
||
main_painter.drawPixmap(origin, buffer);
|
||
}
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// BoxImpl (居中对齐)
|
||
// ----------------------------------------------------------------------
|
||
|
||
struct BoxImpl : public MakeLayoutFunction, ContainerProps {
|
||
|
||
constexpr explicit BoxImpl(
|
||
const qt::size& size, const qt::align& align, const qt::point& origin = {})
|
||
: ContainerProps {
|
||
.size = size,
|
||
.align = align,
|
||
.origin = origin,
|
||
} { }
|
||
|
||
auto make(drawable_trait auto& drawable) {
|
||
const auto container_align = align;
|
||
const auto container_size = size;
|
||
const auto container_origin = origin;
|
||
|
||
auto& drawable_origin = drawable.origin;
|
||
auto& drawable_size = drawable.size;
|
||
|
||
const auto container_w = container_size.width();
|
||
const auto container_h = container_size.height();
|
||
|
||
if (container_align & Qt::AlignRight) {
|
||
drawable_origin.setX(container_origin.x() + container_w - drawable_size.width());
|
||
} else if (container_align & Qt::AlignHCenter) {
|
||
const auto dx = (container_w - drawable_size.width()) / 2;
|
||
drawable_origin.setX(container_origin.x() + dx);
|
||
} else {
|
||
drawable_origin.setX(container_origin.x());
|
||
}
|
||
|
||
if (container_align & Qt::AlignBottom) {
|
||
drawable_origin.setY(container_origin.y() + container_h - drawable_size.height());
|
||
} else if (container_align & Qt::AlignVCenter) {
|
||
const auto dy = (container_h - drawable_size.height()) / 2;
|
||
drawable_origin.setY(container_origin.y() + dy);
|
||
} else {
|
||
drawable_origin.setY(container_origin.y());
|
||
}
|
||
};
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// RowImpl (横向流式布局)
|
||
// ----------------------------------------------------------------------
|
||
|
||
struct RowImpl : public MakeLayoutFunction, ContainerProps {
|
||
// 主轴对齐 (Horizontal)
|
||
const qt::align main_align;
|
||
|
||
constexpr explicit RowImpl(
|
||
const qt::size& size,
|
||
const qt::align& main_align = Qt::AlignLeft, // 主轴对齐:AlignLeft/AlignRight/AlignHCenter
|
||
const qt::align& cross_align = Qt::AlignVCenter, // 非主轴对齐:AlignTop/AlignBottom/AlignVCenter
|
||
const qt::point& origin = {})
|
||
: ContainerProps {
|
||
.size = size,
|
||
.align = cross_align, // ContainerProps::align 存储非主轴对齐
|
||
.origin = origin,
|
||
}
|
||
, main_align(main_align) // 存储主轴对齐
|
||
{ }
|
||
|
||
mutable int current_x = 0;
|
||
|
||
template <drawable_trait... Ts>
|
||
auto make_layout(this auto& self, std::tuple<Ts...>& drawable) {
|
||
// 1. 计算主轴总尺寸 (Total Width)
|
||
int total_width = 0;
|
||
std::apply(
|
||
[&total_width](auto&... d) { ((total_width += d.size.width()), ...); }, drawable);
|
||
|
||
// 2. 计算主轴偏移 (Main Axis Offset)
|
||
int initial_x_offset = 0;
|
||
const int remaining_space = self.size.width() - total_width;
|
||
|
||
if (remaining_space > 0) {
|
||
if (self.main_align & Qt::AlignRight) {
|
||
initial_x_offset = remaining_space;
|
||
} else if (self.main_align & Qt::AlignHCenter) {
|
||
initial_x_offset = remaining_space / 2;
|
||
}
|
||
}
|
||
|
||
// 3. 设置起始 X 坐标
|
||
self.current_x = self.origin.x() + initial_x_offset;
|
||
|
||
// 4. 应用布局到每个元素
|
||
std::apply([&self](auto&... d) { ((self.make(d)), ...); }, drawable);
|
||
}
|
||
|
||
auto make(drawable_trait auto& drawable) {
|
||
const auto container_cross_align = align; // 非主轴对齐 (垂直)
|
||
const auto container_size = size;
|
||
const auto container_origin = origin;
|
||
|
||
auto& drawable_origin = drawable.origin;
|
||
const auto drawable_h = drawable.size.height();
|
||
|
||
// 1. 主轴布局 (X 坐标累加)
|
||
drawable_origin.setX(current_x);
|
||
current_x += drawable.size.width();
|
||
|
||
// 2. 非主轴对齐 (垂直对齐)
|
||
if (container_cross_align & Qt::AlignBottom) {
|
||
drawable_origin.setY(container_origin.y() + container_size.height() - drawable_h);
|
||
} else if (container_cross_align & Qt::AlignVCenter) {
|
||
const auto dy = (container_size.height() - drawable_h) / 2;
|
||
drawable_origin.setY(container_origin.y() + dy);
|
||
} else { // 默认 AlignTop
|
||
drawable_origin.setY(container_origin.y());
|
||
}
|
||
};
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// ColImpl (垂直流式布局)
|
||
// ----------------------------------------------------------------------
|
||
|
||
struct ColImpl : public MakeLayoutFunction, ContainerProps {
|
||
// 主轴对齐 (Vertical)
|
||
const qt::align main_align;
|
||
|
||
constexpr explicit ColImpl(
|
||
const qt::size& size,
|
||
const qt::align& main_align = Qt::AlignTop, // 主轴对齐:AlignTop/AlignBottom/AlignVCenter
|
||
const qt::align& cross_align = Qt::AlignHCenter, // 非主轴对齐:AlignLeft/AlignRight/AlignHCenter
|
||
const qt::point& origin = {})
|
||
: ContainerProps {
|
||
.size = size,
|
||
.align = cross_align, // ContainerProps::align 存储非主轴对齐
|
||
.origin = origin,
|
||
}
|
||
, main_align(main_align) // 存储主轴对齐
|
||
{ }
|
||
|
||
mutable int current_y = 0;
|
||
|
||
template <drawable_trait... Ts>
|
||
auto make_layout(this auto& self, std::tuple<Ts...>& drawable) {
|
||
// 1. 计算主轴总尺寸 (Total Height)
|
||
int total_height = 0;
|
||
std::apply(
|
||
[&total_height](auto&... d) { ((total_height += d.size.height()), ...); }, drawable);
|
||
|
||
// 2. 计算主轴偏移 (Main Axis Offset)
|
||
int initial_y_offset = 0;
|
||
const int remaining_space = self.size.height() - total_height;
|
||
|
||
if (remaining_space > 0) {
|
||
if (self.main_align & Qt::AlignBottom) {
|
||
initial_y_offset = remaining_space;
|
||
} else if (self.main_align & Qt::AlignVCenter) {
|
||
initial_y_offset = remaining_space / 2;
|
||
}
|
||
}
|
||
|
||
// 3. 设置起始 Y 坐标
|
||
self.current_y = self.origin.y() + initial_y_offset;
|
||
|
||
// 4. 应用布局到每个元素
|
||
std::apply([&self](auto&... d) { ((self.make(d)), ...); }, drawable);
|
||
}
|
||
|
||
auto make(drawable_trait auto& drawable) {
|
||
const auto container_cross_align = align; // 非主轴对齐 (水平)
|
||
const auto container_size = size;
|
||
const auto container_origin = origin;
|
||
|
||
auto& drawable_origin = drawable.origin;
|
||
const auto drawable_w = drawable.size.width();
|
||
|
||
// 1. 主轴布局 (Y 坐标累加)
|
||
drawable_origin.setY(current_y);
|
||
current_y += drawable.size.height();
|
||
|
||
// 2. 非主轴对齐 (水平对齐)
|
||
if (container_cross_align & Qt::AlignRight) {
|
||
drawable_origin.setX(container_origin.x() + container_size.width() - drawable_w);
|
||
} else if (container_cross_align & Qt::AlignHCenter) {
|
||
const auto dx = (container_size.width() - drawable_w) / 2;
|
||
drawable_origin.setX(container_origin.x() + dx);
|
||
} else { // 默认 AlignLeft
|
||
drawable_origin.setX(container_origin.x());
|
||
}
|
||
};
|
||
};
|
||
|
||
// ----------------------------------------------------------------------
|
||
// 通用 Container 推导指引 (用于简化用户代码)
|
||
// ----------------------------------------------------------------------
|
||
|
||
template <typename... Ts>
|
||
Container(const SurfaceImpl& impl, Ts&&... args) -> Container<SurfaceImpl, Ts...>;
|
||
|
||
template <typename... Ts>
|
||
Container(const BufferImpl& impl, Ts&&... args) -> Container<BufferImpl, Ts...>;
|
||
|
||
template <typename... Ts>
|
||
Container(const BoxImpl& impl, Ts&&... args) -> Container<BoxImpl, Ts...>;
|
||
|
||
template <typename... Ts>
|
||
Container(const RowImpl& impl, Ts&&... args) -> Container<RowImpl, Ts...>;
|
||
|
||
template <typename... Ts>
|
||
Container(const ColImpl& impl, Ts&&... args) -> Container<ColImpl, Ts...>;
|
||
|
||
// ----------------------------------------------------------------------
|
||
// Paint 类型导出
|
||
// ----------------------------------------------------------------------
|
||
|
||
namespace Paint {
|
||
|
||
template <drawable_trait... Ts>
|
||
using Surface = Container<SurfaceImpl, Ts...>;
|
||
|
||
template <drawable_trait... Ts>
|
||
using Buffer = Container<BufferImpl, Ts...>;
|
||
|
||
template <drawable_trait... Ts>
|
||
using Box = Container<BoxImpl, Ts...>;
|
||
|
||
template <drawable_trait... Ts>
|
||
using Row = Container<RowImpl, Ts...>;
|
||
|
||
template <drawable_trait... Ts>
|
||
using Col = Container<ColImpl, Ts...>;
|
||
}
|
||
|
||
} // namespace creeper::painter
|