GradientWidget.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  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. using alm::Gradient;
  10. GradientWidget::GradientWidget(QWidget* parent) :
  11. QWidget{ parent }
  12. {
  13. dragging = false;
  14. selectedHandle = -1;
  15. mouseOver = -1;
  16. maxValue = 1.0f;
  17. colorPicker = new QColorDialog(this);
  18. colorPicker->setOption(QColorDialog::NoButtons);
  19. connect(colorPicker, &QColorDialog::currentColorChanged, this, &GradientWidget::selectedColorChanged);
  20. setMouseTracking(true);
  21. }
  22. const Gradient& GradientWidget::getGradient(void) const
  23. {
  24. return gradient;
  25. }
  26. void GradientWidget::setGradient(Gradient gr)
  27. {
  28. gradient = std::move(gr);
  29. points = gradient.getPoints();
  30. maxValue = gradient.getMax();
  31. updateGradient();
  32. }
  33. void GradientWidget::updateGradient(void)
  34. {
  35. gradient = Gradient{ points, maxValue };
  36. update();
  37. emit gradientChanged();
  38. }
  39. QColor GradientWidget::colorAtY(float y)
  40. {
  41. float v = handleYToGradVal(y);
  42. return fromRGB(gradient.get(v));
  43. }
  44. void GradientWidget::paintEvent(QPaintEvent* e)
  45. {
  46. QPainter painter{ this };
  47. QRect gradientRect = getGradientRect();
  48. QStyleOption frameOptions;
  49. frameOptions.init(this);
  50. frameOptions.rect = gradientRect;
  51. frameOptions.state |= QStyle::State_Sunken;
  52. style()->drawPrimitive(
  53. QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
  54. int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
  55. int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
  56. if (fhmargins == -1) {
  57. fhmargins = fvmargins = 3;
  58. }
  59. QLinearGradient linGrad;
  60. linGrad.setStart(0, gradientRect.top());
  61. linGrad.setFinalStop(0, gradientRect.bottom());
  62. for (auto it = points.cbegin(); it != points.cend(); it++) {
  63. float at = it->second;
  64. RGBColor col = it->first;
  65. linGrad.setColorAt(at / maxValue, QColor{ col.r, col.g, col.b });
  66. /*if (it + 1 != points.cend()) {
  67. float nat = (it + 1)->second;
  68. for (int i = 0; i < 5; i++) {
  69. float iat = at + i * (nat - at) / 5.0;
  70. linGrad.setColorAt(iat / maxValue, fromRGB(gradient.get(iat)));
  71. }
  72. }*/
  73. }
  74. // adjust rect to have small margins, so the frame
  75. // around the gradient is visible
  76. gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
  77. QBrush brush{ linGrad };
  78. painter.fillRect(gradientRect, brush);
  79. int index = 0;
  80. for (auto& [color, point] : points) {
  81. QRect r = getHandleRect(index);
  82. int hs = HandleState::HANDLE_NORMAL;
  83. if (dragging && selectedHandle == index)
  84. hs |= HANDLE_DOWN;
  85. if (mouseOver == index)
  86. hs |= HANDLE_MOUSEOVER;
  87. if (selectedHandle == index)
  88. hs |= HANDLE_SELECTED;
  89. paintHandle(painter, r, fromRGB(color), hs);
  90. index++;
  91. }
  92. }
  93. void GradientWidget::paintHandle(QPainter& painter, const QRectF& pos,
  94. QColor c, int handleState)
  95. {
  96. const float lineWidth = 2;
  97. QPainterPath qpp = createSlideHandle(pos.width() - lineWidth, pos.height() - lineWidth);
  98. qpp.translate(pos.x() + lineWidth / 2, pos.y() + lineWidth / 2);
  99. if (handleState & HANDLE_SELECTED) {
  100. QColor absLighter;
  101. absLighter.setHsvF(c.hueF(), c.saturationF(), c.valueF() > 0.3 ? c.valueF() : 0.3);
  102. painter.setPen(QPen(QBrush(absLighter.lighter(130)), lineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  103. } else
  104. painter.setPen(QPen(QBrush(c.darker(200)), lineWidth / 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  105. painter.setRenderHint(QPainter::Antialiasing);
  106. QLinearGradient bevel{ 0, pos.top(), 0, pos.bottom() }; // top down linear gradient
  107. if (handleState & HANDLE_DOWN) {
  108. bevel.setColorAt(0, c.darker(120));
  109. bevel.setColorAt(1, c.lighter(120));
  110. }
  111. else if (handleState & HANDLE_MOUSEOVER) {
  112. bevel.setColorAt(0, c.lighter(130));
  113. bevel.setColorAt(1, c.darker(110));
  114. }
  115. else {
  116. bevel.setColorAt(0, c.lighter(120));
  117. bevel.setColorAt(1, c.darker(120));
  118. }
  119. painter.fillPath(qpp, QBrush(bevel));
  120. painter.drawPath(qpp);
  121. }
  122. void GradientWidget::mousePressEvent(QMouseEvent* e)
  123. {
  124. int handle = handleAtPos(e->pos());
  125. if (handle != -1) {
  126. selectedHandle = handle;
  127. dragging = true;
  128. selectOffsetY = e->y() - gradValToHandleY(
  129. points[handle].second);
  130. update();
  131. e->accept();
  132. }
  133. else {
  134. e->ignore();
  135. }
  136. }
  137. void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
  138. {
  139. if (dragging) {
  140. dragging = false;
  141. update();
  142. e->accept();
  143. }
  144. else {
  145. e->ignore();
  146. }
  147. }
  148. void GradientWidget::mouseMoveEvent(QMouseEvent* e)
  149. {
  150. if (dragging) {
  151. float newVal = handleYToGradVal(e->y() - selectOffsetY);
  152. newVal = std::clamp(newVal, 0.0f, maxValue);
  153. points[selectedHandle].second = newVal;
  154. updateGradient();
  155. emit gradientChanged();
  156. e->accept();
  157. }
  158. else {
  159. int handle = handleAtPos(e->pos());
  160. bool needsUpdate = false;
  161. if (mouseOver != handle)
  162. needsUpdate = true;
  163. mouseOver = handle;
  164. e->accept();
  165. if (needsUpdate)
  166. update();
  167. }
  168. }
  169. void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
  170. {
  171. auto torgb = [](const QColor& c) {
  172. return RGBColor{
  173. uint8_t(c.red()), uint8_t(c.green()), uint8_t(c.blue())
  174. };
  175. };
  176. QRect handleArea = getHandleArea();
  177. int handle = handleAtPos(e->pos());
  178. if (handle != -1) {
  179. RGBColor current = points.at(handle).first;
  180. /*QColor newColor = QColorDialog::getColor(current,
  181. this,
  182. tr("Pick Color"));*/
  183. selectedHandle = handle;
  184. colorPicker->setCurrentColor(fromRGB(current));
  185. colorPicker->exec();
  186. /*if (newColor.isValid()) {
  187. points[handle].first = torgb(newColor);
  188. update();
  189. emit gradientChanged();
  190. }*/
  191. e->accept();
  192. }
  193. else if (handleArea.contains(e->pos())) {
  194. float v = handleYToGradVal(e->pos().y());
  195. points.emplace_back(torgb(colorAtY(e->pos().y())), v);
  196. e->accept();
  197. updateGradient();
  198. }
  199. else {
  200. e->ignore();
  201. }
  202. }
  203. QSize GradientWidget::minimumSizeHint(void) const
  204. {
  205. int spacing = this->style()->pixelMetric(
  206. QStyle::PM_LayoutHorizontalSpacing);
  207. if (spacing == -1) {
  208. spacing = this->style()->layoutSpacing(
  209. QSizePolicy::Frame,
  210. QSizePolicy::PushButton,
  211. Qt::Horizontal);
  212. }
  213. return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
  214. }
  215. QSize GradientWidget::sizeHint(void) const
  216. {
  217. int spacing = this->style()->pixelMetric(
  218. QStyle::PM_LayoutHorizontalSpacing);
  219. if (spacing == -1) {
  220. spacing = this->style()->layoutSpacing(
  221. QSizePolicy::Frame,
  222. QSizePolicy::PushButton,
  223. Qt::Horizontal);
  224. }
  225. int spacingV = this->style()->pixelMetric(
  226. QStyle::PM_LayoutVerticalSpacing);
  227. return QSize{ int(handleWidth * 1.1 + spacing),
  228. 2 * spacingV + 3 * handleHeight };
  229. }
  230. void GradientWidget::selectedColorChanged(const QColor& newColor)
  231. {
  232. if (points.size() > selectedHandle) {
  233. points.at(selectedHandle).first = RGBColor {
  234. uint8_t(newColor.red()),
  235. uint8_t(newColor.green()),
  236. uint8_t(newColor.blue())
  237. };
  238. updateGradient();
  239. emit gradientChanged();
  240. }
  241. }
  242. void GradientWidget::removeSelectedHandle(void)
  243. {
  244. if (selectedHandle >= 0 && selectedHandle < points.size()) {
  245. points.erase(points.begin() + selectedHandle);
  246. selectedHandle = -1;
  247. updateGradient();
  248. emit gradientChanged();
  249. }
  250. }
  251. QRect GradientWidget::getGradientRect(void) const
  252. {
  253. QMargins cm = contentsMargins();
  254. int top = cm.top();
  255. int bottom = cm.bottom();
  256. int left = cm.left();
  257. int right = cm.right();
  258. int spacing = this->style()->pixelMetric(
  259. QStyle::PM_LayoutHorizontalSpacing);
  260. if (spacing == -1) {
  261. spacing = this->style()->layoutSpacing(
  262. QSizePolicy::Frame,
  263. QSizePolicy::PushButton,
  264. Qt::Horizontal);
  265. }
  266. top += handleHeight / 2;
  267. bottom += handleHeight / 2;
  268. return QRect{ left, top,
  269. width() - left - right - handleWidth - spacing,
  270. height() - bottom - top };
  271. }
  272. QRect GradientWidget::getHandleRect(int index) const
  273. {
  274. QRect handleArea = getHandleArea();
  275. float y = handleArea.top() + points.at(index).second / maxValue * handleArea.height();
  276. return QRect {
  277. handleArea.x(), int(y - handleHeight / 2),
  278. handleWidth, handleHeight
  279. };
  280. }
  281. QRect GradientWidget::getHandleArea(void) const
  282. {
  283. QMargins cm = contentsMargins();
  284. int top = cm.top();
  285. int bottom = cm.bottom();
  286. int left = cm.left();
  287. int right = cm.right();
  288. top += handleHeight / 2;
  289. bottom += handleHeight / 2;
  290. float y = top;
  291. float x = width() - handleWidth - right;
  292. return QRect {
  293. int(x), top, handleWidth, height() - top - bottom
  294. };
  295. }
  296. int GradientWidget::handleAtPos(QPoint pos) const
  297. {
  298. for (int i = points.size() - 1; i >= 0; i--) {
  299. QRect rect = getHandleRect(i);
  300. if (rect.contains(pos)) {
  301. return i;
  302. }
  303. }
  304. return -1;
  305. }
  306. float GradientWidget::handleYToGradVal(float y) const
  307. {
  308. QRect area = getHandleArea();
  309. return maxValue * (y - area.top()) / area.height();
  310. }
  311. float GradientWidget::gradValToHandleY(float v) const
  312. {
  313. QRect area = getHandleArea();
  314. return area.top() + v / maxValue * area.height();
  315. }
  316. QPainterPath GradientWidget::createSlideHandle(float w, float h)
  317. {
  318. const float rounding = 4;
  319. QPainterPath qpp;
  320. QPolygonF qpf;
  321. qpf << QPointF{ 0, 0.5 * h }
  322. << QPointF{ 0.3 * w, h };
  323. qpp.moveTo(0, 0.5 * h);
  324. qpp.lineTo(0.3 * w, h);
  325. qpp.arcTo(w - rounding, h - rounding, rounding, rounding, -90, 90);
  326. qpp.arcTo(w - rounding, 0, rounding, rounding, 0, 90);
  327. qpp.lineTo(0.3 * w, 0);
  328. qpp.lineTo(0, 0.5 * h);
  329. return qpp;
  330. }