Files
ts-qt/creeper-qt/utility/painter/container.hh
2025-11-25 15:59:47 +08:00

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