1
0

GradientWidget.cpp 13 KB

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