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 }