1
0

GradientWidget.cpp 9.7 KB

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