#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 #include #include /// 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>(); 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(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>> position; Slider& self; };