File indexing completed on 2025-02-02 04:11:33
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "keyframe_transition_widget.hpp" 0008 #include <QPaintEvent> 0009 #include <QPainter> 0010 #include <QMouseEvent> 0011 #include <QPainterPath> 0012 0013 #include "math/math.hpp" 0014 0015 #include <QDebug> 0016 0017 using namespace glaxnimate::gui; 0018 using namespace glaxnimate; 0019 0020 class KeyframeTransitionWidget::Private 0021 { 0022 public: 0023 model::KeyframeTransition* target = nullptr; 0024 int selected_handle = 0; 0025 int highlighted_handle = 0; 0026 int handle_radius = 8; 0027 double y_margin = 0; 0028 double y_margin_value = 0; 0029 QPoint drag_start_mouse; 0030 QPoint drag_start_handle; 0031 0032 bool hold() const 0033 { 0034 return target->hold(); 0035 } 0036 0037 const QPointF& point(int i) { return target->bezier().points()[i]; } 0038 0039 double map_coord(double coord, double size, double margin) 0040 { 0041 return margin + (size - 2 * margin) * coord; 0042 } 0043 0044 QPointF map_pt(const QPointF& p, int width, int height) 0045 { 0046 return QPointF( 0047 map_coord(p.x(), width, handle_radius), 0048 map_coord((1-p.y()), height, y_margin * height + handle_radius) 0049 ); 0050 } 0051 0052 QPointF mapped_point(int i, int width, int height) 0053 { 0054 return map_pt(point(i), width, height); 0055 } 0056 0057 double unmap_coord(double coord, double size, double margin, double bound_margin) 0058 { 0059 return qBound(0. - bound_margin, (coord - margin) / (size - 2.0 * margin), 1. + bound_margin); 0060 } 0061 0062 QPointF unmap_pt(const QPoint& p, int width, int height, bool clamp_y = false) 0063 { 0064 auto y = 1 - unmap_coord(p.y(), height, y_margin * height + handle_radius, y_margin_value); 0065 if ( clamp_y ) 0066 y = qBound(0., y, 1.); 0067 return QPointF{unmap_coord(p.x(), width, 0, 0), y}; 0068 } 0069 0070 }; 0071 0072 0073 KeyframeTransitionWidget::KeyframeTransitionWidget(QWidget* parent) 0074 : QWidget(parent), d(std::make_unique<Private>()) 0075 { 0076 setMouseTracking(true); 0077 setFocusPolicy(Qt::StrongFocus); 0078 } 0079 0080 KeyframeTransitionWidget::~KeyframeTransitionWidget() = default; 0081 0082 void KeyframeTransitionWidget::set_target(model::KeyframeTransition* kft) 0083 { 0084 d->target = kft; 0085 auto margin = math::max(-kft->before().y(), kft->after().y() - 1); 0086 if ( margin > 0 ) 0087 { 0088 d->y_margin_value = margin; 0089 d->y_margin = margin / (2 * margin + 1); 0090 } 0091 0092 update(); 0093 0094 if ( kft ) 0095 { 0096 Q_EMIT before_changed(kft->before_descriptive()); 0097 Q_EMIT after_changed(kft->after_descriptive()); 0098 } 0099 } 0100 0101 void KeyframeTransitionWidget::paintEvent(QPaintEvent*) 0102 { 0103 QPainter painter(this); 0104 0105 0106 QRect bounds = rect(); 0107 QRect center_rect = bounds.adjusted( 0108 d->handle_radius, 0109 d->handle_radius, 0110 -d->handle_radius, 0111 -d->handle_radius 0112 ); 0113 0114 QPalette::ColorGroup group = isEnabled() && d->target && !d->hold() ? QPalette::Active : QPalette::Disabled; 0115 painter.fillRect(bounds, palette().brush(group, QPalette::Window)); 0116 painter.fillRect(center_rect, palette().brush(group, QPalette::Base)); 0117 0118 if ( d->y_margin > 0 ) 0119 { 0120 painter.setPen(QPen(palette().brush(group, QPalette::Window), 2, Qt::DotLine)); 0121 int y = bounds.top() + d->y_margin * bounds.height() + d->handle_radius; 0122 painter.drawLine(bounds.left(), y, bounds.right(), y); 0123 y = bounds.bottom() - d->y_margin * bounds.height() - d->handle_radius; 0124 painter.drawLine(bounds.left(), y, bounds.right(), y); 0125 0126 } 0127 0128 if ( d->target ) 0129 { 0130 bool enabled = isEnabled() && !d->hold(); 0131 painter.setRenderHint(QPainter::Antialiasing); 0132 0133 QBrush fg = palette().brush(group, QPalette::Text); 0134 int w = width(); 0135 int h = height(); 0136 std::array<QPointF, 4> p = { 0137 d->mapped_point(0, w, h), 0138 d->mapped_point(1, w, h), 0139 d->mapped_point(2, w, h), 0140 d->mapped_point(3, w, h) 0141 }; 0142 0143 // Path 0144 QPainterPath pp(p[0]); 0145 pp.cubicTo(p[1], p[2], p[3]); 0146 painter.setBrush(Qt::NoBrush); 0147 painter.setPen(QPen(palette().brush(group, QPalette::Mid), 3)); 0148 painter.drawPath(pp); 0149 painter.setPen(QPen(fg, 1)); 0150 painter.drawPath(pp); 0151 0152 // Connect handles 0153 QPen high_pen(palette().brush(group, QPalette::Highlight), 2); 0154 QPen low_pen(QPen(palette().brush(group, QPalette::Text), 2)); 0155 painter.setBrush(Qt::NoBrush); 0156 painter.setPen(1 == d->selected_handle && enabled ? high_pen : low_pen); 0157 painter.drawLine(p[0], p[1]); 0158 painter.setPen(2 == d->selected_handle && enabled ? high_pen : low_pen); 0159 painter.drawLine(p[2], p[3]); 0160 0161 // Fixed handles 0162 painter.setBrush(fg); 0163 painter.setPen(Qt::NoPen); 0164 painter.drawEllipse(p[0], d->handle_radius, d->handle_radius); 0165 painter.drawEllipse(p[3], d->handle_radius, d->handle_radius); 0166 0167 // Draggable handles 0168 for ( int i = 1; i <= 2; i++ ) 0169 { 0170 if ( i == d->selected_handle && enabled ) 0171 { 0172 painter.setBrush(palette().brush(group, QPalette::Highlight)); 0173 painter.setPen(Qt::transparent); 0174 } 0175 else if ( i == d->highlighted_handle && enabled ) 0176 { 0177 painter.setBrush(palette().brush(group, QPalette::HighlightedText)); 0178 painter.setPen(QPen(palette().brush(group, QPalette::Highlight), 1)); 0179 } 0180 else 0181 { 0182 painter.setBrush(palette().brush(group, QPalette::Base)); 0183 painter.setPen(QPen(palette().brush(group, QPalette::Text), 1)); 0184 } 0185 painter.drawEllipse(p[i], d->handle_radius, d->handle_radius); 0186 } 0187 } 0188 } 0189 0190 void KeyframeTransitionWidget::mousePressEvent(QMouseEvent* event) 0191 { 0192 QWidget::mousePressEvent(event); 0193 if ( !d->target || d->hold() ) 0194 return; 0195 0196 if ( event->button() == Qt::LeftButton ) 0197 { 0198 event->accept(); 0199 d->selected_handle = d->highlighted_handle; 0200 if ( d->selected_handle ) 0201 { 0202 d->drag_start_mouse = event->pos(); 0203 d->drag_start_handle = d->map_pt(d->point(d->selected_handle), width(), height()).toPoint(); 0204 } 0205 update(); 0206 } 0207 } 0208 0209 void KeyframeTransitionWidget::mouseMoveEvent(QMouseEvent* event) 0210 { 0211 QWidget::mouseMoveEvent(event); 0212 if ( !d->target || d->hold() ) 0213 return; 0214 0215 if ( d->selected_handle ) 0216 { 0217 QPoint pos = event->pos() - d->drag_start_mouse + d->drag_start_handle; 0218 0219 bool clamp_y = event->modifiers() & Qt::ControlModifier; 0220 0221 if ( d->selected_handle == 1 ) 0222 { 0223 d->target->set_before(d->unmap_pt(pos, width(), height(), clamp_y)); 0224 Q_EMIT before_changed(d->target->before_descriptive()); 0225 } 0226 else if ( d->selected_handle == 2 ) 0227 { 0228 d->target->set_after(d->unmap_pt(pos, width(), height(), clamp_y)); 0229 Q_EMIT after_changed(d->target->after_descriptive()); 0230 } 0231 update(); 0232 } 0233 else if ( isEnabled() ) 0234 { 0235 event->accept(); 0236 QPointF p = d->unmap_pt(event->pos(), width(), height()); 0237 double d1 = math::length_squared(d->point(1) - p); 0238 double d2 = math::length_squared(d->point(2) - p); 0239 d->highlighted_handle = d1 < d2 ? 1 : 2; 0240 update(); 0241 } 0242 } 0243 0244 void KeyframeTransitionWidget::mouseReleaseEvent(QMouseEvent* event) 0245 { 0246 QWidget::mouseReleaseEvent(event); 0247 if ( !d->target ) 0248 return; 0249 0250 if ( event->button() == Qt::LeftButton ) 0251 { 0252 event->accept(); 0253 d->selected_handle = 0; 0254 update(); 0255 } 0256 } 0257 0258 void KeyframeTransitionWidget::focusInEvent(QFocusEvent* event) 0259 { 0260 QWidget::focusInEvent(event); 0261 } 0262 0263 void KeyframeTransitionWidget::focusOutEvent(QFocusEvent* event) 0264 { 0265 QWidget::focusOutEvent(event); 0266 d->selected_handle = 0; 0267 update(); 0268 } 0269 0270 void KeyframeTransitionWidget::leaveEvent(QEvent* event) 0271 { 0272 QWidget::leaveEvent(event); 0273 if ( !d->selected_handle ) 0274 { 0275 d->highlighted_handle = 0; 0276 update(); 0277 } 0278 } 0279 0280 model::KeyframeTransition * KeyframeTransitionWidget::target() const 0281 { 0282 return d->target; 0283 } 0284 0285 QSize KeyframeTransitionWidget::sizeHint() const 0286 { 0287 return QSize(300, 200); 0288 } 0289 0290 void glaxnimate::gui::KeyframeTransitionWidget::set_y_margin(double margin) 0291 { 0292 d->y_margin = margin; 0293 update(); 0294 } 0295