Ver código fonte

added gradient widget

Nicolas Winkler 5 anos atrás
pai
commit
4bfe33224b
19 arquivos alterados com 667 adições e 37 exclusões
  1. 33 13
      Almond.cpp
  2. 3 0
      Almond.h
  3. 3 0
      Almond.pro
  4. 31 1
      Almond.ui
  5. 35 9
      AlmondMenuWidget.cpp
  6. 20 3
      AlmondMenuWidget.h
  7. 8 2
      BackgroundTask.cpp
  8. 4 2
      CMakeLists.txt
  9. 13 1
      ExportImageMenu.ui
  10. 9 0
      ExportVideoMenu.cpp
  11. 3 0
      ExportVideoMenu.h
  12. 23 3
      ExportVideoMenu.ui
  13. 20 0
      GradientMenu.cpp
  14. 22 0
      GradientMenu.h
  15. 58 0
      GradientMenu.ui
  16. 333 1
      GradientWidget.cpp
  17. 44 1
      GradientWidget.h
  18. 5 0
      MandelWidget.cpp
  19. 0 1
      main.cpp

+ 33 - 13
Almond.cpp

@@ -42,13 +42,18 @@ Almond::Almond(QWidget* parent) :
     amw->setMainMenu(ui.dockWidgetContents_2);
     eim = new ExportImageMenu();
     evm = new ExportVideoMenu();
-    amw->addSubMenu(eim);
-    amw->addSubMenu(evm);
+    gradientMenu = new GradientMenu();
+    AlmondSubMenu* imageSm = amw->addSubMenu(eim);
+    AlmondSubMenu* videoSm = amw->addSubMenu(evm);
+    AlmondSubMenu* gradientSm = amw->addSubMenu(gradientMenu);
     ui.dockWidget_2->setWidget(amw);
 
     connect(amw, &AlmondMenuWidget::submenuCancel, [this] (int) { amw->showMainMenu(); });
