287 lines
11 KiB
C++
287 lines
11 KiB
C++
#pragma once
|
|
|
|
#include "icon-button.hh"
|
|
|
|
#include "modern-qt/utility/animation/animatable.hh"
|
|
#include "modern-qt/utility/animation/state/pid.hh"
|
|
#include "modern-qt/utility/animation/state/spring.hh"
|
|
#include "modern-qt/utility/animation/transition.hh"
|
|
#include "modern-qt/utility/animation/water-ripple.hh"
|
|
#include "modern-qt/utility/material-icon.hh"
|
|
#include "modern-qt/utility/painter/helper.hh"
|
|
|
|
using namespace creeper::icon_button::internal;
|
|
|
|
constexpr auto kHoverOpacity = double { 0.1 };
|
|
constexpr auto kWaterOpacity = double { 0.4 };
|
|
constexpr auto kWidthRatio = double { 1.25 };
|
|
constexpr auto kOutlineWidth = double { 1.5 };
|
|
constexpr auto kSquareRatio = double { 0.5 };
|
|
|
|
constexpr double kp = 15.0, ki = 0.0, kd = 0.0;
|
|
constexpr auto kSpringK = double { 400.0 };
|
|
constexpr auto kSpringD = double { 15.0 };
|
|
|
|
constexpr auto kThreshold1D = double { 1e-1 };
|
|
constexpr auto kWaterSpeed = double { 5.0 };
|
|
|
|
struct IconButton::Impl {
|
|
|
|
bool is_hovered = false;
|
|
|
|
QString font_icon {};
|
|
|
|
Types types { Types::DEFAULT };
|
|
Shape shape { Shape::DEFAULT_ROUND };
|
|
Color color { Color::DEFAULT_FILLED };
|
|
Width width { Width::DEFAULT };
|
|
|
|
QColor container_color = Qt::white;
|
|
QColor container_color_unselected = Qt::white;
|
|
QColor container_color_selected = Qt::white;
|
|
|
|
QColor outline_color = Qt::gray;
|
|
QColor outline_color_unselected = Qt::gray;
|
|
QColor outline_color_selected = Qt::gray;
|
|
|
|
QColor icon_color = Qt::black;
|
|
QColor icon_color_unselected = Qt::black;
|
|
QColor icon_color_selected = Qt::black;
|
|
|
|
QColor hover_color = Qt::gray;
|
|
QColor hover_color_unselected = Qt::gray;
|
|
QColor hover_color_selected = Qt::gray;
|
|
|
|
QColor water_color = Qt::gray;
|
|
|
|
Animatable animatable;
|
|
WaterRippleRenderer water_ripple;
|
|
|
|
std::unique_ptr<TransitionValue<SpringState<double>>> now_container_radius;
|
|
std::unique_ptr<TransitionValue<PidState<Eigen::Vector4d>>> now_color_container;
|
|
std::unique_ptr<TransitionValue<PidState<Eigen::Vector4d>>> now_color_icon;
|
|
std::unique_ptr<TransitionValue<PidState<Eigen::Vector4d>>> now_color_outline;
|
|
|
|
explicit Impl(IconButton& self) noexcept
|
|
: animatable { self }
|
|
, water_ripple { animatable, kWaterSpeed } {
|
|
|
|
{
|
|
auto state = std::make_shared<SpringState<double>>();
|
|
|
|
state->config.epsilon = kThreshold1D;
|
|
state->config.k = kSpringK;
|
|
state->config.d = kSpringD;
|
|
|
|
now_container_radius = make_transition(animatable, std::move(state));
|
|
}
|
|
{
|
|
constexpr auto make_state = [] {
|
|
auto state = std::make_shared<PidState<Eigen::Vector4d>>();
|
|
|
|
state->config.kp = kp;
|
|
state->config.ki = ki;
|
|
state->config.kd = kd;
|
|
return state;
|
|
};
|
|
now_color_container = make_transition(animatable, make_state());
|
|
now_color_icon = make_transition(animatable, make_state());
|
|
now_color_outline = make_transition(animatable, make_state());
|
|
}
|
|
|
|
QObject::connect(&self, &IconButton::clicked, [this, &self] {
|
|
if (types == Types::DEFAULT) {
|
|
const auto center_point = self.mapFromGlobal(QCursor::pos());
|
|
const auto max_distance = std::max(self.width(), self.height());
|
|
water_ripple.clicked(center_point, max_distance);
|
|
}
|
|
|
|
toggle_status();
|
|
update_animation_status(self);
|
|
});
|
|
}
|
|
|
|
auto enter_event(IconButton& self, const QEvent& event) {
|
|
self.setCursor(Qt::PointingHandCursor);
|
|
is_hovered = true;
|
|
}
|
|
|
|
auto leave_event(IconButton& self, const QEvent& event) { is_hovered = false; }
|
|
|
|
auto paint_event(IconButton& self, const QPaintEvent& event) {
|
|
// TODO: 做计算数据缓存优化,特别是 Resize 相关的计算
|
|
const auto icon = self.icon();
|
|
|
|
const auto color_container = from_vector4(*now_color_container);
|
|
const auto color_icon = from_vector4(*now_color_icon);
|
|
const auto color_outline = from_vector4(*now_color_outline);
|
|
|
|
const auto hover_color = is_hovered ? get_hover_color() : Qt::transparent;
|
|
|
|
const auto container_radius = *now_container_radius;
|
|
const auto container_rect = container_rectangle(self);
|
|
|
|
auto clip_path = QPainterPath {};
|
|
clip_path.addRoundedRect(container_rect, container_radius, container_radius);
|
|
|
|
auto renderer = QPainter { &self };
|
|
util::PainterHelper { renderer }
|
|
.set_render_hint(QPainter::Antialiasing)
|
|
.rounded_rectangle(color_container, color_outline, kOutlineWidth, container_rect,
|
|
container_radius, container_radius)
|
|
.apply(water_ripple.renderer(clip_path, water_color))
|
|
.simple_text(font_icon, self.font(), color_icon, container_rect, Qt::AlignCenter)
|
|
.rounded_rectangle(
|
|
hover_color, Qt::transparent, 0, container_rect, container_radius, container_radius)
|
|
.done();
|
|
}
|
|
|
|
auto set_types_type(IconButton& self, Types types) {
|
|
this->types = types;
|
|
update_animation_status(self);
|
|
}
|
|
|
|
auto set_color_type(IconButton& self, Color color) {
|
|
this->color = color;
|
|
update_animation_status(self);
|
|
}
|
|
|
|
auto set_shape_type(IconButton& self, Shape shape) {
|
|
this->shape = shape;
|
|
update_animation_status(self);
|
|
}
|
|
|
|
auto set_width_type(IconButton& self, Width width) {
|
|
this->width = width;
|
|
update_animation_status(self);
|
|
}
|
|
|
|
auto set_color_scheme(IconButton& self, const ColorScheme& scheme) {
|
|
switch (color) {
|
|
case Color::DEFAULT_FILLED:
|
|
container_color = scheme.primary;
|
|
icon_color = scheme.on_primary;
|
|
outline_color = Qt::transparent;
|
|
|
|
container_color_unselected = scheme.surface_container_high;
|
|
icon_color_unselected = scheme.primary;
|
|
outline_color_unselected = Qt::transparent;
|
|
|
|
container_color_selected = scheme.primary;
|
|
icon_color_selected = scheme.on_primary;
|
|
outline_color_selected = Qt::transparent;
|
|
break;
|
|
case Color::TONAL:
|
|
container_color = scheme.secondary_container;
|
|
icon_color = scheme.on_secondary_container;
|
|
outline_color = Qt::transparent;
|
|
|
|
container_color_unselected = scheme.surface_container_high;
|
|
icon_color_unselected = scheme.surface_variant;
|
|
outline_color_unselected = Qt::transparent;
|
|
|
|
container_color_selected = scheme.secondary_container;
|
|
icon_color_selected = scheme.on_secondary_container;
|
|
outline_color_selected = Qt::transparent;
|
|
break;
|
|
case Color::OUTLINED:
|
|
container_color = Qt::transparent;
|
|
outline_color = scheme.outline_variant;
|
|
icon_color = scheme.on_surface_variant;
|
|
|
|
container_color_unselected = Qt::transparent;
|
|
outline_color_unselected = scheme.outline_variant;
|
|
icon_color_unselected = scheme.surface_variant;
|
|
|
|
container_color_selected = scheme.inverse_surface;
|
|
outline_color_selected = scheme.inverse_surface;
|
|
icon_color_selected = scheme.inverse_on_surface;
|
|
break;
|
|
case Color::STANDARD:
|
|
container_color = Qt::transparent;
|
|
outline_color = Qt::transparent;
|
|
icon_color = scheme.on_surface_variant;
|
|
|
|
container_color_unselected = Qt::transparent;
|
|
outline_color_unselected = Qt::transparent;
|
|
icon_color_unselected = scheme.on_surface_variant;
|
|
|
|
container_color_selected = Qt::transparent;
|
|
outline_color_selected = Qt::transparent;
|
|
icon_color_selected = scheme.primary;
|
|
break;
|
|
}
|
|
|
|
hover_color = icon_color;
|
|
hover_color.setAlphaF(kHoverOpacity);
|
|
|
|
hover_color_selected = icon_color_selected;
|
|
hover_color_selected.setAlphaF(kHoverOpacity);
|
|
|
|
hover_color_unselected = icon_color_unselected;
|
|
hover_color_unselected.setAlphaF(kHoverOpacity);
|
|
|
|
water_color = icon_color;
|
|
water_color.setAlphaF(kWaterOpacity);
|
|
|
|
update_animation_status(self);
|
|
}
|
|
|
|
auto load_theme_manager(IconButton& self, ThemeManager& manager) {
|
|
manager.append_handler(&self, [this, &self](const ThemeManager& manager) {
|
|
set_color_scheme(self, manager.color_scheme());
|
|
});
|
|
}
|
|
|
|
private:
|
|
auto update_animation_status(IconButton& self) -> void {
|
|
|
|
const auto container_color_target = (types == Types::DEFAULT) ? container_color : //
|
|
(types == Types::TOGGLE_SELECTED) ? container_color_selected
|
|
: container_color_unselected;
|
|
now_color_container->transition_to(from_color(container_color_target));
|
|
|
|
const auto icon_color_target = (types == Types::DEFAULT) ? icon_color : //
|
|
(types == Types::TOGGLE_SELECTED) ? icon_color_selected
|
|
: icon_color_unselected;
|
|
now_color_icon->transition_to(from_color(icon_color_target));
|
|
|
|
const auto outline_color_target //
|
|
= (types == Types::DEFAULT) ? outline_color
|
|
: (types == Types::TOGGLE_SELECTED) ? outline_color_selected
|
|
: outline_color_unselected;
|
|
now_color_outline->transition_to(from_color(outline_color_target));
|
|
|
|
const auto rectangle = container_rectangle(self);
|
|
const auto radius_round = std::min<double>(rectangle.width(), rectangle.height()) / 2.;
|
|
const auto radius_target = (types == Types::TOGGLE_SELECTED || shape == Shape::SQUARE)
|
|
? radius_round * kSquareRatio
|
|
: radius_round * 1.0;
|
|
now_container_radius->transition_to(radius_target);
|
|
}
|
|
|
|
auto get_hover_color() const noexcept -> QColor {
|
|
switch (types) {
|
|
case Types::DEFAULT:
|
|
return hover_color;
|
|
case Types::TOGGLE_UNSELECTED:
|
|
return hover_color_unselected;
|
|
case Types::TOGGLE_SELECTED:
|
|
return hover_color_selected;
|
|
}
|
|
return { /* 不可能到达的彼岸 */ };
|
|
}
|
|
|
|
auto toggle_status() -> void {
|
|
if (types == Types::TOGGLE_UNSELECTED) types = Types::TOGGLE_SELECTED;
|
|
else if (types == Types::TOGGLE_SELECTED) types = Types::TOGGLE_UNSELECTED;
|
|
}
|
|
|
|
// 设计指南上的大小全是固定的,十分不自由,故转成比例
|
|
auto container_rectangle(IconButton& self) -> QRectF {
|
|
return (width == Width::DEFAULT) ? (extract_rect(self.rect(), 1, 1))
|
|
: (width == Width::NARROW) ? (extract_rect(self.rect(), 1, kWidthRatio))
|
|
: (extract_rect(self.rect(), kWidthRatio, 1));
|
|
}
|
|
};
|