feat:data slove and update heatmap

This commit is contained in:
2025-10-29 14:09:28 +08:00
parent c50b44efe2
commit c6cef3d89d
200 changed files with 100674 additions and 52814 deletions

View File

@@ -1,98 +1,98 @@
#pragma once
#include "modern-qt/utility/wrapper/property.hh"
#include <qpainter.h>
namespace creeper::qt {
using painter = QPainter;
using point = QPointF;
using size = QSizeF;
using rect = QRectF;
using color = QColor;
using real = qreal;
using align = Qt::Alignment;
using string = QString;
using font = QFont;
using text_option = QTextOption;
}
namespace creeper::painter {
template <class T>
concept common_trait = requires(T t) {
{ auto { t.origin } } -> std::same_as<qt::point>;
{ auto { t.size } } -> std::same_as<qt::size>;
};
template <class T>
concept container_trait = requires(T t) {
{ auto { t.align } } -> std::same_as<qt::align>;
} && common_trait<T>;
template <class T>
concept shape_trait = requires(T t) {
{ auto { t.color_container } } -> std::same_as<qt::color>;
{ auto { t.color_outline } } -> std::same_as<qt::color>;
{ auto { t.thickness_outline } } -> std::same_as<qt::real>;
};
template <class T>
concept drawable_trait = common_trait<T> && std::invocable<T, qt::painter&>;
struct CommonProps {
qt::point origin = qt::point { 0, 0 };
qt::size size = qt::size { 0, 0 };
auto rect() const { return qt::rect { origin, size }; }
};
struct ContainerProps {
qt::size size = qt::size { 0, 0 };
qt::align align = qt::align {};
qt::point origin = qt::point { 0, 0 };
auto rect() const { return qt::rect { origin, size }; }
};
struct ShapeProps {
qt::color container_color = Qt::transparent;
qt::color outline_color = Qt::transparent;
qt::real outline_width = 0;
};
}
namespace creeper::painter::common::pro {
struct Token { };
using Size = DerivedProp<Token, qt::size, [](auto& self, auto const& v) { self.size = v; }>;
using Origin = DerivedProp<Token, qt::point, [](auto& self, auto const& v) { self.origin = v; }>;
using ContainerColor =
SetterProp<Token, qt::color, [](auto& self, auto const& v) { self.container_color = v; }>;
using OutlineColor =
SetterProp<Token, qt::color, [](auto& self, auto const& v) { self.outline_color = v; }>;
using OutlineWidth =
SetterProp<Token, qt::real, [](auto& self, auto const& v) { self.outline_width = v; }>;
struct Outline : Token {
qt::color color;
qt::real width;
Outline(const qt::color& color, qt::real width)
: color { color }
, width { width } { }
auto apply(auto& self) {
self.outline_color = color;
self.outline_width = width;
}
};
/// Alias
using Fill = ContainerColor;
/// Export
template <class T>
concept trait = std::derived_from<T, Token>;
CREEPER_DEFINE_CHECKER(trait);
}
#pragma once
#include "modern-qt/utility/wrapper/property.hh"
#include <qpainter.h>
namespace creeper::qt {
using painter = QPainter;
using point = QPointF;
using size = QSizeF;
using rect = QRectF;
using color = QColor;
using real = qreal;
using align = Qt::Alignment;
using string = QString;
using font = QFont;
using text_option = QTextOption;
}
namespace creeper::painter {
template <class T>
concept common_trait = requires(T t) {
{ auto { t.origin } } -> std::same_as<qt::point>;
{ auto { t.size } } -> std::same_as<qt::size>;
};
template <class T>
concept container_trait = requires(T t) {
{ auto { t.align } } -> std::same_as<qt::align>;
} && common_trait<T>;
template <class T>
concept shape_trait = requires(T t) {
{ auto { t.color_container } } -> std::same_as<qt::color>;
{ auto { t.color_outline } } -> std::same_as<qt::color>;
{ auto { t.thickness_outline } } -> std::same_as<qt::real>;
};
template <class T>
concept drawable_trait = common_trait<T> && std::invocable<T, qt::painter&>;
struct CommonProps {
qt::point origin = qt::point { 0, 0 };
qt::size size = qt::size { 0, 0 };
auto rect() const { return qt::rect { origin, size }; }
};
struct ContainerProps {
qt::size size = qt::size { 0, 0 };
qt::align align = qt::align {};
qt::point origin = qt::point { 0, 0 };
auto rect() const { return qt::rect { origin, size }; }
};
struct ShapeProps {
qt::color container_color = Qt::transparent;
qt::color outline_color = Qt::transparent;
qt::real outline_width = 0;
};
}
namespace creeper::painter::common::pro {
struct Token { };
using Size = DerivedProp<Token, qt::size, [](auto& self, auto const& v) { self.size = v; }>;
using Origin = DerivedProp<Token, qt::point, [](auto& self, auto const& v) { self.origin = v; }>;
using ContainerColor =
SetterProp<Token, qt::color, [](auto& self, auto const& v) { self.container_color = v; }>;
using OutlineColor =
SetterProp<Token, qt::color, [](auto& self, auto const& v) { self.outline_color = v; }>;
using OutlineWidth =
SetterProp<Token, qt::real, [](auto& self, auto const& v) { self.outline_width = v; }>;
struct Outline : Token {
qt::color color;
qt::real width;
Outline(const qt::color& color, qt::real width)
: color { color }
, width { width } { }
auto apply(auto& self) {
self.outline_color = color;
self.outline_width = width;
}
};
/// Alias
using Fill = ContainerColor;
/// Export
template <class T>
concept trait = std::derived_from<T, Token>;
CREEPER_DEFINE_CHECKER(trait);
}