-    connect(amw, &AlmondMenuWidget::submenuOK, this, &Almond::submenuOK);
-            
+    //connect(amw, &AlmondMenuWidget::submenuOK, this, &Almond::submenuOK);
+    connect(imageSm, &AlmondSubMenu::accepted, this, &Almond::imageExportOk);
+    connect(videoSm, &AlmondSubMenu::accepted, this, &Almond::videoExportOk);
+    connect(gradientSm, &AlmondSubMenu::accepted, this, &Almond::gradientEditOk);
+
 
     /*QStatusBar* bar = new QStatusBar(this);
     bar->addWidget(new QLabel("ayay"));
@@ -130,7 +135,6 @@ void Almond::submenuOK(int smIndex)
         emit videoExportOk();
         break;
     }
-    amw->showMainMenu();
 }
 
 void Almond::imageExportOk(void)
@@ -158,16 +162,20 @@ void Almond::imageExportOk(void)
     iei.path = eim->getPath().toStdString();
     iei.options.jpegQuality = 95;
     submitBackgroundTask(new ImageExportTask(iei, [this] () { return stoppingBackgroundTasks; }));
+    amw->showMainMenu();
 }
 
 
 void Almond::videoExportOk(void)
 {
-    ExportVideoInfo evi;// = evm->getInfo();
-    evi.start = mnd::MandelViewport::standardView();
-    evi.end = mw->getViewport();
+    ExportVideoInfo evi = evm->getInfo();
     evi.gradient = mw->getGradient();
-    evi.mi = mw->getMandelInfo();
+    evi.mi.smooth = mw->getSmoothColoring();
+    if (currentView == JULIA) {
+        evi.mi.julia = mw->getMandelInfo().julia;
+        evi.mi.juliaX = mw->getMandelInfo().juliaX;
+        evi.mi.juliaY = mw->getMandelInfo().juliaY;
+    }
     if (evi.path == "") {
         QMessageBox errMsg = QMessageBox(QMessageBox::Icon::Critical, "Error", "No path specified.");
         errMsg.exec();
@@ -175,11 +183,20 @@ void Almond::videoExportOk(void)
     else {
         MandelVideoGenerator mvg(evi);
         mnd::MandelGenerator& g = *mw->getGenerator();
+        printf("wii: %ld\n", evi.mi.bWidth);
+        fflush(stdout);
         submitBackgroundTask(new VideoExportTask(std::move(mvg), g));
+        amw->showMainMenu();
     }
 }
 
 
+void Almond::gradientEditOk(void)
+{
+    amw->showMainMenu();
+}
+
+
 void Almond::toggleFullscreen(void)
 {
     if (fullscreenMode) {
@@ -213,6 +230,8 @@ void Almond::backgroundTaskFinished(bool succ, QString message)
 
     ui.backgroundProgress->setFormat(tr("Export Progress"));
     ui.backgroundProgress->setEnabled(false);
+    ui.backgroundProgress->setRange(0, 100);
+    ui.backgroundProgress->setValue(0);
     ui.cancelProgress->setEnabled(false);
     stoppingBackgroundTasks = false;
 }
@@ -258,10 +277,11 @@ void Almond::on_maxIterations_editingFinished()
 
 void Almond::on_chooseGradient_clicked()
 {
-    gcd.exec();
-    auto gradient = gcd.getGradient();
-    if (gradient)
-        mw->setGradient(std::move(*gradient));
+    emit this->amw->showSubMenu(2);
+    //gcd.exec();
+    //auto gradient = gcd.getGradient();
+    //if (gradient)
+    //    mw->setGradient(std::move(*gradient));
 }
 
 

+ 3 - 0
Almond.h

@@ -16,6 +16,7 @@
 #include "AlmondMenuWidget.h"
 #include "ExportImageMenu.h"
 #include "ExportVideoMenu.h"
+#include "GradientMenu.h"
 
 
 #include <memory>
@@ -46,6 +47,7 @@ private:
     AlmondMenuWidget* amw;
     ExportImageMenu* eim;
     ExportVideoMenu* evm;
+    GradientMenu* gradientMenu;
 
     bool fullscreenMode = false;
     QWidget* cw;
@@ -79,6 +81,7 @@ public:
     void submenuOK(int smIndex);
     void imageExportOk(void);
     void videoExportOk(void);
+    void gradientEditOk(void);
 public slots:
     void toggleFullscreen(void);
 private slots:

+ 3 - 0
Almond.pro

@@ -34,6 +34,7 @@ SOURCES += \
         ExportImageMenu.cpp \
         ExportVideoMenu.cpp \
         Gradient.cpp \
+        GradientMenu.cpp \
         GradientWidget.cpp \
         MandelWidget.cpp \
         choosegenerators.cpp \
@@ -52,6 +53,7 @@ HEADERS += \
         ExportImageMenu.h \
         ExportVideoMenu.h \
         Gradient.h \
+        GradientMenu.h \
         GradientWidget.h \
         MandelWidget.h \
         choosegenerators.h \
@@ -64,6 +66,7 @@ FORMS += \
         Almond.ui \
         ExportImageMenu.ui \
         ExportVideoMenu.ui \
+        GradientMenu.ui \
         choosegenerators.ui \
         customgenerator.ui \
         exportimagedialog.ui \

+ 31 - 1
Almond.ui

@@ -10,7 +10,7 @@
     <x>0</x>
     <y>0</y>
     <width>1202</width>
-    <height>691</height>
+    <height>1192</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -28,6 +28,12 @@
    </layout>
   </widget>
   <widget class="QDockWidget" name="dockWidget_2">
+   <property name="sizePolicy">
+    <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
+     <horstretch>0</horstretch>
+     <verstretch>0</verstretch>
+    </sizepolicy>
+   </property>
    <property name="features">
     <set>QDockWidget::AllDockWidgetFeatures</set>
    </property>
@@ -46,6 +52,12 @@
       <layout class="QVBoxLayout" name="verticalLayout_right">
        <item>
         <widget class="QGroupBox" name="grp_zoom">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Zoom</string>
          </property>
@@ -89,6 +101,12 @@
        </item>
        <item>
         <widget class="QGroupBox" name="grp_display_opts">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Display/Calculation Options</string>
          </property>
@@ -163,6 +181,12 @@
        </item>
        <item>
         <widget class="QGroupBox" name="grp_fractal">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Fractal</string>
          </property>
@@ -220,6 +244,12 @@
        </item>
        <item>
         <widget class="QGroupBox" name="grp_export_box">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Export</string>
          </property>

+ 35 - 9
AlmondMenuWidget.cpp

@@ -5,21 +5,32 @@
 #include <QPropertyAnimation>
 #include <QParallelAnimationGroup>
 
+AlmondSubMenu::AlmondSubMenu(QWidget* widget) :
+    w{ widget }
+{
+}
+
+
+QWidget* AlmondSubMenu::widget(void)
+{
+    return w;
+}
+
+
 AlmondMenuWidget::AlmondMenuWidget(QWidget* parent) :
     QFrame{ parent },
     mainMenu{ nullptr }
 {
-    this->setContentsMargins(0, 0, 0, 0);
     rightWidget = new QWidget(this);
     subMenuContainer = new QStackedWidget(rightWidget);
+    subMenuContainer->setContentsMargins(0, 0, 0, 0);
     rightOK = new QPushButton("OK", rightWidget);
     rightCancel = new QPushButton("Cancel", rightWidget);
-    subMenuContainer->setContentsMargins(0, 0, 0, 0);
-    subMenuContainer->setLayout(new QVBoxLayout());
 
     leftWidget = new QWidget(this);
     QVBoxLayout* mainLayout = new QVBoxLayout(leftWidget);
     mainLayout->setMargin(0);
+    mainLayout->setContentsMargins(0, 0, 0, 0);
 
     QVBoxLayout* smlayout = new QVBoxLayout(rightWidget);
     smlayout->addWidget(subMenuContainer, 1);
@@ -52,6 +63,14 @@ AlmondMenuWidget::AlmondMenuWidget(QWidget* parent) :
 }
 
 
+AlmondMenuWidget::~AlmondMenuWidget(void)
+{
+    for (auto& a : subMenus) {
+        delete a;
+    }
+}
+
+
 void AlmondMenuWidget::setMainMenu(QWidget* mainMenu)
 {
     //mainMenu->setParent(this);
@@ -60,10 +79,12 @@ void AlmondMenuWidget::setMainMenu(QWidget* mainMenu)
 }
 
 
-void AlmondMenuWidget::addSubMenu(QWidget* subMenu)
+AlmondSubMenu* AlmondMenuWidget::addSubMenu(QWidget* subMenu)
 {
     subMenuContainer->addWidget(subMenu);
-    this->subMenus.append(subMenu);
+    AlmondSubMenu* almsm = new AlmondSubMenu(subMenu);
+    this->subMenus.append(almsm);
+    return almsm;
 }
 
 
@@ -71,7 +92,8 @@ QSize AlmondMenuWidget::sizeHint(void) const
 {
     QSize hint{ 0, 0 };
     hint = leftWidget->sizeHint();
-    for (auto& widget : subMenus) {
+    for (auto& subMenu : subMenus) {
+        const auto& widget = subMenu->widget();
         QSize widgetHint = widget->sizeHint();
         if (hint.width() < widgetHint.width())
             hint.setWidth(widgetHint.width());
@@ -86,7 +108,8 @@ QSize AlmondMenuWidget::minimumSizeHint(void) const
 {
     QSize hint{ 0, 0 };
     hint = leftWidget->minimumSizeHint();
-    for (auto& widget : subMenus) {
+    for (auto& subMenu : subMenus) {
+        const auto& widget = subMenu->widget();
         QSize widgetHint = widget->minimumSizeHint();
         if (hint.width() < widgetHint.width())
             hint.setWidth(widgetHint.width());
@@ -110,14 +133,17 @@ void AlmondMenuWidget::resizeEvent(QResizeEvent* event)
 
 void AlmondMenuWidget::clickedRightOK(void)
 {
-    emit submenuOK(subMenuContainer->currentIndex());
+    int index = subMenuContainer->currentIndex();
+    emit submenuOK(index);
+    emit subMenus.at(index)->accepted();
 }
 
 
 void AlmondMenuWidget::clickedRightCancel(void)
 {
+    int index = subMenuContainer->currentIndex();
     emit submenuCancel(subMenuContainer->currentIndex());
-    //submenuCancel(0);
+    emit subMenus.at(index)->cancelled();
 }
 
 

+ 20 - 3
AlmondMenuWidget.h

@@ -7,7 +7,23 @@
 #include <QPushButton>
 #include <QStateMachine>
 
-class AlmondMenuWidget : public QFrame
+class AlmondSubMenu :
+    public QObject
+{
+    Q_OBJECT
+
+    QWidget* w;
+public:
+    AlmondSubMenu(QWidget* widget);
+    QWidget* widget(void);
+
+signals:
+    void accepted(void);
+    void cancelled(void);
+};
+
+class AlmondMenuWidget :
+    public QFrame
 {
     Q_OBJECT
         
@@ -17,13 +33,14 @@ class AlmondMenuWidget : public QFrame
     QPushButton* rightOK;
     QPushButton* rightCancel;
     QStackedWidget* subMenuContainer;
-    QList<QWidget*> subMenus;
+    QList<AlmondSubMenu*> subMenus;
     QStateMachine* states;
 public:
     AlmondMenuWidget(QWidget* parent = nullptr);
+    ~AlmondMenuWidget(void);
 
     void setMainMenu(QWidget* mainMenu);
-    void addSubMenu(QWidget* subMenu);
+    AlmondSubMenu* addSubMenu(QWidget* subMenu);
 
     virtual QSize sizeHint(void) const override;
     virtual QSize minimumSizeHint(void) const override;

+ 8 - 2
BackgroundTask.cpp

@@ -20,7 +20,10 @@ void ImageExportTask::run(void)
         alm::exportImage(iei, [this](float percentage) {
             emit progress(percentage);
         }, stopCallback);
-        emit finished(true, "Image successfully exported.");
+        if (!stopCallback())
+            emit finished(true, "Image successfully exported.");
+        else
+            emit finished(false, "Image export cancelled.");
     }
     catch (alm::ImageExportException& ex) {
         emit finished(false, QString("Error during image export: ") + ex.what());
@@ -46,7 +49,10 @@ void VideoExportTask::run(void)
             emit progress(mvpi.progress);
         });
         mvg.generate(generator);
-        emit finished(true, "Video successfully exported.");
+        if (!stopCallback())
+            emit finished(true, "Video successfully exported.");
+        else
+            emit finished(false, "Video export cancelled.");
     }
     catch (alm::VideoExportException& ex) {
         emit finished(false, QString("Error during video export: ") + ex.what());

+ 4 - 2
CMakeLists.txt

@@ -24,11 +24,13 @@ FILE(GLOB AlmondHeaders *.h)
 
 
 IF (WIN32)
-    add_executable(Almond WIN32 ${AlmondSources} Almond.qrc splash.qrc icon.rc)
+    add_executable(Almond WIN32 ${AlmondSources} Almond.qrc splash.qrc icon.rc ${AlmondHeaders})
 ELSE()
-    add_executable(Almond ${AlmondSources} Almond.qrc splash.qrc)
+    add_executable(Almond ${AlmondSources} Almond.qrc splash.qrc ${AlmondHeaders})
 ENDIF()
 
+target_include_directories(Almond PUBLIC .)
+
 add_subdirectory(libalmond)
 
 target_include_directories(Almond SYSTEM PUBLIC ${FFMPEG_INCLUDE_DIRS})

+ 13 - 1
ExportImageMenu.ui

@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>253</width>
+    <width>285</width>
     <height>330</height>
    </rect>
   </property>
@@ -14,6 +14,18 @@
    <string>Form</string>
   </property>
   <layout class="QFormLayout" name="formLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
    <item row="0" column="0" colspan="2">
     <widget class="QLabel" name="titleLbl">
      <property name="font">

+ 9 - 0
ExportVideoMenu.cpp

@@ -2,6 +2,7 @@
 #include "ui_ExportVideoMenu.h"
 #include <QIntValidator>
 #include <QDoubleValidator>
+#include <QFileDialog>
 
 ExportVideoMenu::ExportVideoMenu(QWidget *parent) :
     QWidget{ parent },
@@ -112,3 +113,11 @@ void ExportVideoMenu::setEndViewport(const mnd::MandelViewport& mv)
     ui->endH->setText(QString::fromStdString(mnd::toString(mv.height)));
 }
 
+
+void ExportVideoMenu::on_pathBtn_clicked()
+{
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr("AVI video (*.avi);;MP4 video (*.mp4);;All Files (*)"));
+    ui->pathTxt->setText(saveAs);
+}

+ 3 - 0
ExportVideoMenu.h

@@ -21,6 +21,9 @@ public:
     ExportVideoInfo getInfo(void) const;
     void setStartViewport(const mnd::MandelViewport& mv);
     void setEndViewport(const mnd::MandelViewport& mv);
+private slots:
+    void on_pathBtn_clicked();
+
 private:
     Ui::ExportVideoMenu *ui;
 };

+ 23 - 3
ExportVideoMenu.ui

@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>328</width>
+    <width>614</width>
     <height>543</height>
    </rect>
   </property>
@@ -14,6 +14,18 @@
    <string>Frame</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
    <item>
     <widget class="QLabel" name="titleLbl">
      <property name="font">
@@ -37,6 +49,14 @@
       </sizepolicy>
      </property>
      <widget class="QWidget" name="toolBoxPage1">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>586</width>
+        <height>640</height>
+       </rect>
+      </property>
       <property name="sizePolicy">
        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
         <horstretch>0</horstretch>
@@ -148,8 +168,8 @@
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>310</width>
-        <height>442</height>
+        <width>614</width>
+        <height>373</height>
        </rect>
       </property>
       <property name="sizePolicy">

+ 20 - 0
GradientMenu.cpp

@@ -0,0 +1,20 @@
+#include "GradientMenu.h"
+#include "ui_GradientMenu.h"
+
+GradientMenu::GradientMenu(QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::GradientMenu)
+{
+    ui->setupUi(this);
+    ui->gradientWidget->setGradient(
+    {
+                    {0.1, QColor{10, 200, 20}},
+                    {0.7, QColor{100, 20, 120}}
+    }
+    );
+}
+
+GradientMenu::~GradientMenu()
+{
+    delete ui;
+}

+ 22 - 0
GradientMenu.h

@@ -0,0 +1,22 @@
+#ifndef GRADIENTMENU_H
+#define GRADIENTMENU_H
+
+#include <QWidget>
+
+namespace Ui {
+class GradientMenu;
+}
+
+class GradientMenu : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit GradientMenu(QWidget *parent = nullptr);
+    ~GradientMenu();
+
+private:
+    Ui::GradientMenu *ui;
+};
+
+#endif // GRADIENTMENU_H

+ 58 - 0
GradientMenu.ui

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GradientMenu</class>
+ <widget class="QWidget" name="GradientMenu">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>302</width>
+    <height>508</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Select Gradient</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLabel" name="titleLbl">
+     <property name="font">
+      <font>
+       <pointsize>14</pointsize>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Select Gradient</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="GradientWidget" name="gradientWidget" native="true"/>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>GradientWidget</class>
+   <extends>QWidget</extends>
+   <header>GradientWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

+ 333 - 1
GradientWidget.cpp

@@ -1,6 +1,338 @@
 #include "GradientWidget.h"
 
-GradientWidget::GradientWidget(QWidget *parent) : QWidget(parent)
+#include <QPaintEvent>
+#include <QPainter>
+#include <QStyleOptionSlider>
+#include <QColorDialog>
+#include <QStyle>
+
+#include <algorithm>
+#include <cmath>
+
+GradientWidget::GradientWidget(QWidget* parent) :
+    QWidget{ parent }
+{
+    dragging = false;
+    selectedHandle = -1;
+    mouseOver = -1;
+
+    setMouseTracking(true);
+}
+
+
+const QVector<std::pair<float, QColor>>&
+GradientWidget::getGradient(void) const
+{
+    return points;
+}
+
+
+void GradientWidget::setGradient(QVector<std::pair<float, QColor>> vec)
+{
+    points = std::move(vec);
+}
+
+
+QColor lerp(const QColor& a, const QColor& b, float v)
+{
+    float ar = a.redF();
+    float ag = a.greenF();
+    float ab = a.blueF();
+
+    float br = b.redF();
+    float bg = b.greenF();
+    float bb = b.blueF();
+
+    const float gamma = 2.2;
+
+    ar = std::pow(ar, gamma);
+    ag = std::pow(ag, gamma);
+    ab = std::pow(ab, gamma);
+
+    br = std::pow(br, gamma);
+    bg = std::pow(bg, gamma);
+    bb = std::pow(bb, gamma);
+
+    float nr = br * v + (1 - v) * ar;
+    float ng = bg * v + (1 - v) * ag;
+    float nb = bb * v + (1 - v) * ab;
+
+    nr = std::pow(nr, 1/gamma);
+    ng = std::pow(ng, 1/gamma);
+    nb = std::pow(nb, 1/gamma);
+
+    return QColor{ int(255 * nr), int(255 * ng), int(255 * nb) };
+}
+
+void GradientWidget::paintEvent(QPaintEvent* e)
+{
+    QPainter painter{ this };
+
+    std::vector<int> orderedIndices(points.size());
+    for (int i = 0; i < points.size(); i++)
+        orderedIndices.push_back(i);
+
+    std::sort(orderedIndices.begin(), orderedIndices.end(),
+        [this] (int l, int r) {
+            return points[l].first < points[r].first;
+        });
+
+    QRect gradientRect = getGradientRect();
+    QStyleOption frameOptions;
+    frameOptions.init(this);
+    frameOptions.rect = gradientRect;
+    frameOptions.state |= QStyle::State_Sunken;
+    style()->drawPrimitive(
+                QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
+
+    int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
+    int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
+    if (fhmargins == -1) {
+        fhmargins = fvmargins = 3;
+    }
+
+    QLinearGradient gradient;
+    gradient.setStart(0, gradientRect.top());
+    gradient.setFinalStop(0, gradientRect.bottom());
+
+    // adjust rect to have small margins, so the frame
+    // around the gradient is visible
+    gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
+    float lastPoint = 0;
+    QColor lastColor = QColor{ 0, 0, 0 };
+    // traverse gradient in order and interpolate in linear
+    // RGB space to avoid interpolating in sRGB
+    for (int i = 0; i < orderedIndices.size(); i++) {
+        int index = orderedIndices[i];
+        auto& [point, color] = points[index];
+        int m = 5;
+        if (i > 0) {
+            for (int i = 0; i < m; i++) {
+                float v = float(i) / m;
+                gradient.setColorAt(lastPoint + (point - lastPoint) / m * i,
+                                    lerp(lastColor, color, v));
+            }
+        }
+        gradient.setColorAt(point, color);
+        lastPoint = point;
+        lastColor = color;
+    }
+    QBrush brush{ gradient };
+    painter.fillRect(gradientRect, brush);
+
+    QStyleOption so;
+    so.init(this);
+    int index = 0;
+    for (auto& [point, color] : points) {
+        QRect r = getHandleRect(index);
+        so.rect = r;
+        if (dragging && selectedHandle == index)
+            so.state |= QStyle::State_Sunken;
+        else
+            so.state &= ~QStyle::State_Sunken;
+        if (mouseOver == index)
+            so.state |= QStyle::State_MouseOver;
+        else
+            so.state &= ~QStyle::State_MouseOver;
+        so.palette.setColor(QPalette::ColorRole::Button, color);
+        so.palette.setColor(QPalette::ColorRole::Background, color);
+        style()->drawPrimitive(QStyle::PrimitiveElement::PE_PanelButtonTool, &so, &painter, this);
+        index++;
+    }
+    /*for (auto&[point, color] : points) {
+        QStyleOptionSlider qsos;
+        qsos.rect = QRect{ 100, static_cast<int>(point * height() - 10), 20, 20 };
+        qsos.orientation = Qt::Vertical;
+        qsos.subControls = QStyle::SC_SliderHandle;
+        if (selectedHandle == index) {
+            qsos.state |= QStyle::State_Sunken;
+        }
+        else {
+            qsos.state &= ~QStyle::State_Sunken;
+        }
+        style()->drawComplexControl(QStyle::CC_Slider, &qsos, &painter, this);
+        index++;
+    }*/
+    /*
+
+    QPen pen(Qt::red);
+    pen.setWidth(10);
+    painter.setPen(pen);
+    painter.drawRect(30, 30, 50, 50);
+
+    painter.fillRect(QRect(0, 0, width(), height()), QColor{ 100, 20, 20 });
+    qDebug(std::to_string(width()).c_str());
+    */
+}
+
+
+void GradientWidget::mousePressEvent(QMouseEvent* e)
+{
+    int handle = handleAtPos(e->pos());
+
+    if (handle != -1) {
+        selectedHandle = handle;
+        dragging = true;
+        selectOffsetY = e->y() - gradValToHandleY(
+                    points[handle].first);
+        update();
+        e->accept();
+    }
+    else {
+        e->ignore();
+    }
+}
+
+
+void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
+{
+    if (dragging) {
+        dragging = false;
+        selectedHandle = -1;
+        update();
+        e->accept();
+    }
+    else {
+        e->ignore();
+    }
+}
+
+
+void GradientWidget::mouseMoveEvent(QMouseEvent* e)
 {
+    if (dragging) {
+        float newVal = handleYToGradVal(e->y() - selectOffsetY);
+        newVal = std::clamp(newVal, 0.0f, 1.0f);
+        points[selectedHandle].first = newVal;
+        update();
+        e->accept();
+    }
+    else {
+        int handle = handleAtPos(e->pos());
+        bool needsUpdate = false;
+        if (mouseOver != handle)
+            needsUpdate = true;
+        mouseOver = handle;
+        e->accept();
+        if (needsUpdate)
+            update();
+    }
+}
+
+
+void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
+{
+    int handle = handleAtPos(e->pos());
+    if (handle != -1) {
+        QColor current = points.at(handle).second;
+        QColor newColor = QColorDialog::getColor(current,
+                                                 this,
+                                                 tr("Pick Color"));
+        if (newColor.isValid()) {
+            points[handle].second = newColor;
+            update();
+        }
+    }
+    else {
+        e->ignore();
+    }
+}
 
