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 }