File indexing completed on 2025-02-02 04:11:32

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 "stroke_style_widget.hpp"
0008 #include "ui_stroke_style_widget.h"
0009 
0010 #include <QPainter>
0011 #include <QButtonGroup>
0012 #include <QtMath>
0013 #include <QStyleOptionFrame>
0014 #include <QPointer>
0015 
0016 #include "glaxnimate_app.hpp"
0017 #include "app/settings/settings.hpp"
0018 #include "model/document.hpp"
0019 #include "command/animation_commands.hpp"
0020 #include "utils/pseudo_mutex.hpp"
0021 
0022 using namespace glaxnimate::gui;
0023 using namespace glaxnimate;
0024 
0025 class StrokeStyleWidget::Private
0026 {
0027 public:
0028     Qt::PenCapStyle cap;
0029     Qt::PenJoinStyle join;
0030     Ui::StrokeStyleWidget ui;
0031     QButtonGroup group_cap;
0032     QButtonGroup group_join;
0033     bool dark_theme = false;
0034     QPalette::ColorRole background = QPalette::Base;
0035     utils::PseudoMutex updating;
0036     int stop = -1;
0037     model::Stroke* current_target = nullptr;
0038     std::vector<model::Styler*> targets = {};
0039 
0040 
0041     bool can_update_target()
0042     {
0043         for ( auto target : targets )
0044         {
0045             if ( !target->docnode_locked_recursive() )
0046                 return true;
0047         }
0048 
0049         return false;
0050     }
0051 
0052     void update_background(const QColor& color)
0053     {
0054         background = QPalette::Base;
0055         qreal val = color.valueF();
0056         qreal sat = color.saturationF();
0057 
0058         bool swap = val > 0.65 && sat < 0.5;
0059         if ( dark_theme )
0060             swap = !swap;
0061 
0062         if ( swap )
0063             background = QPalette::Text;
0064     }
0065 
0066     void update_from_target()
0067     {
0068         if ( current_target && !updating )
0069         {
0070             auto lock = updating.get_lock();
0071             ui.spin_stroke_width->setValue(current_target->width.get());
0072             set_cap_style(current_target->cap.get());
0073             set_join_style(current_target->join.get());
0074             ui.spin_miter->setValue(current_target->miter_limit.get());
0075 
0076             ui.color_selector->from_styler(current_target, stop);
0077         }
0078     }
0079 
0080     void set_cap_style(model::Stroke::Cap cap)
0081     {
0082         this->cap = Qt::PenCapStyle(cap);
0083 
0084         switch ( cap )
0085         {
0086             case model::Stroke::ButtCap:
0087                 ui.button_cap_butt->setChecked(true);
0088                 break;
0089             case model::Stroke::RoundCap:
0090                 ui.button_cap_round->setChecked(true);
0091                 break;
0092             case model::Stroke::SquareCap:
0093                 ui.button_cap_square->setChecked(true);
0094                 break;
0095         }
0096     }
0097 
0098     void set_join_style(model::Stroke::Join join)
0099     {
0100         this->join = Qt::PenJoinStyle(join);
0101 
0102         switch ( join )
0103         {
0104             case model::Stroke::BevelJoin:
0105                 ui.button_join_bevel->setChecked(true);
0106                 break;
0107             case model::Stroke::RoundJoin:
0108                 ui.button_join_round->setChecked(true);
0109                 break;
0110             case model::Stroke::MiterJoin:
0111                 ui.button_join_miter->setChecked(true);
0112                 break;
0113         }
0114     }
0115 
0116     template<class Prop>
0117     void set(const QString& name, Prop (model::Stroke::*prop), const QVariant& value, bool commit)
0118     {
0119         if ( updating )
0120             return;
0121 
0122         auto cmd = std::make_unique<command::SetMultipleAnimated>(name, commit);
0123 
0124         for ( auto target : targets )
0125         {
0126             if ( !target->docnode_locked_recursive() )
0127                 cmd->push_property_not_animated(&(static_cast<model::Stroke*>(target)->*prop), value);
0128         }
0129 
0130         if ( !cmd->empty() && current_target )
0131             current_target->push_command(cmd.release());
0132     }
0133 
0134     void set_color(const QColor&, bool commit)
0135     {
0136         if ( updating )
0137             return;
0138 
0139         ui.color_selector->apply_to_targets(i18n("Update Stroke Color"), targets, stop, commit);
0140     }
0141 };
0142 
0143 StrokeStyleWidget::StrokeStyleWidget(QWidget* parent)
0144     : QWidget(parent), d(std::make_unique<Private>())
0145 {
0146     d->ui.setupUi(this);
0147 
0148 #ifdef Q_OS_ANDROID
0149     d->ui.button_cap_butt->setIcon(QIcon::fromTheme("stroke-cap-butt"));
0150     d->ui.button_cap_round->setIcon(QIcon::fromTheme("stroke-cap-round"));
0151     d->ui.button_cap_square->setIcon(QIcon::fromTheme("stroke-cap-square"));
0152     d->ui.button_join_bevel->setIcon(QIcon::fromTheme("stroke-join-bevel"));
0153     d->ui.button_join_miter->setIcon(QIcon::fromTheme("stroke-join-miter"));
0154     d->ui.button_join_round->setIcon(QIcon::fromTheme("stroke-join-round"));
0155     d->ui.main_layout->setMargin(0);
0156 #endif
0157 
0158     d->group_cap.addButton(d->ui.button_cap_butt);
0159     d->group_cap.addButton(d->ui.button_cap_round);
0160     d->group_cap.addButton(d->ui.button_cap_square);
0161 
0162     d->group_join.addButton(d->ui.button_join_bevel);
0163     d->group_join.addButton(d->ui.button_join_round);
0164     d->group_join.addButton(d->ui.button_join_miter);
0165 
0166     d->ui.spin_stroke_width->setValue(app::settings::get<qreal>("tools", "stroke_width"));
0167     d->ui.spin_miter->setValue(app::settings::get<qreal>("tools", "stroke_miter"));
0168     d->set_cap_style(app::settings::get<model::Stroke::Cap>("tools", "stroke_cap"));
0169     d->set_join_style(app::settings::get<model::Stroke::Join>("tools", "stroke_join"));
0170 
0171 //    d->ui.tab_widget->setTabEnabled(2, false);
0172     d->ui.tab_widget->removeTab(2);
0173 
0174     d->ui.color_selector->set_current_color(app::settings::get<QColor>("tools", "color_secondary"));
0175     d->ui.color_selector->hide_secondary();
0176 
0177     d->dark_theme = palette().window().color().valueF() < 0.5;
0178 }
0179 
0180 StrokeStyleWidget::~StrokeStyleWidget() = default;
0181 
0182 void StrokeStyleWidget::changeEvent(QEvent* e)
0183 {
0184     QWidget::changeEvent(e);
0185     if ( e->type() == QEvent::LanguageChange)
0186     {
0187         d->ui.retranslateUi(this);
0188     }
0189 }
0190 
0191 void StrokeStyleWidget::paintEvent(QPaintEvent* event)
0192 {
0193     QWidget::paintEvent(event);
0194     qreal stroke_width = d->ui.spin_stroke_width->value();
0195     const qreal frame_margin = 6;
0196     const qreal margin = frame_margin+stroke_width*M_SQRT2/2;
0197 
0198     QPainter p(this);
0199     p.setRenderHint(QPainter::Antialiasing);
0200 
0201     QStyleOptionFrame panel;
0202     panel.initFrom(this);
0203     if ( isEnabled() )
0204         panel.state = QStyle::State_Enabled;
0205     panel.rect = d->ui.frame->geometry();
0206     panel.lineWidth = 2;
0207     panel.midLineWidth = 0;
0208     panel.state |= QStyle::State_Raised;
0209     panel.frameShape = QFrame::StyledPanel;
0210 //     style()->drawPrimitive(QStyle::PE_Frame, &panel, &p, this);
0211     style()->drawControl(QStyle::CE_ShapedFrame, &panel, &p, this);
0212 
0213     p.setPen(pen_style());
0214     p.setBrush(Qt::NoBrush);
0215 
0216     QRectF draw_area = QRectF(d->ui.frame->geometry()).adjusted(margin, margin, -margin, -margin);
0217     QPolygonF poly;
0218     poly.push_back(draw_area.bottomLeft());
0219     poly.push_back(QPointF(draw_area.center().x(), draw_area.top()));
0220     poly.push_back(draw_area.bottomRight());
0221 
0222     p.drawPolyline(poly);
0223 }
0224 
0225 void StrokeStyleWidget::check_cap()
0226 {
0227     if ( d->ui.button_cap_butt->isChecked() )
0228         d->cap = Qt::FlatCap;
0229     else if ( d->ui.button_cap_round->isChecked() )
0230         d->cap = Qt::RoundCap;
0231     else if ( d->ui.button_cap_square->isChecked() )
0232         d->cap = Qt::SquareCap;
0233 
0234     d->set(i18n("Set Line Cap"), &model::Stroke::cap, int(d->cap), true);
0235 
0236     Q_EMIT pen_style_changed();
0237     update();
0238 }
0239 
0240 void StrokeStyleWidget::check_join()
0241 {
0242     if ( d->ui.button_join_bevel->isChecked() )
0243         d->join = Qt::BevelJoin;
0244     else if ( d->ui.button_join_round->isChecked() )
0245         d->join = Qt::RoundJoin;
0246     else if ( d->ui.button_join_miter->isChecked() )
0247         d->join = Qt::MiterJoin;
0248 
0249     d->set(i18n("Set Line Join"), &model::Stroke::join, int(d->join), true);
0250 
0251     Q_EMIT pen_style_changed();
0252     update();
0253 }
0254 
0255 void StrokeStyleWidget::save_settings() const
0256 {
0257     app::settings::set("tools", "stroke_width", d->ui.spin_stroke_width->value());
0258     app::settings::set("tools", "stroke_miter", d->ui.spin_miter->value());
0259     app::settings::set("tools", "stroke_cap", int(d->cap));
0260     app::settings::set("tools", "stroke_join", int(d->join));
0261 }
0262 
0263 QPen StrokeStyleWidget::pen_style() const
0264 {
0265     QPen pen(d->ui.color_selector->current_color(), d->ui.spin_stroke_width->value());
0266     pen.setCapStyle(d->cap);
0267     pen.setJoinStyle(d->join);
0268     pen.setMiterLimit(d->ui.spin_miter->value());
0269     return pen;
0270 }
0271 
0272 QColor StrokeStyleWidget::current_color() const
0273 {
0274     return d->ui.color_selector->current_color();
0275 }
0276 
0277 
0278 void StrokeStyleWidget::set_color(const QColor& color)
0279 {
0280     d->ui.color_selector->set_current_color(color);
0281     if ( d->can_update_target() )
0282         d->set_color(color, true);
0283 }
0284 
0285 void StrokeStyleWidget::check_color(const QColor& color)
0286 {
0287     d->update_background(color);
0288     if ( d->can_update_target() )
0289         d->set_color(color, false);
0290 
0291     update();
0292     Q_EMIT pen_style_changed();
0293     Q_EMIT color_changed(color);
0294 }
0295 
0296 void glaxnimate::gui::StrokeStyleWidget::before_set_target()
0297 {
0298     if ( d->current_target )
0299     {
0300         disconnect(d->current_target, &model::Object::property_changed, this, &StrokeStyleWidget::property_changed);
0301     }
0302 }
0303 
0304 
0305 void glaxnimate::gui::StrokeStyleWidget::after_set_target()
0306 {
0307     if ( d->current_target )
0308     {
0309         d->update_from_target();
0310 //         Q_EMIT color_changed(d->ui.color_selector->current_color());
0311         connect(d->current_target, &model::Object::property_changed, this, &StrokeStyleWidget::property_changed);
0312         update();
0313     }
0314 }
0315 
0316 void glaxnimate::gui::StrokeStyleWidget::set_current(model::Stroke* stroke)
0317 {
0318     before_set_target();
0319     d->current_target = stroke;
0320     d->stop = -1;
0321     after_set_target();
0322 }
0323 
0324 void glaxnimate::gui::StrokeStyleWidget::set_targets(std::vector<model::Stroke *> targets)
0325 {
0326     d->targets.clear();
0327     d->targets.assign(targets.begin(), targets.end());
0328 }
0329 
0330 
0331 void StrokeStyleWidget::property_changed(const model::BaseProperty* prop)
0332 {
0333     d->update_from_target();
0334     if ( prop == &d->current_target->color || prop == &d->current_target->use )
0335         Q_EMIT color_changed(d->ui.color_selector->current_color());
0336     update();
0337 }
0338 
0339 void StrokeStyleWidget::check_miter(double w)
0340 {
0341     d->set(i18n("Set Miter Limit"), &model::Stroke::miter_limit, w, false);
0342 
0343     Q_EMIT pen_style_changed();
0344     update();
0345 }
0346 
0347 void StrokeStyleWidget::check_width(double w)
0348 {
0349     d->set(i18n("Set Line Width"), &model::Stroke::width, w, false);
0350 
0351     Q_EMIT pen_style_changed();
0352     update();
0353 }
0354 
0355 void StrokeStyleWidget::color_committed(const QColor& color)
0356 {
0357     if ( d->can_update_target() )
0358         d->set_color(color, true);
0359     Q_EMIT pen_style_changed();
0360 }
0361 
0362 void StrokeStyleWidget::commit_width()
0363 {
0364     d->set(i18n("Set Line Width"), &model::Stroke::width, d->ui.spin_stroke_width->value(), true);
0365     Q_EMIT pen_style_changed();
0366 }
0367 
0368 model::Stroke * StrokeStyleWidget::current() const
0369 {
0370     return d->current_target;
0371 }
0372 
0373 void StrokeStyleWidget::set_palette_model(color_widgets::ColorPaletteModel* palette_model)
0374 {
0375     d->ui.color_selector->set_palette_model(palette_model);
0376 }
0377 
0378 void StrokeStyleWidget::set_gradient_stop(model::Styler* styler, int index)
0379 {
0380     if ( auto stroke = styler->cast<model::Stroke>() )
0381     {
0382         before_set_target();
0383         d->current_target = stroke;
0384         d->stop = index;
0385         after_set_target();
0386     }
0387 }
0388 
0389 void StrokeStyleWidget::set_stroke_width(qreal w)
0390 {
0391     d->ui.spin_stroke_width->setValue(w);
0392 }
0393 
0394 void StrokeStyleWidget::clear_color()
0395 {
0396     d->ui.color_selector->clear_targets(i18n("Clear Line Color"), d->targets);
0397     Q_EMIT pen_style_changed();
0398 }