+
+QSize GradientWidget::minimumSizeHint(void) const
+{
+    int spacing = this->style()->pixelMetric(
+                QStyle::PM_LayoutHorizontalSpacing);
+    if (spacing == -1) {
+        spacing = this->style()->layoutSpacing(
+                    QSizePolicy::Frame,
+                    QSizePolicy::PushButton,
+                    Qt::Horizontal);
+    }
+    return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
+}
+
+
+QSize GradientWidget::sizeHint(void) const
+{
+    int spacing = this->style()->pixelMetric(
+                QStyle::PM_LayoutHorizontalSpacing);
+    if (spacing == -1) {
+        spacing = this->style()->layoutSpacing(
+                    QSizePolicy::Frame,
+                    QSizePolicy::PushButton,
+                    Qt::Horizontal);
+    }
+    return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
+}
+
+
+QRect GradientWidget::getGradientRect(void) const
+{
+    int left, top, right, bottom;
+    getContentsMargins(&left, &top, &right, &bottom);
+    int spacing = this->style()->pixelMetric(
+                QStyle::PM_LayoutHorizontalSpacing);
+    if (spacing == -1) {
+        spacing = this->style()->layoutSpacing(
+                    QSizePolicy::Frame,
+                    QSizePolicy::PushButton,
+                    Qt::Horizontal);
+    }
+    top += handleHeight / 2;
+    bottom += handleHeight / 2;
+    return  QRect{ left, top,
+                width() - left - right - handleWidth - spacing,
+                height() - bottom - top };
 }
