GradientWidget.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. int hs = HandleState::HANDLE_NORMAL;
  139. if (dragging && selectedHandle == index)
  140. hs |= HANDLE_DOWN;
  141. if (mouseOver == index)
  142. hs |= HANDLE_MOUSEOVER;
  143. if (selectedHandle == index)
  144. hs |= HANDLE_SELECTED;
  145. paintHandle(painter, r, color, hs);
  146. index++;
  147. }
  148. /*for (auto&[point, color] : points) {
  149. QStyleOptionSlider qsos;
  150. qsos.rect = QRect{ 100, static_cast<int>(point * height() - 10), 20, 20 };
  151. qsos.orientation = Qt::Vertical;
  152. qsos.subControls = QStyle::SC_SliderHandle;
  153. if (selectedHandle == index) {
  154. qsos.state |= QStyle::State_Sunken;
  155. }
  156. else {
  157. qsos.state &= ~QStyle::State_Sunken;
  158. }
  159. style()->drawComplexControl(QStyle::CC_Slider, &qsos, &painter, this);
  160. index++;
  161. }*/
  162. /*
  163. QPen pen(Qt::red);
  164. pen.setWidth(10);
  165. painter.setPen(pen);
  166. painter.drawRect(30, 30, 50, 50);
  167. painter.fillRect(QRect(0, 0, width(), height()), QColor{ 100, 20, 20 });
  168. qDebug(std::to_string(width()).c_str());
  169. */
  170. }
  171. void GradientWidget::paintHandle(QPainter& painter, const QRectF& pos,
  172. QColor c, int handleState)
  173. {
  174. const float lineWidth = 2;
  175. QPainterPath qpp = createSlideHandle(pos.width() - lineWidth, pos.height() - lineWidth);
  176. qpp.translate(pos.x() + lineWidth / 2, pos.y() + lineWidth / 2);
  177. if (handleState & HANDLE_SELECTED) {
  178. QColor absLighter;
  179. absLighter.setHsvF(c.hueF(), c.saturationF(), c.valueF() > 0.3 ? c.valueF() : 0.3);
  180. painter.setPen(QPen(QBrush(absLighter.lighter(130)), lineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  181. } else
  182. painter.setPen(QPen(QBrush(c.darker(200)), lineWidth / 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  183. painter.setRenderHint(QPainter::Antialiasing);
  184. QLinearGradient bevel{ 0, pos.top(), 0, pos.bottom() }; // top down linear gradient
  185. if (handleState & HANDLE_DOWN) {
  186. bevel.setColorAt(0, c.darker(120));
  187. bevel.setColorAt(1, c.lighter(120));
  188. }
  189. else if (handleState & HANDLE_MOUSEOVER) {
  190. bevel.setColorAt(0, c.lighter(130));
  191. bevel.setColorAt(1, c.darker(110));
  192. }
  193. else {
  194. bevel.setColorAt(0, c.lighter(120));
  195. bevel.setColorAt(1, c.darker(120));
  196. }
  197. painter.fillPath(qpp, QBrush(bevel));
  198. painter.drawPath(qpp);
  199. }
  200. void GradientWidget::mousePressEvent(QMouseEvent* e)
  201. {
  202. int handle = handleAtPos(e->pos());
  203. if (handle != -1) {
  204. selectedHandle = handle;
  205. dragging = true;
  206. selectOffsetY = e->y() - gradValToHandleY(
  207. points[handle].first);
  208. update();
  209. e->accept();
  210. }
  211. else {
  212. e->ignore();
  213. }
  214. }
  215. void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
  216. {
  217. if (dragging) {
  218. dragging = false;
  219. update();
  220. e->accept();
  221. }
  222. else {
  223. e->ignore();
  224. }
  225. }
  226. void GradientWidget::mouseMoveEvent(QMouseEvent* e)
  227. {
  228. if (dragging) {
  229. float newVal = handleYToGradVal(e->y() - selectOffsetY);
  230. newVal = std::clamp(newVal, 0.0f, 1.0f);
  231. points[selectedHandle].first = newVal;
  232. update();
  233. emit gradientChanged();
  234. e->accept();
  235. }
  236. else {
  237. int handle = handleAtPos(e->pos());
  238. bool needsUpdate = false;
  239. if (mouseOver != handle)
  240. needsUpdate = true;
  241. mouseOver = handle;
  242. e->accept();
  243. if (needsUpdate)
  244. update();
  245. }
  246. }
  247. void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
  248. {
  249. QRect handleArea = getHandleArea();
  250. int handle = handleAtPos(e->pos());
  251. if (handle != -1) {
  252. QColor current = points.at(handle).second;
  253. QColor newColor = QColorDialog::getColor(current,
  254. this,
  255. tr("Pick Color"));
  256. if (newColor.isValid()) {
  257. points[handle].second = newColor;
  258. update();
  259. emit gradientChanged();
  260. }
  261. e->accept();
  262. }
  263. else if (handleArea.contains(e->pos())) {
  264. float v = handleYToGradVal(e->pos().y());
  265. points.append({ v, colorAtY(e->pos().y()) });
  266. e->accept();
  267. update();
  268. }
  269. else {
  270. e->ignore();
  271. }
  272. }
  273. QSize GradientWidget::minimumSizeHint(void) const
  274. {
  275. int spacing = this->style()->pixelMetric(
  276. QStyle::PM_LayoutHorizontalSpacing);
  277. if (spacing == -1) {
  278. spacing = this->style()->layoutSpacing(
  279. QSizePolicy::Frame,
  280. QSizePolicy::PushButton,
  281. Qt::Horizontal);
  282. }
  283. return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
  284. }
  285. QSize GradientWidget::sizeHint(void) const
  286. {
  287. int spacing = this->style()->pixelMetric(
  288. QStyle::PM_LayoutHorizontalSpacing);
  289. if (spacing == -1) {
  290. spacing = this->style()->layoutSpacing(
  291. QSizePolicy::Frame,
  292. QSizePolicy::PushButton,
  293. Qt::Horizontal);
  294. }
  295. return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
  296. }
  297. QRect GradientWidget::getGradientRect(void) const
  298. {
  299. int left, top, right, bottom;
  300. getContentsMargins(&left, &top, &right, &bottom);
  301. int spacing = this->style()->pixelMetric(
  302. QStyle::PM_LayoutHorizontalSpacing);
  303. if (spacing == -1) {
  304. spacing = this->style()->layoutSpacing(
  305. QSizePolicy::Frame,
  306. QSizePolicy::PushButton,
  307. Qt::Horizontal);
  308. }
  309. top += handleHeight / 2;
  310. bottom += handleHeight / 2;
  311. return QRect{ left, top,
  312. width() - left - right - handleWidth - spacing,
  313. height() - bottom - top };
  314. }
  315. QRect GradientWidget::getHandleRect(int index) const
  316. {
  317. QRect handleArea = getHandleArea();
  318. float y = handleArea.top() + points.at(index).first * handleArea.height();
  319. return QRect {
  320. handleArea.x(), int(y - handleHeight / 2),
  321. handleWidth, handleHeight
  322. };
  323. }
  324. QRect GradientWidget::getHandleArea(void) const
  325. {
  326. int left, top, right, bottom;
  327. getContentsMargins(&left, &top, &right, &bottom);
  328. top += handleHeight / 2;
  329. bottom += handleHeight / 2;
  330. float y = top;
  331. float x = width() - handleWidth - right;
  332. return QRect {
  333. int(x), top, handleWidth, height() - top - bottom
  334. };
  335. }
  336. int GradientWidget::handleAtPos(QPoint pos) const
  337. {
  338. for (int i = points.size() - 1; i >= 0; i--) {
  339. QRect rect = getHandleRect(i);
  340. if (rect.contains(pos)) {
  341. return i;
  342. }
  343. }
  344. return -1;
  345. }
  346. float GradientWidget::handleYToGradVal(float y) const
  347. {
  348. QRect area = getHandleArea();
  349. return (y - area.top()) / area.height();
  350. }
  351. float GradientWidget::gradValToHandleY(float v) const
  352. {
  353. QRect area = getHandleArea();
  354. return area.top() + v * area.height();
  355. }
  356. QPainterPath GradientWidget::createSlideHandle(float w, float h)
  357. {
  358. const float rounding = 4;
  359. QPainterPath qpp;
  360. QPolygonF qpf;
  361. qpf << QPointF{ 0, 0.5 * h }
  362. << QPointF{ 0.3 * w, h };
  363. qpp.moveTo(0, 0.5 * h);
  364. qpp.lineTo(0.3 * w, h);
  365. qpp.arcTo(w - rounding, h - rounding, rounding, rounding, -90, 90);
  366. qpp.arcTo(w - rounding, 0, rounding, rounding, 0, 90);
  367. qpp.lineTo(0.3 * w, 0);
  368. qpp.lineTo(0, 0.5 * h);
  369. return qpp;
  370. }