View File

@@ -1,347 +1,347 @@
#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
#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

View File

@@ -1,15 +1,15 @@
#include "modern-qt/utility/painter/helper.hh"
#include <qdebug.h>
namespace creeper::util {
constexpr auto enable_print_paint_event_count = bool { false };
auto print_paint_event_count() noexcept -> void {
if constexpr (enable_print_paint_event_count) {
static auto count = std::size_t { 0 };
qDebug() << "[PainterHelper] Paint Event:" << count++;
}
}
}
#include "modern-qt/utility/painter/helper.hh"
#include <qdebug.h>
namespace creeper::util {
constexpr auto enable_print_paint_event_count = bool { false };
auto print_paint_event_count() noexcept -> void {
if constexpr (enable_print_paint_event_count) {
static auto count = std::size_t { 0 };
qDebug() << "[PainterHelper] Paint Event:" << count++;
}
}
}

View File

@@ -1,224 +1,224 @@
#pragma once
#include <qpainter.h>
#include <qpainterpath.h>
namespace creeper::util {
auto print_paint_event_count() noexcept -> void;
/// @brief 隐藏冗杂的细节,解放命令式的绘图调用
class PainterHelper {
public:
using Renderer = std::function<void(QPainter&)>;
explicit PainterHelper(QPainter& painter)
: painter(painter) {
print_paint_event_count();
}
inline void done() { }
inline PainterHelper& apply(const Renderer& renderer) {
renderer(painter);
return *this;
}
inline PainterHelper& apply(const std::ranges::range auto& renderers)
requires std::same_as<std::ranges::range_value_t<decltype(renderers)>, Renderer>
{
for (const auto& renderer : renderers)
renderer(painter);
return *this;
}
public:
inline PainterHelper& set_brush(const QBrush& brush) {
painter.setBrush(brush);
return *this;
}
inline PainterHelper& set_pen(const QPen& pen) {
painter.setPen(pen);
return *this;
}
inline PainterHelper& set_opacity(double opacity) {
painter.setOpacity(opacity);
return *this;
}
inline PainterHelper& set_render_hint(QPainter::RenderHint hint, bool on = true) {
painter.setRenderHint(hint, on);
return *this;
}
inline PainterHelper& set_render_hints(QPainter::RenderHints hint, bool on = true) {
painter.setRenderHints(hint, on);
return *this;
}
inline PainterHelper& set_clip_path(
const QPainterPath& path, Qt::ClipOperation operation = Qt::ReplaceClip) {
painter.setClipPath(path, operation);
return *this;
}
public:
inline PainterHelper& point(const QColor& color, double radius, const QPointF& point) {
pen_only({ color, radius * 2 }).drawPoint(point);
return *this;
}
inline PainterHelper& ellipse(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect) {
brush_only({ background }).drawEllipse(rect);
const auto half = border_width / 2;
if (border_width != 0)
pen_only({ border_color, border_width })
.drawEllipse(rect.adjusted(half, half, -half, -half));
return *this;
}
inline PainterHelper& ellipse(const QColor& background, const QColor& border_color,
double border_width, const QPointF& origin, double radius_x, double radius_y) {
brush_only({ background }).drawEllipse(origin, radius_x, radius_y);
if (border_width != 0)
pen_only({ border_color, border_width })
.drawEllipse(origin, radius_x - border_width / 2, radius_y - border_width / 2);
return *this;
}
inline PainterHelper& rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect) {
brush_only({ background }).drawRect(rect);
if (border_width == 0) return *this;
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
pen_only({ border_color, border_width }).drawRect(inliner_border_rectangle);
return *this;
}
inline PainterHelper& rounded_rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect, double radius_x, double radius_y) {
brush_only({ background }).drawRoundedRect(rect, radius_x, radius_y);
if (border_width == 0) return *this;
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
pen_only({ border_color, border_width })
.drawRoundedRect(inliner_border_rectangle, std::max(radius_x - border_width / 2, 0.),
std::max(radius_y - border_width / 2, 0.));
return *this;
}
inline PainterHelper& rounded_rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect, double tl, double tr, double br, double bl) {
const auto path = make_rounded_rect_path(rect, tl, tr, br, bl);
brush_only({ background }).drawPath(path);
if (border_width == 0) return *this;
const auto inliner = [=](double r) { return std::max(r - border_width / 2, 0.); };
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
const auto inliner_path = make_rounded_rect_path(
inliner_border_rectangle, inliner(tl), inliner(tr), inliner(br), inliner(bl));
pen_only({ border_color, border_width }).drawPath(inliner_path);
return *this;
}
// Pen 是以路径为中心来绘制图片,有绘出 rect 导致画面被裁切的可能,由于是 path 类型,不好做限制
inline PainterHelper& path(const QColor& background, const QColor& border_color,
double border_width, const QPainterPath& path) {
brush_only({ background }).drawPath(path);
if (border_width != 0) pen_only({ border_color, border_width }).drawPath(path);
return *this;
}
inline PainterHelper& pixmap(const QPixmap& pixmap, const QRectF& dst, const QRectF& src) {
painter.drawPixmap(dst, pixmap, src);
return *this;
}
inline PainterHelper& simple_text(const QString& text, const QFont& font, const QColor& color,
const QRectF& rect, Qt::Alignment alignment) {
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setFont(font);
painter.setBrush(Qt::NoBrush);
painter.setPen({ color });
painter.drawText(rect, alignment, text);
return *this;
}
private:
QPainter& painter;
QPainter& pen_only(const QPen& pen) {
painter.setBrush(Qt::NoBrush);
painter.setPen(pen);
return painter;
}
QPainter& brush_only(const QBrush& brush) {
painter.setBrush(brush);
painter.setPen(Qt::NoPen);
return painter;
}
static auto make_rounded_rect_path(
const QRectF& rect, qreal tl, qreal tr, qreal br, qreal bl) noexcept -> QPainterPath {
auto path = QPainterPath {};
const auto half_width = rect.width() / 2.0;
const auto half_height = rect.height() / 2.0;
const auto max_radius = std::min(half_width, half_height);
const auto clamp_radius = [&](qreal r) {
return r < 0 ? max_radius : std::min(r, max_radius);
};
tl = clamp_radius(tl);
tr = clamp_radius(tr);
br = clamp_radius(br);
bl = clamp_radius(bl);
path.moveTo(rect.topLeft() + QPointF(tl, 0));
path.lineTo(rect.topRight() - QPointF(tr, 0));
path.arcTo(
QRectF(rect.topRight().x() - 2 * tr, rect.topRight().y(), 2 * tr, 2 * tr), 90, -90);
path.lineTo(rect.bottomRight() - QPointF(0, br));
path.arcTo(QRectF(rect.bottomRight().x() - 2 * br, rect.bottomRight().y() - 2 * br, 2 * br,
2 * br),
0, -90);
path.lineTo(rect.bottomLeft() + QPointF(bl, 0));
path.arcTo(QRectF(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * bl, 2 * bl, 2 * bl),
270, -90);
path.lineTo(rect.topLeft() + QPointF(0, tl));
path.arcTo(QRectF(rect.topLeft().x(), rect.topLeft().y(), 2 * tl, 2 * tl), 180, -90);
path.closeSubpath();
return path;
}
};
}
#pragma once
#include <qpainter.h>
#include <qpainterpath.h>
namespace creeper::util {
auto print_paint_event_count() noexcept -> void;
/// @brief 隐藏冗杂的细节,解放命令式的绘图调用
class PainterHelper {
public:
using Renderer = std::function<void(QPainter&)>;
explicit PainterHelper(QPainter& painter)
: painter(painter) {
print_paint_event_count();
}
inline void done() { }
inline PainterHelper& apply(const Renderer& renderer) {
renderer(painter);
return *this;
}
inline PainterHelper& apply(const std::ranges::range auto& renderers)
requires std::same_as<std::ranges::range_value_t<decltype(renderers)>, Renderer>
{
for (const auto& renderer : renderers)
renderer(painter);
return *this;
}
public:
inline PainterHelper& set_brush(const QBrush& brush) {
painter.setBrush(brush);
return *this;
}
inline PainterHelper& set_pen(const QPen& pen) {
painter.setPen(pen);
return *this;
}
inline PainterHelper& set_opacity(double opacity) {
painter.setOpacity(opacity);
return *this;
}
inline PainterHelper& set_render_hint(QPainter::RenderHint hint, bool on = true) {
painter.setRenderHint(hint, on);
return *this;
}
inline PainterHelper& set_render_hints(QPainter::RenderHints hint, bool on = true) {
painter.setRenderHints(hint, on);
return *this;
}
inline PainterHelper& set_clip_path(
const QPainterPath& path, Qt::ClipOperation operation = Qt::ReplaceClip) {
painter.setClipPath(path, operation);
return *this;
}
public:
inline PainterHelper& point(const QColor& color, double radius, const QPointF& point) {
pen_only({ color, radius * 2 }).drawPoint(point);
return *this;
}
inline PainterHelper& ellipse(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect) {
brush_only({ background }).drawEllipse(rect);
const auto half = border_width / 2;
if (border_width != 0)
pen_only({ border_color, border_width })
.drawEllipse(rect.adjusted(half, half, -half, -half));
return *this;
}
inline PainterHelper& ellipse(const QColor& background, const QColor& border_color,
double border_width, const QPointF& origin, double radius_x, double radius_y) {
brush_only({ background }).drawEllipse(origin, radius_x, radius_y);
if (border_width != 0)
pen_only({ border_color, border_width })
.drawEllipse(origin, radius_x - border_width / 2, radius_y - border_width / 2);
return *this;
}
inline PainterHelper& rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect) {
brush_only({ background }).drawRect(rect);
if (border_width == 0) return *this;
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
pen_only({ border_color, border_width }).drawRect(inliner_border_rectangle);
return *this;
}
inline PainterHelper& rounded_rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect, double radius_x, double radius_y) {
brush_only({ background }).drawRoundedRect(rect, radius_x, radius_y);
if (border_width == 0) return *this;
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
pen_only({ border_color, border_width })
.drawRoundedRect(inliner_border_rectangle, std::max(radius_x - border_width / 2, 0.),
std::max(radius_y - border_width / 2, 0.));
return *this;
}
inline PainterHelper& rounded_rectangle(const QColor& background, const QColor& border_color,
double border_width, const QRectF& rect, double tl, double tr, double br, double bl) {
const auto path = make_rounded_rect_path(rect, tl, tr, br, bl);
brush_only({ background }).drawPath(path);
if (border_width == 0) return *this;
const auto inliner = [=](double r) { return std::max(r - border_width / 2, 0.); };
const auto inliner_border_rectangle =
rect.adjusted(border_width / 2, border_width / 2, -border_width / 2, -border_width / 2);
const auto inliner_path = make_rounded_rect_path(
inliner_border_rectangle, inliner(tl), inliner(tr), inliner(br), inliner(bl));
pen_only({ border_color, border_width }).drawPath(inliner_path);
return *this;
}
// Pen 是以路径为中心来绘制图片,有绘出 rect 导致画面被裁切的可能,由于是 path 类型,不好做限制
inline PainterHelper& path(const QColor& background, const QColor& border_color,
double border_width, const QPainterPath& path) {
brush_only({ background }).drawPath(path);
if (border_width != 0) pen_only({ border_color, border_width }).drawPath(path);
return *this;
}
inline PainterHelper& pixmap(const QPixmap& pixmap, const QRectF& dst, const QRectF& src) {
painter.drawPixmap(dst, pixmap, src);
return *this;
}
inline PainterHelper& simple_text(const QString& text, const QFont& font, const QColor& color,
const QRectF& rect, Qt::Alignment alignment) {
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setFont(font);
painter.setBrush(Qt::NoBrush);
painter.setPen({ color });
painter.drawText(rect, alignment, text);
return *this;
}
private:
QPainter& painter;
QPainter& pen_only(const QPen& pen) {
painter.setBrush(Qt::NoBrush);
painter.setPen(pen);
return painter;
}
QPainter& brush_only(const QBrush& brush) {
painter.setBrush(brush);
painter.setPen(Qt::NoPen);
return painter;
}
static auto make_rounded_rect_path(
const QRectF& rect, qreal tl, qreal tr, qreal br, qreal bl) noexcept -> QPainterPath {
auto path = QPainterPath {};
const auto half_width = rect.width() / 2.0;
const auto half_height = rect.height() / 2.0;
const auto max_radius = std::min(half_width, half_height);
const auto clamp_radius = [&](qreal r) {
return r < 0 ? max_radius : std::min(r, max_radius);
};
tl = clamp_radius(tl);
tr = clamp_radius(tr);
br = clamp_radius(br);
bl = clamp_radius(bl);
path.moveTo(rect.topLeft() + QPointF(tl, 0));
path.lineTo(rect.topRight() - QPointF(tr, 0));
path.arcTo(
QRectF(rect.topRight().x() - 2 * tr, rect.topRight().y(), 2 * tr, 2 * tr), 90, -90);
path.lineTo(rect.bottomRight() - QPointF(0, br));
path.arcTo(QRectF(rect.bottomRight().x() - 2 * br, rect.bottomRight().y() - 2 * br, 2 * br,
2 * br),
0, -90);
path.lineTo(rect.bottomLeft() + QPointF(bl, 0));
path.arcTo(QRectF(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * bl, 2 * bl, 2 * bl),
270, -90);
path.lineTo(rect.topLeft() + QPointF(0, tl));
path.arcTo(QRectF(rect.topLeft().x(), rect.topLeft().y(), 2 * tl, 2 * tl), 180, -90);
path.closeSubpath();
return path;
}
};
}