+
+
+QRect GradientWidget::getHandleRect(int index) const
+{
+    QRect handleArea = getHandleArea();
+    float y = handleArea.top() + points.at(index).first * handleArea.height();
+    return QRect {
+        handleArea.x(), int(y - handleHeight / 2),
+        handleWidth, handleHeight
+    };
+}
+
+
+QRect GradientWidget::getHandleArea(void) const
+{
+    int left, top, right, bottom;
+    getContentsMargins(&left, &top, &right, &bottom);
+    top += handleHeight / 2;
+    bottom += handleHeight / 2;
+    float y = top;
+    float x = width() - handleWidth - right;
+    return QRect {
+        int(x), top, handleWidth, height() - top - bottom
+    };
+}
+
+
+int GradientWidget::handleAtPos(QPoint pos) const
+{
+    for (int i = points.size() - 1; i >= 0; i--) {
+        QRect rect = getHandleRect(i);
+        if (rect.contains(pos)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+float GradientWidget::handleYToGradVal(float y) const
+{
+    QRect area = getHandleArea();
+    return (y - area.top()) / area.height();
+}
+
+
+float GradientWidget::gradValToHandleY(float v) const
+{
+    QRect area = getHandleArea();
+    return area.top() + v * area.height();
+}
+

+ 44 - 1
GradientWidget.h

@@ -2,15 +2,58 @@
 #define GRADIENTWIDGET_H
 
 #include <QWidget>
+#include <QLinearGradient>
 
-class GradientWidget : public QWidget
+#include <utility>
+
+class GradientWidget :
+    public QWidget
 {
     Q_OBJECT
+    QVector<std::pair<float, QColor>> points;
+
+    bool dragging;
+    int selectedHandle;
+    float selectOffsetY;
+
+    int mouseOver;
+
+    int handleWidth = 60;
+    int handleHeight = 30;
 public:
     explicit GradientWidget(QWidget *parent = nullptr);
 
+    const QVector<std::pair<float, QColor>>& getGradient(void) const;
+    void setGradient(QVector<std::pair<float, QColor>>);
+
+    void paintEvent(QPaintEvent* e) override;
+
+    void mousePressEvent(QMouseEvent* e) override;
+    void mouseReleaseEvent(QMouseEvent* e) override;
+    void mouseMoveEvent(QMouseEvent* e) override;
+    void mouseDoubleClickEvent(QMouseEvent* e) override;
+
+    QSize minimumSizeHint(void) const override;
+    QSize sizeHint(void) const override;
+
+protected:
+    /// \brief the area in which the gradient is displayed
+    QRect getGradientRect(void) const;
+
+    /// \brief the area in which the handle with index
+    /// \c index is displayed
+    QRect getHandleRect(int index) const;
+
+    /// \brief the area in which the handles can move around
+    QRect getHandleArea(void) const;
+
+    int handleAtPos(QPoint pos) const;
+
+    float handleYToGradVal(float y) const;
+    float gradValToHandleY(float v) const;
 signals:
 
 };
 
 #endif // GRADIENTWIDGET_H
+

+ 5 - 0
MandelWidget.cpp

@@ -2,6 +2,9 @@
 #include <cmath>
 #include <sstream>
 
+#include <QStyle>
+#include <QStyleOption>
+
 using namespace mnd;
 
 #include <cstdio>
@@ -757,6 +760,8 @@ void MandelWidget::drawRubberband(void)
     rubberbandPainter.setPen(pen);
 
     rubberbandPainter.drawRect(rubberband);
+    //QStyleOption so;
+    //style()->drawControl(QStyle::CE_RubberBand, &so, &rubberbandPainter, this);
 }
 
 

+ 0 - 1
main.cpp

@@ -3,7 +3,6 @@
 #include <QPixmap>
 #include <QScreen>
 #include <QSplashScreen>
-//#include <QStyleFactory>
 #include <cmath>
 
 int main(int argc, char *argv[])