344 lines
12 KiB
C++
344 lines
12 KiB
C++
#pragma once
|
|
#include "creeper-qt/utility/painter/common.hh"
|
|
|
|
namespace creeper::painter {
|
|
|
|
template <class Impl, drawable_trait... Ts>
|
|
struct Container : public Impl {
|
|
std::tuple<std::decay_t<Ts>...> drawable;
|
|
|
|
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 {
|
|
const qt::align main_align;
|
|
|
|
constexpr explicit RowImpl(
|
|
const qt::size& size,
|
|
const qt::align& main_align = Qt::AlignLeft,
|
|
const qt::align& cross_align = Qt::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 {
|
|
const qt::align main_align;
|
|
|
|
constexpr explicit ColImpl(
|
|
const qt::size& size,
|
|
const qt::align& main_align = Qt::AlignTop,
|
|
const qt::align& cross_align = Qt::AlignHCenter,
|
|
const qt::point& origin = {})
|
|
: ContainerProps {
|
|
.size = size,
|
|
.align = cross_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
|