Files
ts-qt/modern-qt/widget/sliders.impl.hh
2025-10-20 00:32:01 +08:00

255 lines
8.8 KiB
C++

#include "sliders.hh"
#include "modern-qt/utility/animation/animatable.hh"
#include "modern-qt/utility/animation/state/pid.hh"
#include "modern-qt/utility/animation/transition.hh"
#include "modern-qt/utility/painter/helper.hh"
#include <qevent.h>
#include <qnamespace.h>
#include <qpainter.h>
/// TODO:
/// [ ] Adapt other directions
/// [ ] Add Disable status
/// [ ] Add Inset icon
/// [ ] Add Stops
/// [ ] Add Value indicator
using namespace creeper::slider::internal;
struct Slider::Impl {
public:
explicit Impl(Slider& self) noexcept
: self { self }
, animatable { self } {
// Transition For Handle Position
{
auto state = std::make_shared<PidState<double>>();
state->config.kp = 20.0;
state->config.epsilon = 1e-4;
position = make_transition(animatable, std::move(state));
}
}
auto set_direction(Qt::ArrowType direction) noexcept -> void {
this->direction = direction;
self.update();
}
auto set_color_specs(const ColorSpecs& color_specs) noexcept -> void {
this->color_specs = color_specs;
self.update();
}
auto set_measurements(const Measurements& measurements) noexcept -> void {
this->measurements = measurements;
self.update();
}
void set_color_scheme(const ColorScheme& scheme) {
// Alpha 97 (约 38%): 用于禁用状态的文本、图标等前景元素 (Text/Icon).
auto on_surface_disabled_foreground = scheme.on_surface;
on_surface_disabled_foreground.setAlpha(97);
// Alpha 31 (约 12%): 用于禁用状态的轨道、填充、容器等背景元素 (Container/Track).
auto on_surface_disabled_container = scheme.on_surface;
on_surface_disabled_container.setAlpha(31);
// --- 启用 (Enabled) 状态映射 ---
auto& enabled = color_specs.enabled;
// 1. 值指示器 (气泡)
enabled.value_indicator = scheme.inverse_surface;
enabled.value_text = scheme.inverse_on_surface;
// 2. 停止点指示器 (Stop Indicators)
enabled.stop_indicator_active = scheme.primary;
enabled.stop_indicator_inactive = scheme.secondary_container;
// 3. 轨道 (Track)
enabled.track_active = scheme.primary;
enabled.track_inactive = scheme.secondary_container;
// 4. 拖动把手 (Handle)
enabled.handle = scheme.primary;
// --- 禁用 (Disabled) 状态映射 ---
auto& disabled = color_specs.disabled;
// 1. 值指示器 (气泡) - 气泡本身被禁用时,其背景和文字也应是禁用色
disabled.value_indicator = on_surface_disabled_container;
disabled.value_text = on_surface_disabled_foreground;
// 2. 停止点指示器
disabled.stop_indicator_active = on_surface_disabled_container;
disabled.stop_indicator_inactive = on_surface_disabled_container;
// 3. 轨道
disabled.track_active = on_surface_disabled_container;
disabled.track_inactive = on_surface_disabled_container;
// 4. 拖动把手
disabled.handle = on_surface_disabled_container;
}
auto load_theme_manager(ThemeManager& manager) { manager.append_handler(&self, self); }
auto set_progress(double progress, bool animatable = true) noexcept {
this->progress = std::clamp(progress, 0.0, 1.0);
if (animatable) {
this->position->transition_to(progress);
} else {
this->position->snap_to(progress), self.update();
}
}
auto get_progress() const noexcept { return progress; }
public:
auto paint_event(QPaintEvent*) -> void {
const auto& color = enabled ? color_specs.enabled : color_specs.disabled;
// TODO: Develop some util to simplify those calculating
const auto handle_spacing = double { 1.5 * measurements.handle_width };
const auto common_radius = double { 0.5 * measurements.handle_width };
// Handle shape
const auto handle_color = color.handle;
const auto handle_thickness = measurements.handle_width;
const auto handle_length = measurements.handle_height;
const auto handle_radius = 0.5 * handle_thickness;
const auto handle_groove = self.width() - 2 * handle_thickness;
const auto handle_center = is_horizontal()
? QPointF { handle_thickness + *position * handle_groove, 0.5 * self.height() }
: QPointF { 0.5 * self.width(), handle_thickness + *position * handle_groove };
const auto handle_thickness_real = pressed ? 0.5 * handle_thickness : handle_thickness;
const auto handle_w = is_horizontal() ? handle_thickness_real : handle_length;
const auto handle_h = is_horizontal() ? handle_length : handle_thickness_real;
const auto handle_rectangle = QRectF {
handle_center.x() - 0.5 * handle_w,
handle_center.y() - 0.5 * handle_h,
handle_w,
handle_h,
};
// Outline center of 4 sides
const auto center_l = QPointF { 0.0 * self.width(), 0.5 * self.height() };
const auto center_r = QPointF { 1.0 * self.width(), 0.5 * self.height() };
const auto center_t = QPointF { 0.5 * self.width(), 0.0 * self.height() };
const auto center_b = QPointF { 0.5 * self.width(), 1.0 * self.height() };
// Track shape
const auto half_h = measurements.track_height / 2.;
const auto track_1 = is_horizontal()
? QRectF { center_l + QPointF { 0, -half_h },
handle_center + QPointF { -handle_spacing, +half_h } }
: QRectF { center_t + QPointF { -half_h, 0 },
handle_center + QPointF { +half_h, -handle_spacing } };
const auto track_2 = is_horizontal()
? QRectF { handle_center + QPointF { +handle_spacing, -half_h },
center_r + QPointF { 0, +half_h } }
: QRectF { handle_center + QPointF { -half_h, +handle_spacing },
center_b + QPointF { +half_h, 0 } };
const auto track_color_1 = color.track_active;
const auto track_color_2 = color.track_inactive;
const auto track_shape = measurements.track_shape;
// Stop Indicator
auto painter = QPainter { &self };
util::PainterHelper { painter }
.set_render_hint(QPainter::Antialiasing)
// Track Part 1
.rounded_rectangle(track_color_1, Qt::transparent, 0, track_1, track_shape,
common_radius, common_radius, track_shape)
// Track Part 2
.rounded_rectangle(track_color_2, Qt::transparent, 0, track_2, common_radius,
track_shape, track_shape, common_radius)
// Stop Indicator
// TODO:
// Handle Shape
.rounded_rectangle(
handle_color, Qt::transparent, 0, handle_rectangle, handle_radius, handle_radius)
// Done
.done();
}
auto mouse_release_event(QMouseEvent* event) noexcept -> void {
if (!enabled) return;
pressed = false;
update_progress(event->pos());
emit self.signal_value_change_finished(progress);
}
auto mouse_press_event(QMouseEvent* event) noexcept -> void {
if (!enabled) return;
pressed = true;
update_progress(event->pos());
emit self.signal_value_change(progress);
}
auto mouse_move_event(QMouseEvent* event) noexcept -> void {
if (!enabled) return;
update_progress(event->pos());
emit self.signal_value_change(progress);
}
private:
auto is_horizontal() const noexcept -> bool {
return direction == Qt::RightArrow || direction == Qt::LeftArrow;
}
auto update_progress(const QPoint& point) noexcept -> void {
const auto w = self.width();
const auto h = self.height();
const auto x = point.x();
const auto y = point.y();
auto spindle_len = int {};
auto spindle_pos = int {};
const auto thickness = measurements.handle_width;
if (!is_horizontal()) {
spindle_len = h - 2 * thickness;
spindle_pos = y - 1 * thickness;
} else {
spindle_len = w - 2 * thickness;
spindle_pos = x - 1 * thickness;
}
progress = static_cast<double>(spindle_pos) / spindle_len;
progress = std::clamp(progress, 0., 1.);
position->transition_to(progress);
}
private:
double progress = 0.0;
uint steps = 0;
bool enabled = true;
bool pressed = false;
Qt::ArrowType direction = Qt::RightArrow;
ColorSpecs color_specs = ColorSpecs {};
Measurements measurements = Measurements::Xs();
Animatable animatable;
std::unique_ptr<TransitionValue<PidState<double>>> position;
Slider& self;
};