GradientWidget.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  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. maxValue = 1.0f;
  16. colorPicker = new QColorDialog(this);
  17. colorPicker->setOption(QColorDialog::NoButtons);
  18. connect(colorPicker, &QColorDialog::currentColorChanged, this, &GradientWidget::selectedColorChanged);
  19. setMouseTracking(true);
  20. }
  21. /*const QVector<QPair<float, QColor>>& GradientWidget::getGradient(void) const
  22. {
  23. return points;
  24. }*/
  25. const Gradient& GradientWidget::getGradient(void) const
  26. {
  27. return gradient;
  28. }
  29. void GradientWidget::setGradient(Gradient gr)
  30. {
  31. gradient = std::move(gr);
  32. points = gradient.getPoints();
  33. maxValue = gradient.getMax();
  34. }
  35. QColor lerp(const QColor& a, const QColor& b, float v)
  36. {
  37. float ar = a.redF();
  38. float ag = a.greenF();
  39. float ab = a.blueF();
  40. float br = b.redF();
  41. float bg = b.greenF();
  42. float bb = b.blueF();
  43. const float gamma = 2.2;
  44. ar = std::pow(ar, gamma);
  45. ag = std::pow(ag, gamma);
  46. ab = std::pow(ab, gamma);
  47. br = std::pow(br, gamma);
  48. bg = std::pow(bg, gamma);
  49. bb = std::pow(bb, gamma);
  50. float nr = br * v + (1 - v) * ar;
  51. float ng = bg * v + (1 - v) * ag;
  52. float nb = bb * v + (1 - v) * ab;
  53. nr = std::pow(nr, 1/gamma);
  54. ng = std::pow(ng, 1/gamma);
  55. nb = std::pow(nb, 1/gamma);
  56. return QColor{ int(255 * nr), int(255 * ng), int(255 * nb) };
  57. }
  58. void GradientWidget::updateGradient(void)
  59. {
  60. gradient = Gradient{ points, maxValue };
  61. }
  62. QColor GradientWidget::colorAtY(float y)
  63. {
  64. float v = handleYToGradVal(y);
  65. QColor up = QColor(QColor::Invalid);
  66. QColor down = QColor(QColor::Invalid);
  67. float upv = 0;
  68. float downv = 1;
  69. for (const auto& [color, val] : points) {
  70. if (val >= upv && val < v) {
  71. upv = val;
  72. up = QColor(color.r, color.g, color.b);
  73. }
  74. if (val <= downv && val > v) {
  75. downv = val;
  76. down = QColor(color.r, color.g, color.b);
  77. }
  78. }
  79. if (!up.isValid())
  80. return down;
  81. if (!down.isValid())
  82. return up;
  83. return lerp(up, down, (v - upv) / (downv - upv));
  84. }
  85. void GradientWidget::paintEvent(QPaintEvent* e)
  86. {
  87. QPainter painter{ this };
  88. QRect gradientRect = getGradientRect();
  89. QStyleOption frameOptions;
  90. frameOptions.init(this);
  91. frameOptions.rect = gradientRect;
  92. frameOptions.state |= QStyle::State_Sunken;
  93. style()->drawPrimitive(
  94. QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
  95. int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
  96. int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
  97. if (fhmargins == -1) {
  98. fhmargins = fvmargins = 3;
  99. }
  100. QLinearGradient linGrad;
  101. linGrad.setStart(0, gradientRect.top());
  102. linGrad.setFinalStop(0, gradientRect.bottom());
  103. const int stops = this->height() / 5;
  104. /*for (int i = 0; i < stops; i++) {
  105. auto col = gradient.get(float(i) / stops * gradient.getMax());
  106. linGrad.setColorAt(float(i) / stops, QColor{ col.r, col.g, col.b });
  107. }*/
  108. for (const auto& [col, at] : points) {
  109. linGrad.setColorAt(at / maxValue, QColor{ col.r, col.g, col.b });
  110. }
  111. // adjust rect to have small margins, so the frame
  112. // around the gradient is visible
  113. gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
  114. float lastPoint = 0;
  115. QColor lastColor = QColor{ 0, 0, 0 };
  116. /*std::vector<int> orderedIndices(points.size());
  117. for (int i = 0; i < points.size(); i++)
  118. orderedIndices.push_back(i);
  119. std::sort(orderedIndices.begin(), orderedIndices.end(),
  120. [this] (int l, int r) {
  121. return points[l].first < points[r].first;
  122. });
  123. // traverse gradient in order and interpolate in linear
  124. // RGB space to avoid interpolating in sRGB
  125. for (int i = 0; i < orderedIndices.size(); i++) {
  126. int index = orderedIndices[i];
  127. auto& [point, color] = points[index];
  128. int m = 17;
  129. if (i > 0) {
  130. for (int i = 0; i < m; i++) {
  131. float v = float(i) / m;
  132. gradient.setColorAt(lastPoint + (point - lastPoint) / m * i,
  133. lerp(lastColor, color, v));
  134. }
  135. }
  136. gradient.setColorAt(point, color);
  137. lastPoint = point;
  138. lastColor = color;
  139. }*/
  140. QBrush brush{ linGrad };
  141. painter.fillRect(gradientRect, brush);
  142. int index = 0;
  143. for (auto& [color, point] : points) {
  144. QRect r = getHandleRect(index);
  145. /*QStyleOptionButton so;
  146. so.init(this);
  147. so.rect = r;
  148. if (dragging && selectedHandle == index)
  149. so.state |= QStyle::State_Sunken;
  150. else if (selectedHandle == index)
  151. so.state |= QStyle::State_HasFocus;
  152. else
  153. so.state &= ~QStyle::State_Sunken & ~QStyle::State_HasFocus;
  154. if (mouseOver == index)
  155. so.state |= QStyle::State_MouseOver;
  156. else
  157. so.state &= ~QStyle::State_MouseOver;
  158. so.palette.setColor(QPalette::ColorRole::Button, color);
  159. style()->drawControl(QStyle::ControlElement::CE_PushButton, &so, &painter, this);*/
  160. int hs = HandleState::HANDLE_NORMAL;
  161. if (dragging && selectedHandle == index)
  162. hs |= HANDLE_DOWN;
  163. if (mouseOver == index)
  164. hs |= HANDLE_MOUSEOVER;
  165. if (selectedHandle == index)
  166. hs |= HANDLE_SELECTED;
  167. paintHandle(painter, r, fromRGB(color), hs);
  168. index++;
  169. }
  170. /*for (auto&[point, color] : points) {
  171. QStyleOptionSlider qsos;
  172. qsos.rect = QRect{ 100, static_cast<int>(point * height() - 10), 20, 20 };
  173. qsos.orientation = Qt::Vertical;
  174. qsos.subControls = QStyle::SC_SliderHandle;
  175. if (selectedHandle == index) {
  176. qsos.state |= QStyle::State_Sunken;
  177. }
  178. else {
  179. qsos.state &= ~QStyle::State_Sunken;
  180. }
  181. style()->drawComplexControl(QStyle::CC_Slider, &qsos, &painter, this);
  182. index++;
  183. }*/
  184. /*
  185. QPen pen(Qt::red);
  186. pen.setWidth(10);
  187. painter.setPen(pen);
  188. painter.drawRect(30, 30, 50, 50);
  189. painter.fillRect(QRect(0, 0, width(), height()), QColor{ 100, 20, 20 });
  190. qDebug(std::to_string(width()).c_str());
  191. */
  192. }
  193. void GradientWidget::paintHandle(QPainter& painter, const QRectF& pos,
  194. QColor c, int handleState)
  195. {
  196. const float lineWidth = 2;
  197. QPainterPath qpp = createSlideHandle(pos.width() - lineWidth, pos.height() - lineWidth);
  198. qpp.translate(pos.x() + lineWidth / 2, pos.y() + lineWidth / 2);
  199. if (handleState & HANDLE_SELECTED) {
  200. QColor absLighter;
  201. absLighter.setHsvF(c.hueF(), c.saturationF(), c.valueF() > 0.3 ? c.valueF() : 0.3);
  202. painter.setPen(QPen(QBrush(absLighter.lighter(130)), lineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  203. } else
  204. painter.setPen(QPen(QBrush(c.darker(200)), lineWidth / 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  205. painter.setRenderHint(QPainter::Antialiasing);
  206. QLinearGradient bevel{ 0, pos.top(), 0, pos.bottom() }; // top down linear gradient
  207. if (handleState & HANDLE_DOWN) {
  208. bevel.setColorAt(0, c.darker(120));
  209. bevel.setColorAt(1, c.lighter(120));
  210. }
  211. else if (handleState & HANDLE_MOUSEOVER) {
  212. bevel.setColorAt(0, c.lighter(130));
  213. bevel.setColorAt(1, c.darker(110));
  214. }
  215. else {
  216. bevel.setColorAt(0, c.lighter(120));
  217. bevel.setColorAt(1, c.darker(120));
  218. }
  219. painter.fillPath(qpp, QBrush(bevel));
  220. painter.drawPath(qpp);
  221. }
  222. void GradientWidget::mousePressEvent(QMouseEvent* e)
  223. {
  224. int handle = handleAtPos(e->pos());
  225. if (handle != -1) {
  226. selectedHandle = handle;
  227. dragging = true;
  228. selectOffsetY = e->y() - gradValToHandleY(
  229. points[handle].second);
  230. update();
  231. e->accept();
  232. }
  233. else {
  234. e->ignore();
  235. }
  236. }
  237. void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
  238. {
  239. if (dragging) {
  240. dragging = false;
  241. update();
  242. e->accept();
  243. }
  244. else {
  245. e->ignore();
  246. }
  247. }
  248. void GradientWidget::mouseMoveEvent(QMouseEvent* e)
  249. {
  250. if (dragging) {
  251. float newVal = handleYToGradVal(e->y() - selectOffsetY);
  252. newVal = std::clamp(newVal, 0.0f, maxValue);
  253. points[selectedHandle].second = newVal;
  254. updateGradient();
  255. update();
  256. emit gradientChanged();
  257. e->accept();
  258. }
  259. else {
  260. int handle = handleAtPos(e->pos());
  261. bool needsUpdate = false;
  262. if (mouseOver != handle)
  263. needsUpdate = true;
  264. mouseOver = handle;
  265. e->accept();
  266. if (needsUpdate)
  267. update();
  268. }
  269. }
  270. void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
  271. {
  272. auto torgb = [](const QColor& c) {
  273. return RGBColor{
  274. uint8_t(c.red()), uint8_t(c.green()), uint8_t(c.blue())
  275. };
  276. };
  277. QRect handleArea = getHandleArea();
  278. int handle = handleAtPos(e->pos());
  279. if (handle != -1) {
  280. RGBColor current = points.at(handle).first;
  281. /*QColor newColor = QColorDialog::getColor(current,
  282. this,
  283. tr("Pick Color"));*/
  284. selectedHandle = handle;
  285. colorPicker->setCurrentColor(fromRGB(current));
  286. colorPicker->exec();
  287. /*if (newColor.isValid()) {
  288. points[handle].first = torgb(newColor);
  289. update();
  290. emit gradientChanged();
  291. }*/
  292. e->accept();
  293. }
  294. else if (handleArea.contains(e->pos())) {
  295. float v = handleYToGradVal(e->pos().y());
  296. points.emplace_back(torgb(colorAtY(e->pos().y())), v);
  297. e->accept();
  298. updateGradient();
  299. update();
  300. }
  301. else {
  302. e->ignore();
  303. }
  304. }
  305. QSize GradientWidget::minimumSizeHint(void) const
  306. {
  307. int spacing = this->style()->pixelMetric(
  308. QStyle::PM_LayoutHorizontalSpacing);
  309. if (spacing == -1) {
  310. spacing = this->style()->layoutSpacing(
  311. QSizePolicy::Frame,
  312. QSizePolicy::PushButton,
  313. Qt::Horizontal);
  314. }
  315. return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
  316. }
  317. QSize GradientWidget::sizeHint(void) const
  318. {
  319. int spacing = this->style()->pixelMetric(
  320. QStyle::PM_LayoutHorizontalSpacing);
  321. if (spacing == -1) {
  322. spacing = this->style()->layoutSpacing(
  323. QSizePolicy::Frame,
  324. QSizePolicy::PushButton,
  325. Qt::Horizontal);
  326. }
  327. return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
  328. }
  329. void GradientWidget::selectedColorChanged(const QColor& newColor)
  330. {
  331. if (points.size() > selectedHandle) {
  332. points.at(selectedHandle).first = RGBColor {
  333. uint8_t(newColor.red()),
  334. uint8_t(newColor.green()),
  335. uint8_t(newColor.blue())
  336. };
  337. updateGradient();
  338. update();
  339. emit gradientChanged();
  340. }
  341. }
  342. QRect GradientWidget::getGradientRect(void) const
  343. {
  344. QMargins cm = contentsMargins();
  345. int top = cm.top();
  346. int bottom = cm.bottom();
  347. int left = cm.left();
  348. int right = cm.right();
  349. int spacing = this->style()->pixelMetric(
  350. QStyle::PM_LayoutHorizontalSpacing);
  351. if (spacing == -1) {
  352. spacing = this->style()->layoutSpacing(
  353. QSizePolicy::Frame,
  354. QSizePolicy::PushButton,
  355. Qt::Horizontal);
  356. }
  357. top += handleHeight / 2;
  358. bottom += handleHeight / 2;
  359. return QRect{ left, top,
  360. width() - left - right - handleWidth - spacing,
  361. height() - bottom - top };
  362. }
  363. QRect GradientWidget::getHandleRect(int index) const
  364. {
  365. QRect handleArea = getHandleArea();
  366. float y = handleArea.top() + points.at(index).second / maxValue * handleArea.height();
  367. return QRect {
  368. handleArea.x(), int(y - handleHeight / 2),
  369. handleWidth, handleHeight
  370. };
  371. }
  372. QRect GradientWidget::getHandleArea(void) const
  373. {
  374. QMargins cm = contentsMargins();
  375. int top = cm.top();
  376. int bottom = cm.bottom();
  377. int left = cm.left();
  378. int right = cm.right();
  379. top += handleHeight / 2;
  380. bottom += handleHeight / 2;
  381. float y = top;
  382. float x = width() - handleWidth - right;
  383. return QRect {
  384. int(x), top, handleWidth, height() - top - bottom
  385. };
  386. }
  387. int GradientWidget::handleAtPos(QPoint pos) const
  388. {
  389. for (int i = points.size() - 1; i >= 0; i--) {
  390. QRect rect = getHandleRect(i);
  391. if (rect.contains(pos)) {
  392. return i;
  393. }
  394. }
  395. return -1;
  396. }
  397. float GradientWidget::handleYToGradVal(float y) const
  398. {
  399. QRect area = getHandleArea();
  400. return maxValue * (y - area.top()) / area.height();
  401. }
  402. float GradientWidget::gradValToHandleY(float v) const
  403. {
  404. QRect area = getHandleArea();
  405. return area.top() + v / maxValue * area.height();
  406. }
  407. QPainterPath GradientWidget::createSlideHandle(float w, float h)
  408. {
  409. const float rounding = 4;
  410. QPainterPath qpp;
  411. QPolygonF qpf;
  412. qpf << QPointF{ 0, 0.5 * h }
  413. << QPointF{ 0.3 * w, h };
  414. qpp.moveTo(0, 0.5 * h);
  415. qpp.lineTo(0.3 * w, h);
  416. qpp.arcTo(w - rounding, h - rounding, rounding, rounding, -90, 90);
  417. qpp.arcTo(w - rounding, 0, rounding, rounding, 0, 90);
  418. qpp.lineTo(0.3 * w, 0);
  419. qpp.lineTo(0, 0.5 * h);
  420. return qpp;
  421. }