GradientWidget.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. #include "GradientWidget.h"
  2. #include <QPaintEvent>
  3. #include <QPainter>
  4. #include <QStyleOptionSlider>
  5. #include <QColorDialog>
  6. #include <QStyle>
  7. #include <algorithm>
  8. #include <cmath>
  9. GradientWidget::GradientWidget(QWidget* parent) :
  10. QWidget{ parent }
  11. {
  12. dragging = false;
  13. selectedHandle = -1;
  14. mouseOver = -1;
  15. setMouseTracking(true);
  16. }
  17. const QVector<std::pair<float, QColor>>&
  18. GradientWidget::getGradient(void) const
  19. {
  20. return points;
  21. }
  22. void GradientWidget::setGradient(QVector<std::pair<float, QColor>> vec)
  23. {
  24. points = std::move(vec);
  25. }
  26. QColor lerp(const QColor& a, const QColor& b, float v)
  27. {
  28. float ar = a.redF();
  29. float ag = a.greenF();
  30. float ab = a.blueF();
  31. float br = b.redF();
  32. float bg = b.greenF();
  33. float bb = b.blueF();
  34. const float gamma = 2.2;
  35. ar = std::pow(ar, gamma);
  36. ag = std::pow(ag, gamma);
  37. ab = std::pow(ab, gamma);
  38. br = std::pow(br, gamma);
  39. bg = std::pow(bg, gamma);
  40. bb = std::pow(bb, gamma);
  41. float nr = br * v + (1 - v) * ar;
  42. float ng = bg * v + (1 - v) * ag;
  43. float nb = bb * v + (1 - v) * ab;
  44. nr = std::pow(nr, 1/gamma);
  45. ng = std::pow(ng, 1/gamma);
  46. nb = std::pow(nb, 1/gamma);
  47. return QColor{ int(255 * nr), int(255 * ng), int(255 * nb) };
  48. }
  49. void GradientWidget::paintEvent(QPaintEvent* e)
  50. {
  51. QPainter painter{ this };
  52. std::vector<int> orderedIndices(points.size());
  53. for (int i = 0; i < points.size(); i++)
  54. orderedIndices.push_back(i);
  55. std::sort(orderedIndices.begin(), orderedIndices.end(),
  56. [this] (int l, int r) {
  57. return points[l].first < points[r].first;
  58. });
  59. QRect gradientRect = getGradientRect();
  60. QStyleOption frameOptions;
  61. frameOptions.init(this);
  62. frameOptions.rect = gradientRect;
  63. frameOptions.state |= QStyle::State_Sunken;
  64. style()->drawPrimitive(
  65. QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
  66. int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
  67. int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
  68. if (fhmargins == -1) {
  69. fhmargins = fvmargins = 3;
  70. }
  71. QLinearGradient gradient;
  72. gradient.setStart(0, gradientRect.top());
  73. gradient.setFinalStop(0, gradientRect.bottom());
  74. // adjust rect to have small margins, so the frame
  75. // around the gradient is visible
  76. gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
  77. float lastPoint = 0;
  78. QColor lastColor = QColor{ 0, 0, 0 };
  79. // traverse gradient in order and interpolate in linear
  80. // RGB space to avoid interpolating in sRGB
  81. for (int i = 0; i < orderedIndices.size(); i++) {
  82. int index = orderedIndices[i];
  83. auto& [point, color] = points[index];
  84. int m = 5;
  85. if (i > 0) {
  86. for (int i = 0; i < m; i++) {
  87. float v = float(i) / m;
  88. gradient.setColorAt(lastPoint + (point - lastPoint) / m * i,
  89. lerp(lastColor, color, v));
  90. }
  91. }
  92. gradient.setColorAt(point, color);
  93. lastPoint = point;
  94. lastColor = color;
  95. }
  96. QBrush brush{ gradient };
  97. painter.fillRect(gradientRect, brush);
  98. QStyleOption so;
  99. so.init(this);
  100. int index = 0;
  101. for (auto& [point, color] : points) {
  102. QRect r = getHandleRect(index);
  103. so.rect = r;
  104. if (dragging && selectedHandle == index)
  105. so.state |= QStyle::State_Sunken;
  106. else
  107. so.state &= ~QStyle::State_Sunken;
  108. if (mouseOver == index)
  109. so.state |= QStyle::State_MouseOver;
  110. else
  111. so.state &= ~QStyle::State_MouseOver;
  112. so.palette.setColor(QPalette::ColorRole::Button, color);
  113. so.palette.setColor(QPalette::ColorRole::Background, color);
  114. style()->drawPrimitive(QStyle::PrimitiveElement::PE_PanelButtonTool, &so, &painter, this);
  115. index++;
  116. }
  117. /*for (auto&[point, color] : points) {
  118. QStyleOptionSlider qsos;
  119. qsos.rect = QRect{ 100, static_cast<int>(point * height() - 10), 20, 20 };
  120. qsos.orientation = Qt::Vertical;
  121. qsos.subControls = QStyle::SC_SliderHandle;
  122. if (selectedHandle == index) {
  123. qsos.state |= QStyle::State_Sunken;
  124. }
  125. else {
  126. qsos.state &= ~QStyle::State_Sunken;
  127. }
  128. style()->drawComplexControl(QStyle::CC_Slider, &qsos, &painter, this);
  129. index++;
  130. }*/
  131. /*
  132. QPen pen(Qt::red);
  133. pen.setWidth(10);
  134. painter.setPen(pen);
  135. painter.drawRect(30, 30, 50, 50);
  136. painter.fillRect(QRect(0, 0, width(), height()), QColor{ 100, 20, 20 });
  137. qDebug(std::to_string(width()).c_str());
  138. */
  139. }
  140. void GradientWidget::mousePressEvent(QMouseEvent* e)
  141. {
  142. int handle = handleAtPos(e->pos());
  143. if (handle != -1) {
  144. selectedHandle = handle;
  145. dragging = true;
  146. selectOffsetY = e->y() - gradValToHandleY(
  147. points[handle].first);
  148. update();
  149. e->accept();
  150. }
  151. else {
  152. e->ignore();
  153. }
  154. }
  155. void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
  156. {
  157. if (dragging) {
  158. dragging = false;
  159. selectedHandle = -1;
  160. update();
  161. e->accept();
  162. }
  163. else {
  164. e->ignore();
  165. }
  166. }
  167. void GradientWidget::mouseMoveEvent(QMouseEvent* e)
  168. {
  169. if (dragging) {
  170. float newVal = handleYToGradVal(e->y() - selectOffsetY);
  171. newVal = std::clamp(newVal, 0.0f, 1.0f);
  172. points[selectedHandle].first = newVal;
  173. update();
  174. e->accept();
  175. }
  176. else {
  177. int handle = handleAtPos(e->pos());
  178. bool needsUpdate = false;
  179. if (mouseOver != handle)
  180. needsUpdate = true;
  181. mouseOver = handle;
  182. e->accept();
  183. if (needsUpdate)
  184. update();
  185. }
  186. }
  187. void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
  188. {
  189. int handle = handleAtPos(e->pos());
  190. if (handle != -1) {
  191. QColor current = points.at(handle).second;
  192. QColor newColor = QColorDialog::getColor(current,
  193. this,
  194. tr("Pick Color"));
  195. if (newColor.isValid()) {
  196. points[handle].second = newColor;
  197. update();
  198. }
  199. }
  200. else {
  201. e->ignore();
  202. }
  203. }
  204. QSize GradientWidget::minimumSizeHint(void) const
  205. {
  206. int spacing = this->style()->pixelMetric(
  207. QStyle::PM_LayoutHorizontalSpacing);
  208. if (spacing == -1) {
  209. spacing = this->style()->layoutSpacing(
  210. QSizePolicy::Frame,
  211. QSizePolicy::PushButton,
  212. Qt::Horizontal);
  213. }
  214. return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
  215. }
  216. QSize GradientWidget::sizeHint(void) const
  217. {
  218. int spacing = this->style()->pixelMetric(
  219. QStyle::PM_LayoutHorizontalSpacing);
  220. if (spacing == -1) {
  221. spacing = this->style()->layoutSpacing(
  222. QSizePolicy::Frame,
  223. QSizePolicy::PushButton,
  224. Qt::Horizontal);
  225. }
  226. return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
  227. }
  228. QRect GradientWidget::getGradientRect(void) const
  229. {
  230. int left, top, right, bottom;
  231. getContentsMargins(&left, &top, &right, &bottom);
  232. int spacing = this->style()->pixelMetric(
  233. QStyle::PM_LayoutHorizontalSpacing);
  234. if (spacing == -1) {
  235. spacing = this->style()->layoutSpacing(
  236. QSizePolicy::Frame,
  237. QSizePolicy::PushButton,
  238. Qt::Horizontal);
  239. }
  240. top += handleHeight / 2;
  241. bottom += handleHeight / 2;
  242. return QRect{ left, top,
  243. width() - left - right - handleWidth - spacing,
  244. height() - bottom - top };
  245. }
  246. QRect GradientWidget::getHandleRect(int index) const
  247. {
  248. QRect handleArea = getHandleArea();
  249. float y = handleArea.top() + points.at(index).first * handleArea.height();
  250. return QRect {
  251. handleArea.x(), int(y - handleHeight / 2),
  252. handleWidth, handleHeight
  253. };
  254. }
  255. QRect GradientWidget::getHandleArea(void) const
  256. {
  257. int left, top, right, bottom;
  258. getContentsMargins(&left, &top, &right, &bottom);
  259. top += handleHeight / 2;
  260. bottom += handleHeight / 2;
  261. float y = top;
  262. float x = width() - handleWidth - right;
  263. return QRect {
  264. int(x), top, handleWidth, height() - top - bottom
  265. };
  266. }
  267. int GradientWidget::handleAtPos(QPoint pos) const
  268. {
  269. for (int i = points.size() - 1; i >= 0; i--) {
  270. QRect rect = getHandleRect(i);
  271. if (rect.contains(pos)) {
  272. return i;
  273. }
  274. }
  275. return -1;
  276. }
  277. float GradientWidget::handleYToGradVal(float y) const
  278. {
  279. QRect area = getHandleArea();
  280. return (y - area.top()) / area.height();
  281. }
  282. float GradientWidget::gradValToHandleY(float v) const
  283. {
  284. QRect area = getHandleArea();
  285. return area.top() + v * area.height();
  286. }