diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index 1ac93c362..e5115e906 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -194,7 +194,8 @@ void PreferencesDialog::save() { // Check if any settings will require a restart of Olive if (olive::CurrentConfig.use_software_fallback != use_software_fallbacks_checkbox->isChecked() || olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value() - || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value()) { + || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value() + || olive::CurrentConfig.style != static_cast(ui_style->currentData().toInt())) { // any changes to these settings will require a restart - ask the user if we should do one now or later @@ -234,8 +235,10 @@ void PreferencesDialog::save() { } // save settings from UI to backend - olive::CurrentConfig.css_path = custom_css_fn->text(); - olive::MainWindow->load_css_from_file(olive::CurrentConfig.css_path); + if (olive::CurrentConfig.css_path != custom_css_fn->text()) { + olive::CurrentConfig.css_path = custom_css_fn->text(); + olive::MainWindow->Restyle(); + } olive::CurrentConfig.recording_mode = recordingComboBox->currentIndex() + 1; olive::CurrentConfig.img_seq_formats = imgSeqFormatEdit->text(); @@ -254,6 +257,9 @@ void PreferencesDialog::save() { olive::CurrentConfig.use_software_fallback = use_software_fallbacks_checkbox->isChecked(); olive::CurrentConfig.language_file = language_combobox->currentData().toString(); + olive::CurrentConfig.style = static_cast(ui_style->currentData().toInt()); + + // Check if the thumbnail or waveform icon if (olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value() || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value()) { // we're changing the size of thumbnails and waveforms, so let's delete them and regenerate them next start @@ -500,19 +506,6 @@ void PreferencesDialog::setup_ui() { row++; - // General -> Custom CSS - general_layout->addWidget(new QLabel(tr("Custom CSS:"), this), row, 0); - - custom_css_fn = new QLineEdit(general_tab); - custom_css_fn->setText(olive::CurrentConfig.css_path); - general_layout->addWidget(custom_css_fn, row, 1, 1, 3); - - QPushButton* custom_css_browse = new QPushButton(tr("Browse"), general_tab); - connect(custom_css_browse, SIGNAL(clicked(bool)), this, SLOT(browse_css_file())); - general_layout->addWidget(custom_css_browse, row, 4); - - row++; - // General -> Image Sequence Formats general_layout->addWidget(new QLabel(tr("Image sequence formats:"), this), row, 0); @@ -522,26 +515,6 @@ void PreferencesDialog::setup_ui() { row++; - // General -> Audio Recording - general_layout->addWidget(new QLabel(tr("Audio Recording:"), this), row, 0); - - recordingComboBox = new QComboBox(general_tab); - recordingComboBox->addItem(tr("Mono")); - recordingComboBox->addItem(tr("Stereo")); - general_layout->addWidget(recordingComboBox, row, 1, 1, 4); - - row++; - - // General -> Effect Textbox Lines - general_layout->addWidget(new QLabel(tr("Effect Textbox Lines:"), this), row, 0); - - effect_textbox_lines_field = new QSpinBox(general_tab); - effect_textbox_lines_field->setMinimum(1); - effect_textbox_lines_field->setValue(olive::CurrentConfig.effect_textbox_lines); - general_layout->addWidget(effect_textbox_lines_field, row, 1, 1, 4); - - row++; - // General -> Thumbnail and Waveform Resolution general_layout->addWidget(new QLabel(tr("Thumbnail Resolution:"), this), row, 0); @@ -583,6 +556,50 @@ void PreferencesDialog::setup_ui() { add_default_effects_to_clips->setChecked(olive::CurrentConfig.add_default_effects_to_clips); behavior_tab_layout->addWidget(add_default_effects_to_clips); + // Appearance + QWidget* appearance_tab = new QWidget(this); + tabWidget->addTab(appearance_tab, tr("Appearance")); + + row = 0; + + QGridLayout* appearance_layout = new QGridLayout(appearance_tab); + + // Appearance -> Theme + appearance_layout->addWidget(new QLabel(tr("Theme")), row, 0); + + ui_style = new QComboBox(); + ui_style->addItem(tr("Olive Dark (Default)"), olive::styling::kOliveDefaultDark); + ui_style->addItem(tr("Olive Light"), olive::styling::kOliveDefaultLight); + ui_style->addItem(tr("Native"), olive::styling::kNativeDarkIcons); + ui_style->addItem(tr("Native (Light Icons)"), olive::styling::kNativeLightIcons); + ui_style->setCurrentIndex(olive::CurrentConfig.style); + appearance_layout->addWidget(ui_style, row, 1, 1, 2); + + row++; + + // Appearance -> Custom CSS + appearance_layout->addWidget(new QLabel(tr("Custom CSS:"), this), row, 0); + + custom_css_fn = new QLineEdit(general_tab); + custom_css_fn->setText(olive::CurrentConfig.css_path); + appearance_layout->addWidget(custom_css_fn, row, 1); + + QPushButton* custom_css_browse = new QPushButton(tr("Browse"), general_tab); + connect(custom_css_browse, SIGNAL(clicked(bool)), this, SLOT(browse_css_file())); + appearance_layout->addWidget(custom_css_browse, row, 2); + + row++; + + // Appearance -> Effect Textbox Lines + appearance_layout->addWidget(new QLabel(tr("Effect Textbox Lines:"), this), row, 0); + + effect_textbox_lines_field = new QSpinBox(general_tab); + effect_textbox_lines_field->setMinimum(1); + effect_textbox_lines_field->setValue(olive::CurrentConfig.effect_textbox_lines); + appearance_layout->addWidget(effect_textbox_lines_field, row, 1, 1, 2); + + row++; + // Playback QWidget* playback_tab = new QWidget(this); QVBoxLayout* playback_tab_layout = new QVBoxLayout(playback_tab); @@ -630,7 +647,11 @@ void PreferencesDialog::setup_ui() { QGridLayout* audio_tab_layout = new QGridLayout(audio_tab); - audio_tab_layout->addWidget(new QLabel(tr("Output Device:")), 0, 0); + row = 0; + + // Audio -> Output Device + + audio_tab_layout->addWidget(new QLabel(tr("Output Device:")), row, 0); audio_output_devices = new QComboBox(); audio_output_devices->addItem(tr("Default"), ""); @@ -647,9 +668,13 @@ void PreferencesDialog::setup_ui() { } } - audio_tab_layout->addWidget(audio_output_devices, 0, 1); + audio_tab_layout->addWidget(audio_output_devices, row, 1); - audio_tab_layout->addWidget(new QLabel(tr("Input Device:")), 1, 0); + row++; + + // Audio -> Input Device + + audio_tab_layout->addWidget(new QLabel(tr("Input Device:")), row, 0); audio_input_devices = new QComboBox(); audio_input_devices->addItem(tr("Default"), ""); @@ -666,9 +691,13 @@ void PreferencesDialog::setup_ui() { } } - audio_tab_layout->addWidget(audio_input_devices, 1, 1); + audio_tab_layout->addWidget(audio_input_devices, row, 1); - audio_tab_layout->addWidget(new QLabel(tr("Sample Rate:")), 2, 0); + row++; + + // Audio -> Sample Rate + + audio_tab_layout->addWidget(new QLabel(tr("Sample Rate:")), row, 0); audio_sample_rate = new QComboBox(); combobox_audio_sample_rates(audio_sample_rate); @@ -679,7 +708,19 @@ void PreferencesDialog::setup_ui() { } } - audio_tab_layout->addWidget(audio_sample_rate, 2, 1); + audio_tab_layout->addWidget(audio_sample_rate, row, 1); + + row++; + + // Audio -> Audio Recording + audio_tab_layout->addWidget(new QLabel(tr("Audio Recording:"), this), row, 0); + + recordingComboBox = new QComboBox(general_tab); + recordingComboBox->addItem(tr("Mono")); + recordingComboBox->addItem(tr("Stereo")); + audio_tab_layout->addWidget(recordingComboBox, row, 1); + + row++; tabWidget->addTab(audio_tab, tr("Audio")); diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index 03d7bd4e5..e192a9a63 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -93,6 +93,7 @@ private: QSpinBox* thumbnail_res_spinbox; QSpinBox* waveform_res_spinbox; QCheckBox* add_default_effects_to_clips; + QComboBox* ui_style; QVector key_shortcut_actions; QVector key_shortcut_items; diff --git a/effects/effect.cpp b/effects/effect.cpp index 799aa3538..dec9b4d38 100644 --- a/effects/effect.cpp +++ b/effects/effect.cpp @@ -595,7 +595,7 @@ void Effect::save(QXmlStreamWriter& stream) { for (int j=0;jFieldCount();j++) { EffectField* field = row->Field(j); - if (field->id().isEmpty()) { + if (!field->id().isEmpty()) { stream.writeStartElement("field"); // field stream.writeAttribute("id", field->id()); for (int k=0;kkeyframes.size();k++) { diff --git a/effects/effectrow.cpp b/effects/effectrow.cpp index 16cfde39d..3821c08a0 100644 --- a/effects/effectrow.cpp +++ b/effects/effectrow.cpp @@ -85,6 +85,8 @@ void EffectRow::SetKeyframingEnabled(bool enabled) { olive::UndoStack.push(ca); + update_ui(false); + } else { // Confirm with the user whether they really want to disable keyframing @@ -105,7 +107,8 @@ void EffectRow::SetKeyframingEnabled(bool enabled) { ca->append(new SetIsKeyframing(this, false)); olive::UndoStack.push(ca); - panel_effect_controls->update_keyframes(); + + update_ui(false); } else { diff --git a/global/config.cpp b/global/config.cpp index 5b09d877b..53b43e42a 100644 --- a/global/config.cpp +++ b/global/config.cpp @@ -71,7 +71,8 @@ Config::Config() waveform_resolution(64), thumbnail_resolution(120), add_default_effects_to_clips(true), - invert_timeline_scroll_axes(true) + invert_timeline_scroll_axes(true), + style(olive::styling::kOliveDefaultDark) {} void Config::load(QString path) { @@ -211,6 +212,9 @@ void Config::load(QString path) { } else if (stream.name() == "AddDefaultEffectsToClips") { stream.readNext(); add_default_effects_to_clips = (stream.text() == "1"); + } else if (stream.name() == "Style") { + stream.readNext(); + style = static_cast(stream.text().toInt()); } } } @@ -278,6 +282,7 @@ void Config::save(QString path) { stream.writeTextElement("ThumbnailResolution", QString::number(thumbnail_resolution)); stream.writeTextElement("WaveformResolution", QString::number(waveform_resolution)); stream.writeTextElement("AddDefaultEffectsToClips", QString::number(add_default_effects_to_clips)); + stream.writeTextElement("Style", QString::number(style)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/global/config.h b/global/config.h index a83eb30d7..3b768e31e 100644 --- a/global/config.h +++ b/global/config.h @@ -23,6 +23,8 @@ #include +#include "ui/styling.h" + namespace olive { /** * @brief Version identifier for saved projects @@ -517,6 +519,13 @@ struct Config { */ bool invert_timeline_scroll_axes; + /** + * @brief Style to use when theming Olive. + * + * Set to a member of olive::styling::Style. + */ + olive::styling::Style style; + /** * @brief Load config from file * diff --git a/olive.pro b/olive.pro index 252f51051..f559cbf16 100644 --- a/olive.pro +++ b/olive.pro @@ -170,7 +170,8 @@ SOURCES += \ effects/fields/labelfield.cpp \ effects/fields/buttonfield.cpp \ ui/effectui.cpp \ - effects/transition.cpp + effects/transition.cpp \ + ui/styling.cpp HEADERS += \ ui/mainwindow.h \ @@ -294,7 +295,8 @@ HEADERS += \ effects/fields/buttonfield.h \ effects/fields/colorfield.h \ effects/fields/combofield.h \ - effects/transition.h + effects/transition.h \ + ui/styling.h FORMS += diff --git a/ui/icons.cpp b/ui/icons.cpp index 19dda5aeb..0e3eff232 100644 --- a/ui/icons.cpp +++ b/ui/icons.cpp @@ -1,6 +1,7 @@ #include "icons.h" -#include "oliveglobal.h" +#include "global/global.h" +#include "global/config.h" QIcon olive::icon::LeftArrow; QIcon olive::icon::RightArrow; @@ -27,14 +28,27 @@ QIcon olive::icon::CreateIconFromSVG(const QString &path) { QIcon icon; + QPainter p; + + // Draw the icon as a solid color QPixmap normal(path); + + if (olive::styling::UseDarkIcons()) { + p.begin(&normal); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(normal.rect(), QColor(32, 32, 32)); + p.end(); + } + icon.addPixmap(normal, QIcon::Normal, QIcon::On); + // Create semi-transparent disabled icon QPixmap disabled(normal.size()); disabled.fill(Qt::transparent); // draw semi-transparent version of icon for the disabled variant - QPainter p(&disabled); + p.begin(&disabled); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); p.setOpacity(0.5); p.drawPixmap(0, 0, normal); p.end(); diff --git a/ui/labelslider.cpp b/ui/labelslider.cpp index 614c8b556..94751124e 100644 --- a/ui/labelslider.cpp +++ b/ui/labelslider.cpp @@ -25,6 +25,7 @@ #include "global/config.h" #include "global/math.h" #include "global/debug.h" +#include "ui/styling.h" #include #include @@ -119,7 +120,13 @@ QString LabelSlider::ValueToString() { } void LabelSlider::SetColor(QString c) { - if (c.isEmpty()) c = "#ffc000"; + if (c.isEmpty()) { + if (olive::styling::UseDarkIcons()) { + c = "#0080ff"; + } else { + c = "#ffc000"; + } + } setStyleSheet("QLabel{color:" + c + ";text-decoration:underline;}QLabel:disabled{color:#808080;}"); } diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 9c14f7da4..ca7b47eec 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -194,7 +194,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), first_show(true) { - olive::icon::Initialize(); olive::cursor::Initialize(); open_debug_file(); @@ -203,29 +202,6 @@ MainWindow::MainWindow(QWidget *parent) : olive::MainWindow = this; - // set up style? - - qApp->setStyle(QStyleFactory::create("Fusion")); - setStyleSheet(DEFAULT_CSS); - - QPalette darkPalette; - darkPalette.setColor(QPalette::Window, QColor(53,53,53)); - darkPalette.setColor(QPalette::WindowText, Qt::white); - darkPalette.setColor(QPalette::Base, QColor(25,25,25)); - darkPalette.setColor(QPalette::AlternateBase, QColor(53,53,53)); - darkPalette.setColor(QPalette::ToolTipBase, QColor(25,25,25)); - darkPalette.setColor(QPalette::ToolTipText, Qt::white); - darkPalette.setColor(QPalette::Text, Qt::white); - darkPalette.setColor(QPalette::Button, QColor(53,53,53)); - darkPalette.setColor(QPalette::ButtonText, Qt::white); darkPalette.setColor(QPalette::BrightText, Qt::red); - darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); - darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::HighlightedText, Qt::black); - - qApp->setPalette(darkPalette); - - // end style QWidget* centralWidget = new QWidget(this); centralWidget->setMaximumSize(QSize(0, 0)); setCentralWidget(centralWidget); @@ -296,13 +272,13 @@ MainWindow::MainWindow(QWidget *parent) : QString config_fn = config_dir.filePath("config.xml"); if (QFileInfo::exists(config_fn)) { olive::CurrentConfig.load(config_fn); - - if (!olive::CurrentConfig.css_path.isEmpty()) { - load_css_from_file(olive::CurrentConfig.css_path); - } } } + Restyle(); + + olive::icon::Initialize(); + alloc_panels(this); // populate menu bars @@ -413,14 +389,53 @@ void MainWindow::save_shortcuts(const QString& fn) { } } -void MainWindow::load_css_from_file(const QString &fn) { +bool MainWindow::load_css_from_file(const QString &fn) { QFile css_file(fn); if (css_file.exists() && css_file.open(QFile::ReadOnly)) { setStyleSheet(css_file.readAll()); css_file.close(); + return true; + } + return false; +} + +void MainWindow::Restyle() +{ + // Set up UI style + if (olive::styling::UseNativeUI()) { + qApp->setStyle(QStyleFactory::create("")); } else { - // set default stylesheet - setStyleSheet(DEFAULT_CSS); + qApp->setStyle(QStyleFactory::create("Fusion")); + + // Set up whether to load custom CSS or default CSS+palette + if (!olive::CurrentConfig.css_path.isEmpty() + && load_css_from_file(olive::CurrentConfig.css_path)) { + + setPalette(QPalette()); + + } else { + + // set default palette + QPalette darkPalette; + darkPalette.setColor(QPalette::Window, QColor(53,53,53)); + darkPalette.setColor(QPalette::WindowText, Qt::white); + darkPalette.setColor(QPalette::Base, QColor(25,25,25)); + darkPalette.setColor(QPalette::AlternateBase, QColor(53,53,53)); + darkPalette.setColor(QPalette::ToolTipBase, QColor(25,25,25)); + darkPalette.setColor(QPalette::ToolTipText, Qt::white); + darkPalette.setColor(QPalette::Text, Qt::white); + darkPalette.setColor(QPalette::Button, QColor(53,53,53)); + darkPalette.setColor(QPalette::ButtonText, Qt::white); + darkPalette.setColor(QPalette::BrightText, Qt::red); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(darkPalette); + + // set default CSS + setStyleSheet(DEFAULT_CSS); + } } } diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 2aad2a65f..e06987228 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -73,8 +73,17 @@ public: * @param fn * * URL to load the CSS file from. + * + * @return + * + * **TRUE** if CSS was successfully loaded. */ - void load_css_from_file(const QString& fn); + bool load_css_from_file(const QString& fn); + + /** + * @brief Set application's QStyle based on values from Config + */ + void Restyle(); public slots: /** @@ -248,7 +257,6 @@ private: QAction* ripple_delete_inout_point_; QAction* setedit_marker_; - // view menu actions QMenu* view_menu; QAction* zoom_in_; diff --git a/ui/styling.cpp b/ui/styling.cpp new file mode 100644 index 000000000..2c890a3ef --- /dev/null +++ b/ui/styling.cpp @@ -0,0 +1,24 @@ +#include "styling.h" + +#include "global/config.h" + +bool olive::styling::UseDarkIcons() +{ + return olive::CurrentConfig.style == kOliveDefaultLight || olive::CurrentConfig.style == kNativeDarkIcons; +} + +QColor olive::styling::GetIconColor() +{ + if (UseDarkIcons()) { + return Qt::black; + } else { + return Qt::white; + } +} + + + +bool olive::styling::UseNativeUI() +{ + return olive::CurrentConfig.style == kNativeLightIcons || olive::CurrentConfig.style == kNativeDarkIcons; +} diff --git a/ui/styling.h b/ui/styling.h new file mode 100644 index 000000000..c8a1b7343 --- /dev/null +++ b/ui/styling.h @@ -0,0 +1,67 @@ +#ifndef STYLING_H +#define STYLING_H + +#include + +namespace olive { + namespace styling { + + /** + * @brief Officially supported styles to use in Olive + */ + enum Style { + /** + Qt Fusion-based cross-platform UI. The default styling of Olive. Can also be heavily customized with a CSS + file. + */ + kOliveDefaultDark, + + /** + Qt Fusion-based cross-platform UI. The default styling of Olive. Can also be heavily customized with a CSS + file. This will use the + */ + kOliveDefaultLight, + + /** + Use current OS's native styling (or at least Qt's default). Most UIs use a light theming, so this will + automatically implement dark icons/UI elements. + */ + kNativeDarkIcons, + + /** + Use current OS's native styling (or at least Qt's default). Most UIs use a light theming, but in case one + doesn't, this option will provide light icons for use with a dark theme. + */ + kNativeLightIcons + }; + + /** + * @brief Return whether to use dark icons or light icons + * @return + * + * **TRUE** if icons should be dark. + */ + bool UseDarkIcons(); + + /** + * @brief Return whether to use native UI or Fusion + * @return + * + * **TRUE** if UI should use native styling + */ + bool UseNativeUI(); + + /** + * @brief Return the current icon color based on Config::use_dark_icons. + * + * Also used by some other UI elements like the lines and text on the TimelineHeader + * + * @return + * + * Either white or black depending on Config::use_dark_icons + */ + QColor GetIconColor(); + } +} + +#endif // STYLING_H diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index 510132704..de9e71902 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -28,6 +28,7 @@ #include "project/media.h" #include "panels/viewer.h" #include "global/config.h" +#include "global/global.h" #include "ui/menuhelper.h" #include "global/debug.h" @@ -393,7 +394,7 @@ void TimelineHeader::paintEvent(QPaintEvent*) { if (lineX > lastLineX+LINE_MIN_PADDING) { if (draw_text) { - p.setPen(Qt::white); + p.setPen(olive::styling::GetIconColor()); p.drawText(QRect(text_x, 0, fullTextWidth, yoff), timecode); } @@ -419,7 +420,7 @@ void TimelineHeader::paintEvent(QPaintEvent*) { in_x = getHeaderScreenPointFromFrame((resizing_workarea ? temp_workarea_in : viewer->seq->workarea_in)); int out_x = getHeaderScreenPointFromFrame((resizing_workarea ? temp_workarea_out : viewer->seq->workarea_out)); p.fillRect(QRect(in_x, 0, out_x-in_x, height()), QColor(0, 192, 255, 128)); - p.setPen(Qt::white); + p.setPen(olive::styling::GetIconColor()); p.drawLine(in_x, 0, in_x, height()); p.drawLine(out_x, 0, out_x, height()); }