File indexing completed on 2025-02-02 04:11:04
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 "gradient.hpp" 0008 0009 #include <QPainter> 0010 0011 #include "model/document.hpp" 0012 #include "model/assets/assets.hpp" 0013 0014 #include "command/object_list_commands.hpp" 0015 #include "command/animation_commands.hpp" 0016 #include "command/undo_macro_guard.hpp" 0017 0018 #include "utils/sort_gradient.hpp" 0019 0020 0021 using namespace glaxnimate; 0022 0023 0024 GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::GradientColors) 0025 GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Gradient) 0026 0027 template<> 0028 std::optional<QGradientStops> glaxnimate::model::detail::variant_cast<QGradientStops>(const QVariant& val) 0029 { 0030 if ( !val.canConvert<QGradientStops>() ) 0031 { 0032 if ( val.canConvert<QVariantList>() ) 0033 { 0034 QGradientStops stops; 0035 for ( auto stop : val.toList() ) 0036 { 0037 if ( stop.canConvert<QGradientStop>() ) 0038 { 0039 stops.push_back(stop.value<QGradientStop>()); 0040 } 0041 else if ( stop.canConvert<QVariantList>() ) 0042 { 0043 auto sl = stop.toList(); 0044 if ( sl.size() == 2 && sl[0].canConvert<double>() && sl[1].canConvert<QColor>() ) 0045 stops.push_back({sl[0].toDouble(), sl[1].value<QColor>()}); 0046 } 0047 } 0048 return stops; 0049 } 0050 return {}; 0051 } 0052 0053 QVariant converted = val; 0054 #if QT_VERSION_MAJOR < 6 0055 if ( !converted.convert(qMetaTypeId<QGradientStops>()) ) 0056 #else 0057 if ( !converted.convert(QMetaType::fromType<QGradientStops>()) ) 0058 #endif 0059 return {}; 0060 return converted.value<QGradientStops>(); 0061 } 0062 0063 0064 template<> 0065 QGradientStops math::lerp<QGradientStops>(const QGradientStops& a, const QGradientStops& b, double factor) 0066 { 0067 if ( a.size() != b.size() ) 0068 return factor >= 1 ? b : a; 0069 0070 QGradientStops mix; 0071 mix.reserve(a.size()); 0072 for ( int i = 0; i < a.size(); i++ ) 0073 mix.push_back({ 0074 math::lerp(a[i].first, b[i].first, factor), 0075 math::lerp(a[i].second, b[i].second, factor) 0076 }); 0077 0078 return mix; 0079 } 0080 0081 QString glaxnimate::model::GradientColors::type_name_human() const 0082 { 0083 return i18n("Gradient"); 0084 } 0085 0086 0087 QIcon glaxnimate::model::GradientColors::instance_icon() const 0088 { 0089 QPixmap icon(32, 32); 0090 QPainter p(&icon); 0091 QLinearGradient g(0, 0, icon.width(), 0); 0092 g.setStops(colors.get()); 0093 p.fillRect(icon.rect(), g); 0094 return icon; 0095 } 0096 0097 bool glaxnimate::model::GradientColors::remove_if_unused(bool clean_lists) 0098 { 0099 if ( clean_lists && users().empty() ) 0100 { 0101 document()->push_command(new command::RemoveObject( 0102 this, 0103 &document()->assets()->gradient_colors->values 0104 )); 0105 return true; 0106 } 0107 0108 return false; 0109 } 0110 0111 static QVariant split_gradient(QGradientStops colors, int index, float factor, const QColor& new_color) 0112 { 0113 int before = index; 0114 int after = index+1; 0115 0116 if ( after >= colors.size() ) 0117 { 0118 before = colors.size() - 2; 0119 after = colors.size() - 1; 0120 } 0121 0122 colors.push_back({ 0123 math::lerp(colors[before].first, colors[after].first, factor), 0124 new_color.isValid() ? new_color : math::lerp(colors[before].second, colors[after].second, 0.5) 0125 }); 0126 0127 utils::sort_gradient(colors); 0128 return QVariant::fromValue(colors); 0129 } 0130 0131 void glaxnimate::model::GradientColors::split_segment(int segment_index, float factor, const QColor& new_color) 0132 { 0133 command::UndoMacroGuard guard(i18n("Add color to %1", name.get()), document()); 0134 if ( segment_index < 0 ) 0135 segment_index = 0; 0136 0137 if ( !colors.animated() ) 0138 { 0139 colors.set_undoable(split_gradient(colors.get(), segment_index, factor, new_color)); 0140 } 0141 else 0142 { 0143 for ( const auto& kf : colors ) 0144 document()->push_command(new command::SetKeyframe( 0145 &colors, kf.time(), split_gradient(kf.get(), segment_index, factor, new_color), true 0146 )); 0147 } 0148 } 0149 0150 void glaxnimate::model::GradientColors::remove_stop(int index) 0151 { 0152 command::UndoMacroGuard guard(i18n("Remove color from %1", name.get()), document()); 0153 0154 if ( index < 0 ) 0155 index = 0; 0156 0157 if ( !colors.animated() ) 0158 { 0159 auto stops = colors.get(); 0160 stops.erase(std::min(stops.begin() + index, stops.end())); 0161 colors.set_undoable(QVariant::fromValue(stops)); 0162 } 0163 else 0164 { 0165 for ( const auto& kf : colors ) 0166 { 0167 auto stops = kf.get(); 0168 stops.erase(std::min(stops.begin() + index, stops.end())); 0169 0170 document()->push_command(new command::SetKeyframe( 0171 &colors, kf.time(), QVariant::fromValue(stops), true 0172 )); 0173 } 0174 } 0175 } 0176 0177 std::vector<glaxnimate::model::DocumentNode *> glaxnimate::model::Gradient::valid_refs() const 0178 { 0179 return document()->assets()->gradient_colors->values.valid_reference_values(false); 0180 } 0181 0182 bool glaxnimate::model::Gradient::is_valid_ref ( glaxnimate::model::DocumentNode* node ) const 0183 { 0184 return document()->assets()->gradient_colors->values.is_valid_reference_value(node, true); 0185 } 0186 0187 void glaxnimate::model::Gradient::on_ref_visual_changed() 0188 { 0189 Q_EMIT style_changed(); 0190 } 0191 0192 void glaxnimate::model::Gradient::on_ref_changed ( glaxnimate::model::GradientColors* new_ref, glaxnimate::model::GradientColors* old_ref ) 0193 { 0194 if ( old_ref ) 0195 disconnect(old_ref, &GradientColors::colors_changed, this, &Gradient::on_ref_visual_changed); 0196 0197 if ( new_ref ) 0198 { 0199 connect(new_ref, &GradientColors::colors_changed, this, &Gradient::on_ref_visual_changed); 0200 } 0201 else 0202 { 0203 detach(); 0204 } 0205 0206 colors_changed_from(old_ref, new_ref); 0207 } 0208 0209 QString glaxnimate::model::Gradient::type_name_human() const 0210 { 0211 return i18n("%1 Gradient", gradient_type_name(type.get())); 0212 } 0213 0214 QBrush glaxnimate::model::Gradient::brush_style ( glaxnimate::model::FrameTime t ) const 0215 { 0216 if ( type.get() == Radial ) 0217 { 0218 QRadialGradient g(start_point.get_at(t), radius(t), highlight.get_at(t)); 0219 if ( colors.get() ) 0220 g.setStops(colors->colors.get_at(t)); 0221 g.setSpread(QGradient::PadSpread); 0222 return g; 0223 } 0224 else if ( type.get() == Conical ) 0225 { 0226 auto start = start_point.get_at(t); 0227 auto end = end_point.get_at(t); 0228 auto angle = -math::rad2deg(math::atan2(end.y() - start.y(), end.x() - start.x())); 0229 QConicalGradient g(start, angle); 0230 if ( colors.get() ) 0231 g.setStops(colors->colors.get_at(t)); 0232 return g; 0233 } 0234 else 0235 { 0236 QLinearGradient g(start_point.get_at(t), end_point.get_at(t)); 0237 if ( colors.get() ) 0238 g.setStops(colors->colors.get_at(t)); 0239 g.setSpread(QGradient::PadSpread); 0240 return g; 0241 } 0242 } 0243 0244 QBrush glaxnimate::model::Gradient::constrained_brush_style(FrameTime t, const QRectF& bounds) const 0245 { 0246 if ( type.get() == Radial ) 0247 { 0248 QRadialGradient g(bounds.center(), bounds.width() / 2); 0249 if ( colors.get() ) 0250 g.setStops(colors->colors.get_at(t)); 0251 return g; 0252 } 0253 else if ( type.get() == Conical ) 0254 { 0255 QConicalGradient g(bounds.center(), 02); 0256 if ( colors.get() ) 0257 g.setStops(colors->colors.get_at(t)); 0258 return g; 0259 } 0260 else 0261 { 0262 QLinearGradient g(bounds.topLeft(), bounds.topRight()); 0263 if ( colors.get() ) 0264 g.setStops(colors->colors.get_at(t)); 0265 return g; 0266 } 0267 } 0268 0269 void glaxnimate::model::Gradient::fill_icon(QPixmap& icon) const 0270 { 0271 QPainter p(&icon); 0272 p.fillRect(icon.rect(), constrained_brush_style(time(), icon.rect())); 0273 } 0274 0275 qreal glaxnimate::model::Gradient::radius(glaxnimate::model::FrameTime t) const 0276 { 0277 return math::length(start_point.get_at(t) - end_point.get_at(t)); 0278 } 0279 0280 QString glaxnimate::model::Gradient::gradient_type_name(GradientType t) 0281 { 0282 switch ( t ) 0283 { 0284 case Linear: 0285 return i18n("Linear"); 0286 case Radial: 0287 return i18n("Radial"); 0288 case Conical: 0289 return i18n("Conical"); 0290 } 0291 0292 return {}; 0293 } 0294 0295 void glaxnimate::model::Gradient::on_property_changed(const glaxnimate::model::BaseProperty*, const QVariant&) 0296 { 0297 Q_EMIT style_changed(); 0298 } 0299 0300 bool glaxnimate::model::Gradient::remove_if_unused(bool) 0301 { 0302 if ( users().empty() ) 0303 { 0304 colors.set_undoable(QVariant::fromValue((glaxnimate::model::GradientColors*)nullptr)); 0305 document()->push_command(new command::RemoveObject( 0306 this, 0307 &document()->assets()->gradients->values 0308 )); 0309 return true; 0310 } 0311 return false; 0312 }