View File

@@ -1,200 +1,200 @@
#pragma once
#include "modern-qt/utility/painter/common.hh"
#include <qpainterpath.h>
namespace creeper::painter::internal {
struct EraseRectangle : public CommonProps {
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
painter.setBrush(Qt::black);
painter.setPen(Qt::NoPen);
painter.drawRect(rect());
painter.restore();
}
};
struct Rectangle : public CommonProps, ShapeProps {
auto operator()(qt::painter& painter) const noexcept {
painter.save();
const auto rectangle = qt::rect { origin, size };
painter.setBrush(container_color);
painter.setPen(Qt::NoPen);
painter.drawRect(rectangle);
if (outline_width > 0) {
const auto thickness = outline_width / 2;
const auto inliner = rectangle.adjusted(thickness, thickness, -thickness, -thickness);
painter.setPen({ outline_color, outline_width });
painter.setBrush(Qt::NoBrush);
painter.drawRect(inliner);
}
painter.restore();
}
};
struct RoundedRectangle : public CommonProps, ShapeProps {
double radius_tl = 0;
double radius_tr = 0;
double radius_bl = 0;
double radius_br = 0;
auto set_radiuses(double r) {
radius_tl = r;
radius_tr = r;
radius_bl = r;
radius_br = r;
}
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
const auto rect = qt::rect { origin, size };
const auto outline_shape =
make_rounded_rect_path(rect, radius_tl, radius_tr, radius_br, radius_bl);
painter.setPen(Qt::NoPen);
painter.setBrush(container_color);
painter.drawPath(outline_shape);
if (outline_width > 0) {
const auto thickness = outline_width;
const auto inliner_f = [=](double r) { return std::max(r - thickness / 2, 0.); };
const auto inliner_rect =
rect.adjusted(thickness / 2, thickness / 2, -thickness / 2, -thickness / 2);
const auto inliner_shape = make_rounded_rect_path(inliner_rect, inliner_f(radius_tl),
inliner_f(radius_tr), inliner_f(radius_br), inliner_f(radius_bl));
painter.setBrush(Qt::NoBrush);
painter.setPen({ outline_color, outline_width });
painter.drawPath(inliner_shape);
}
painter.restore();
}
static constexpr auto make_rounded_rect_path(
const qt::rect& rect, qreal tl, qreal tr, qreal br, qreal bl) noexcept -> QPainterPath {
auto path = QPainterPath {};
const auto max_radius = std::min(rect.width(), rect.height()) / 2.0;
const auto clamp = [&](qreal r) -> qreal {
return r < 0 ? max_radius : std::min(r, max_radius);
};
tl = clamp(tl);
tr = clamp(tr);
br = clamp(br);
bl = clamp(bl);
const auto Arc = [](qreal x, qreal y, qreal r,
int start_angle) -> std::tuple<qt::rect, int, int> {
return { qt::rect(x, y, 2 * r, 2 * r), start_angle, -90 };
};
path.moveTo(rect.topLeft() + qt::point(tl, 0));
path.lineTo(rect.topRight() - qt::point(tr, 0));
const auto [tr_rect, tr_start, tr_span] =
Arc(rect.topRight().x() - 2 * tr, rect.topRight().y(), tr, 90);
path.arcTo(tr_rect, tr_start, tr_span);
path.lineTo(rect.bottomRight() - qt::point(0, br));
const auto [br_rect, br_start, br_span] =
Arc(rect.bottomRight().x() - 2 * br, rect.bottomRight().y() - 2 * br, br, 0);
path.arcTo(br_rect, br_start, br_span);
path.lineTo(rect.bottomLeft() + qt::point(bl, 0));
const auto [bl_rect, bl_start, bl_span] =
Arc(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * bl, bl, 270);
path.arcTo(bl_rect, bl_start, bl_span);
path.lineTo(rect.topLeft() + qt::point(0, tl));
const auto [tl_rect, tl_start, tl_span] =
Arc(rect.topLeft().x(), rect.topLeft().y(), tl, 180);
path.arcTo(tl_rect, tl_start, tl_span);
path.closeSubpath();
return path;
}
};
struct Text : CommonProps {
qt::string text;
qt::font font;
qt::color color = Qt::black;
qt::text_option text_option;
qt::real scale = 1.;
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.scale(scale, scale);
const auto origin_rect = rect();
const auto offset_x = origin_rect.x() * (1.0 - scale);
const auto center_y = origin_rect.y() + origin_rect.height() / 2.0;
const auto offset_y = center_y * (1.0 - scale);
painter.translate(offset_x, offset_y);
painter.setBrush(Qt::NoBrush);
painter.setPen(color);
painter.setFont(font);
const auto scaled_rect = qt::rect {
origin_rect.x() / scale,
origin_rect.y() / scale,
origin_rect.width() / scale,
origin_rect.height() / scale,
};
painter.drawText(scaled_rect, text, text_option);
painter.restore();
}
};
}
namespace creeper::painter {
/// Export Rounded Rectangle
using RadiusTL = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_tl = radius; }>;
using RadiusTR = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_tr = radius; }>;
using RadiusBL = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_bl = radius; }>;
using RadiusBR = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_br = radius; }>;
using Radiuses = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.set_radiuses(radius); }>;
/// Export Text
using Text = DerivedProp<common::pro::Token, qt::string,
[](auto& self, const auto& text) { self.text = text; }>;
using Font = DerivedProp<common::pro::Token, qt::font,
[](auto& self, const auto& font) { self.font = font; }>;
using Color = DerivedProp<common::pro::Token, qt::color,
[](auto& self, const auto& color) { self.color = color; }>;
using Scale = SetterProp<common::pro::Token, qt::real,
[](auto& self, const auto& scale) { self.scale = scale; }>;
using TextOption = DerivedProp<common::pro::Token, qt::text_option,
[](auto& self, const auto& option) { self.text_option = option; }>;
namespace Paint {
using EraseRectangle = Declarative<internal::EraseRectangle, CheckerOr<common::pro::checker>>;
using Rectangle = Declarative<internal::Rectangle, CheckerOr<common::pro::checker>>;
using RoundedRectangle =
Declarative<internal::RoundedRectangle, CheckerOr<common::pro::checker>>;
using Text = Declarative<internal::Text, CheckerOr<common::pro::checker>>;
}
}
#pragma once
#include "modern-qt/utility/painter/common.hh"
#include <qpainterpath.h>
namespace creeper::painter::internal {
struct EraseRectangle : public CommonProps {
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
painter.setBrush(Qt::black);
painter.setPen(Qt::NoPen);
painter.drawRect(rect());
painter.restore();
}
};
struct Rectangle : public CommonProps, ShapeProps {
auto operator()(qt::painter& painter) const noexcept {
painter.save();
const auto rectangle = qt::rect { origin, size };
painter.setBrush(container_color);
painter.setPen(Qt::NoPen);
painter.drawRect(rectangle);
if (outline_width > 0) {
const auto thickness = outline_width / 2;
const auto inliner = rectangle.adjusted(thickness, thickness, -thickness, -thickness);
painter.setPen({ outline_color, outline_width });
painter.setBrush(Qt::NoBrush);
painter.drawRect(inliner);
}
painter.restore();
}
};
struct RoundedRectangle : public CommonProps, ShapeProps {
double radius_tl = 0;
double radius_tr = 0;
double radius_bl = 0;
double radius_br = 0;
auto set_radiuses(double r) {
radius_tl = r;
radius_tr = r;
radius_bl = r;
radius_br = r;
}
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
const auto rect = qt::rect { origin, size };
const auto outline_shape =
make_rounded_rect_path(rect, radius_tl, radius_tr, radius_br, radius_bl);
painter.setPen(Qt::NoPen);
painter.setBrush(container_color);
painter.drawPath(outline_shape);
if (outline_width > 0) {
const auto thickness = outline_width;
const auto inliner_f = [=](double r) { return std::max(r - thickness / 2, 0.); };
const auto inliner_rect =
rect.adjusted(thickness / 2, thickness / 2, -thickness / 2, -thickness / 2);
const auto inliner_shape = make_rounded_rect_path(inliner_rect, inliner_f(radius_tl),
inliner_f(radius_tr), inliner_f(radius_br), inliner_f(radius_bl));
painter.setBrush(Qt::NoBrush);
painter.setPen({ outline_color, outline_width });
painter.drawPath(inliner_shape);
}
painter.restore();
}
static constexpr auto make_rounded_rect_path(
const qt::rect& rect, qreal tl, qreal tr, qreal br, qreal bl) noexcept -> QPainterPath {
auto path = QPainterPath {};
const auto max_radius = std::min(rect.width(), rect.height()) / 2.0;
const auto clamp = [&](qreal r) -> qreal {
return r < 0 ? max_radius : std::min(r, max_radius);
};
tl = clamp(tl);
tr = clamp(tr);
br = clamp(br);
bl = clamp(bl);
const auto Arc = [](qreal x, qreal y, qreal r,
int start_angle) -> std::tuple<qt::rect, int, int> {
return { qt::rect(x, y, 2 * r, 2 * r), start_angle, -90 };
};
path.moveTo(rect.topLeft() + qt::point(tl, 0));
path.lineTo(rect.topRight() - qt::point(tr, 0));
const auto [tr_rect, tr_start, tr_span] =
Arc(rect.topRight().x() - 2 * tr, rect.topRight().y(), tr, 90);
path.arcTo(tr_rect, tr_start, tr_span);
path.lineTo(rect.bottomRight() - qt::point(0, br));
const auto [br_rect, br_start, br_span] =
Arc(rect.bottomRight().x() - 2 * br, rect.bottomRight().y() - 2 * br, br, 0);
path.arcTo(br_rect, br_start, br_span);
path.lineTo(rect.bottomLeft() + qt::point(bl, 0));
const auto [bl_rect, bl_start, bl_span] =
Arc(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * bl, bl, 270);
path.arcTo(bl_rect, bl_start, bl_span);
path.lineTo(rect.topLeft() + qt::point(0, tl));
const auto [tl_rect, tl_start, tl_span] =
Arc(rect.topLeft().x(), rect.topLeft().y(), tl, 180);
path.arcTo(tl_rect, tl_start, tl_span);
path.closeSubpath();
return path;
}
};
struct Text : CommonProps {
qt::string text;
qt::font font;
qt::color color = Qt::black;
qt::text_option text_option;
qt::real scale = 1.;
auto operator()(qt::painter& painter) const noexcept {
painter.save();
painter.scale(scale, scale);
const auto origin_rect = rect();
const auto offset_x = origin_rect.x() * (1.0 - scale);
const auto center_y = origin_rect.y() + origin_rect.height() / 2.0;
const auto offset_y = center_y * (1.0 - scale);
painter.translate(offset_x, offset_y);
painter.setBrush(Qt::NoBrush);
painter.setPen(color);
painter.setFont(font);
const auto scaled_rect = qt::rect {
origin_rect.x() / scale,
origin_rect.y() / scale,
origin_rect.width() / scale,
origin_rect.height() / scale,
};
painter.drawText(scaled_rect, text, text_option);
painter.restore();
}
};
}
namespace creeper::painter {
/// Export Rounded Rectangle
using RadiusTL = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_tl = radius; }>;
using RadiusTR = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_tr = radius; }>;
using RadiusBL = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_bl = radius; }>;
using RadiusBR = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.radius_br = radius; }>;
using Radiuses = SetterProp<common::pro::Token, double,
[](auto& self, auto radius) { self.set_radiuses(radius); }>;
/// Export Text
using Text = DerivedProp<common::pro::Token, qt::string,
[](auto& self, const auto& text) { self.text = text; }>;
using Font = DerivedProp<common::pro::Token, qt::font,
[](auto& self, const auto& font) { self.font = font; }>;
using Color = DerivedProp<common::pro::Token, qt::color,
[](auto& self, const auto& color) { self.color = color; }>;
using Scale = SetterProp<common::pro::Token, qt::real,
[](auto& self, const auto& scale) { self.scale = scale; }>;
using TextOption = DerivedProp<common::pro::Token, qt::text_option,
[](auto& self, const auto& option) { self.text_option = option; }>;
namespace Paint {
using EraseRectangle = Declarative<internal::EraseRectangle, CheckerOr<common::pro::checker>>;
using Rectangle = Declarative<internal::Rectangle, CheckerOr<common::pro::checker>>;
using RoundedRectangle =
Declarative<internal::RoundedRectangle, CheckerOr<common::pro::checker>>;
using Text = Declarative<internal::Text, CheckerOr<common::pro::checker>>;
}
}