CycleListWidget.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. #include "CycleListWidget.h"
  2. #include "QFluentWidgets.h"
  3. #include "ScrollArea.h"
  4. #include <QEvent>
  5. #include <QEvent>
  6. #include <QPainter>
  7. #include <QWheelEvent>
  8. ScrollIcon::ScrollIcon(IconType type, Qfw::Theme t) : FluentIconBase(""), m_theme(t), m_type(type)
  9. {
  10. iconEngine->setIconPath(iconPath());
  11. }
  12. ScrollIcon::~ScrollIcon() { }
  13. QString ScrollIcon::iconName(ScrollIcon::IconType type)
  14. {
  15. switch (type) {
  16. case UP:
  17. return "Up";
  18. case DOWN:
  19. return "Down";
  20. }
  21. return "Unknown";
  22. }
  23. QString ScrollIcon::iconPath()
  24. {
  25. QString colorName;
  26. if (m_theme == Qfw::Theme::AUTO) {
  27. colorName = QFWIns.isDarkTheme() ? "white" : "black";
  28. } else {
  29. if (m_theme == Qfw::DARK) {
  30. colorName = "white";
  31. } else {
  32. colorName = "black";
  33. }
  34. }
  35. return QString(":/qfluentwidgets/images/time_picker/%1_%2.svg").arg(iconName(m_type)).arg(colorName);
  36. }
  37. QIcon ScrollIcon::icon()
  38. {
  39. return QIcon(iconEngine->clone());
  40. }
  41. QString ScrollIcon::typeName() const
  42. {
  43. return iconName(m_type);
  44. }
  45. QString ScrollIcon::enumName() const
  46. {
  47. QMetaEnum metaEnum = QMetaEnum::fromType<IconType>();
  48. return metaEnum.valueToKey(m_type);
  49. }
  50. FluentIconBase *ScrollIcon::clone()
  51. {
  52. return new ScrollIcon(m_type, m_theme);
  53. }
  54. Qfw::Theme ScrollIcon::theme() const
  55. {
  56. return m_theme;
  57. }
  58. void ScrollIcon::setTheme(const Qfw::Theme &theme)
  59. {
  60. m_theme = theme;
  61. iconEngine->setIconPath(iconPath());
  62. }
  63. ScrollButton::ScrollButton(FluentIconBase *icon, QWidget *parent) : QToolButton(parent), isPressed(false), m_icon(icon)
  64. {
  65. installEventFilter(this);
  66. }
  67. bool ScrollButton::eventFilter(QObject *watched, QEvent *event)
  68. {
  69. if (watched == this) {
  70. if (event->type() == QEvent::MouseButtonPress) {
  71. isPressed = true;
  72. update();
  73. } else if (event->type() == QEvent::MouseButtonRelease) {
  74. isPressed = false;
  75. update();
  76. }
  77. }
  78. return QToolButton::eventFilter(watched, event);
  79. }
  80. void ScrollButton::paintEvent(QPaintEvent *event)
  81. {
  82. QToolButton::paintEvent(event);
  83. QPainter painter(this);
  84. painter.setRenderHints(QPainter::Antialiasing);
  85. int w, h;
  86. if (!isPressed) {
  87. w = 10;
  88. h = 10;
  89. } else {
  90. w = 8;
  91. h = 8;
  92. }
  93. int x = (this->width() - w) / 2;
  94. int y = (this->height() - h) / 2;
  95. m_icon->render(&painter, QRect(x, y, w, h));
  96. }
  97. CycleListWidget::CycleListWidget(const QVariantList &items, const QSize &itemSize, Qt::AlignmentFlag align,
  98. QWidget *parent)
  99. : QListWidget(parent), m_itemSize(itemSize), m_align(align), m_isCycle(false), m_currentIndex(-1)
  100. {
  101. m_upButton = new ScrollButton(new ScrollIcon(ScrollIcon::UP), this);
  102. m_downButton = new ScrollButton(new ScrollIcon(ScrollIcon::DOWN), this);
  103. m_scrollDuration = 250;
  104. m_originItems = items;
  105. vScrollBar = new SmoothScrollBar(this);
  106. m_visibleNumber = 9;
  107. // repeat adding items to achieve circular scrolling
  108. setItems(items);
  109. this->setVerticalScrollMode(ScrollPerPixel);
  110. this->setVerticalScrollBar(vScrollBar);
  111. this->vScrollBar->setScrollAnimation(m_scrollDuration);
  112. this->setViewportMargins(0, 0, 0, 0);
  113. this->setFixedSize(itemSize.width() + 8, itemSize.height() * m_visibleNumber);
  114. // hide scroll bar
  115. this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  116. this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  117. m_upButton->hide();
  118. m_downButton->hide();
  119. connect(m_upButton, &ScrollButton::clicked, this, &CycleListWidget::scrollUp);
  120. connect(m_downButton, &ScrollButton::clicked, this, &CycleListWidget::scrollDown);
  121. connect(this, &CycleListWidget::itemClicked, this, &CycleListWidget::onItemClicked);
  122. this->installEventFilter(this);
  123. }
  124. void CycleListWidget::setItems(const QVariantList &items)
  125. {
  126. this->clear();
  127. createItems(items);
  128. }
  129. int CycleListWidget::currentIndex() const
  130. {
  131. return m_currentIndex;
  132. }
  133. void CycleListWidget::setCurrentIndex(int index)
  134. {
  135. if (!m_isCycle) {
  136. int n = m_visibleNumber / 2;
  137. m_currentIndex = qMax(n, qMin(n + m_originItems.count() - 1, index));
  138. } else {
  139. int N = this->count() / 2;
  140. int m = (m_visibleNumber + 1) / 2;
  141. m_currentIndex = index;
  142. // scroll to center to achieve circular scrolling
  143. if (index >= this->count() - m) {
  144. m_currentIndex = N + index - this->count();
  145. QListWidget::scrollToItem(this->item(this->currentIndex() - 1), PositionAtCenter);
  146. } else if (index <= m - 1) {
  147. m_currentIndex = N + index;
  148. QListWidget::scrollToItem(this->item(N + index + 1), PositionAtCenter);
  149. }
  150. }
  151. }
  152. QListWidgetItem *CycleListWidget::currentItem() const
  153. {
  154. return this->item(this->currentIndex());
  155. }
  156. /// scroll to item, scroll to center position
  157. void CycleListWidget::scrollToItem(QListWidgetItem *item, QAbstractItemView::ScrollHint /*hint*/)
  158. {
  159. int index = this->row(item);
  160. int y = item->sizeHint().height() * (index - m_visibleNumber / 2);
  161. vScrollBar->scrollTo(y);
  162. // clear selection
  163. clearSelection();
  164. item->setSelected(false);
  165. emit currentItemChanged(item);
  166. }
  167. void CycleListWidget::setSelectedItem(const QString &text)
  168. {
  169. if (text.isEmpty()) {
  170. return;
  171. }
  172. auto items = this->findItems(text, Qt::MatchExactly);
  173. if (items.isEmpty()) {
  174. return;
  175. }
  176. if (items.count() >= 2) {
  177. setCurrentIndex(this->row(items.at(1)));
  178. } else {
  179. setCurrentIndex(this->row(items.at(0)));
  180. }
  181. QListWidget::scrollToItem(this->currentItem(), QListWidget::PositionAtCenter);
  182. }
  183. QSize CycleListWidget::itemSize() const
  184. {
  185. return m_itemSize;
  186. }
  187. bool CycleListWidget::eventFilter(QObject *watched, QEvent *event)
  188. {
  189. if (watched != this || event->type() != QEvent::KeyPress) {
  190. return QListWidget::eventFilter(watched, event);
  191. }
  192. if (event->type() == QEvent::KeyPress) {
  193. QKeyEvent *keyEvt = static_cast<QKeyEvent *>(event);
  194. if (keyEvt->key() == Qt::Key_Down) {
  195. scrollDown();
  196. return true;
  197. } else if (keyEvt->key() == Qt::Key_Up) {
  198. scrollUp();
  199. return true;
  200. }
  201. }
  202. return QListWidget::eventFilter(watched, event);
  203. }
  204. void CycleListWidget::wheelEvent(QWheelEvent *event)
  205. {
  206. if (event->angleDelta().y() < 0) {
  207. scrollDown();
  208. } else {
  209. scrollUp();
  210. }
  211. }
  212. void CycleListWidget::leaveEvent(QEvent * /*event*/)
  213. {
  214. m_upButton->hide();
  215. m_downButton->hide();
  216. }
  217. void CycleListWidget::resizeEvent(QResizeEvent * /*event*/)
  218. {
  219. m_upButton->resize(this->width(), 34);
  220. m_downButton->resize(this->width(), 34);
  221. m_downButton->move(0, this->height() - 34);
  222. }
  223. void CycleListWidget::enterEvent(QEvent * /*event*/)
  224. {
  225. m_upButton->show();
  226. m_downButton->show();
  227. }
  228. void CycleListWidget::createItems(const QVariantList &items)
  229. {
  230. const int N = items.count();
  231. m_isCycle = (N > m_visibleNumber);
  232. if (m_isCycle) {
  233. addColumnItems(items);
  234. addColumnItems(items);
  235. m_currentIndex = items.count();
  236. QListWidget::scrollToItem(this->item(this->currentIndex() - m_visibleNumber / 2), QListWidget::PositionAtTop);
  237. } else {
  238. int n = m_visibleNumber / 2; // add empty items to enable scrolling
  239. QVariantList emptyList;
  240. for (int i = 0; i < n; ++i) {
  241. emptyList << "";
  242. }
  243. addColumnItems(emptyList, true);
  244. addColumnItems(items);
  245. addColumnItems(emptyList, true);
  246. m_currentIndex = n;
  247. }
  248. }
  249. void CycleListWidget::addColumnItems(const QVariantList &items, bool disabled)
  250. {
  251. for (auto i : items) {
  252. auto item = new QListWidgetItem(i.toString(), this);
  253. item->setSizeHint(this->m_itemSize);
  254. item->setTextAlignment(this->m_align | Qt::AlignVCenter);
  255. if (disabled) {
  256. item->setFlags(Qt::NoItemFlags);
  257. }
  258. this->addItem(item);
  259. }
  260. }
  261. // scroll up an item
  262. void CycleListWidget::scrollUp()
  263. {
  264. this->setCurrentIndex(m_currentIndex - 1);
  265. this->scrollToItem(this->currentItem());
  266. }
  267. // scroll down an item
  268. void CycleListWidget::scrollDown()
  269. {
  270. this->setCurrentIndex(m_currentIndex + 1);
  271. this->scrollToItem(this->currentItem());
  272. }
  273. void CycleListWidget::onItemClicked(QListWidgetItem *item)
  274. {
  275. this->setCurrentIndex(this->row(item));
  276. this->scrollToItem(this->currentItem());
  277. }