NavigationPanel.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. #include "NavigationPanel.h"
  2. #include "Widgets/ScrollArea.h"
  3. #include "Widgets/ToolTip.h"
  4. #include "Common/Icon.h"
  5. #include <QApplication>
  6. #include <QDebug>
  7. #include <QResizeEvent>
  8. NavigationPanel::NavigationPanel(bool minimalEnabled, QWidget *parent) : QFrame(parent)
  9. {
  10. m_parent = parent;
  11. m_isMenuButtonVisible = true;
  12. m_isReturnButtonVisible = false;
  13. scrollArea = new ScrollArea(this);
  14. scrollWidget = new QWidget();
  15. menuButton = new NavigationToolButton(NEWFLICON(FluentIcon, MENU), this);
  16. returnButton = new NavigationToolButton(NEWFLICON(FluentIcon, RETURN), this);
  17. vBoxLayout = new NavigationItemLayout(this);
  18. topLayout = new NavigationItemLayout();
  19. bottomLayout = new NavigationItemLayout();
  20. scrollLayout = new NavigationItemLayout(scrollWidget);
  21. history = new NavigationHistory({}, this);
  22. m_expandAni = new QPropertyAnimation(this, "geometry", this);
  23. m_expandWidth = 322;
  24. isMinimalEnabled = minimalEnabled;
  25. if (isMinimalEnabled) {
  26. displayMode = NavigationDisplayMode::MINIMAL;
  27. } else {
  28. displayMode = NavigationDisplayMode::COMPACT;
  29. }
  30. initWidget();
  31. }
  32. void NavigationPanel::initWidget()
  33. {
  34. resize(48, height());
  35. setAttribute(Qt::WA_StyledBackground);
  36. window()->installEventFilter(this);
  37. returnButton->hide();
  38. returnButton->setDisabled(true);
  39. scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  40. scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  41. scrollArea->setWidget(scrollWidget);
  42. scrollArea->setWidgetResizable(true);
  43. m_expandAni->setEasingCurve(QEasingCurve::OutQuad);
  44. m_expandAni->setDuration(150);
  45. connect(menuButton, &NavigationToolButton::clicked, this, &NavigationPanel::toggle);
  46. connect(m_expandAni, &QPropertyAnimation::finished, this, &NavigationPanel::onExpandAniFinished);
  47. connect(history, &NavigationHistory::emptyChanged, returnButton, &NavigationToolButton::setDisabled);
  48. connect(returnButton, &NavigationToolButton::clicked, history, &NavigationHistory::pop);
  49. // add tool tip;
  50. returnButton->installEventFilter(new ToolTipFilter(returnButton, 1000));
  51. returnButton->setToolTip(tr("Back"));
  52. menuButton->installEventFilter(new ToolTipFilter(menuButton, 1000));
  53. menuButton->setToolTip(tr("Open Navigation"));
  54. setProperty("menu", false);
  55. scrollWidget->setObjectName("scrollWidget");
  56. FluentStyleSheet::apply("NAVIGATION_INTERFACE", this);
  57. initLayout();
  58. }
  59. void NavigationPanel::initLayout()
  60. {
  61. vBoxLayout->setContentsMargins(0, 5, 0, 5);
  62. topLayout->setContentsMargins(4, 0, 4, 0);
  63. bottomLayout->setContentsMargins(4, 0, 4, 0);
  64. scrollLayout->setContentsMargins(4, 0, 4, 0);
  65. vBoxLayout->setSpacing(4);
  66. topLayout->setSpacing(4);
  67. bottomLayout->setSpacing(4);
  68. scrollLayout->setSpacing(4);
  69. vBoxLayout->addLayout(topLayout, 0);
  70. vBoxLayout->addWidget(scrollArea, 1, Qt::AlignTop);
  71. vBoxLayout->addLayout(bottomLayout, 0);
  72. vBoxLayout->setAlignment(Qt::AlignTop);
  73. topLayout->setAlignment(Qt::AlignTop);
  74. scrollLayout->setAlignment(Qt::AlignTop);
  75. bottomLayout->setAlignment(Qt::AlignBottom);
  76. topLayout->addWidget(returnButton, 0, Qt::AlignTop);
  77. topLayout->addWidget(menuButton, 0, Qt::AlignTop);
  78. }
  79. void NavigationPanel::addItem(const QString &routeKey, FluentIconBase *icon, const QString &text,
  80. const QObject *receiver, const char *onClick, bool selectable,
  81. NavigationItemPosition position)
  82. {
  83. if (history->m_items.keys().contains(routeKey)) {
  84. return;
  85. }
  86. NavigationPushButton *button = new NavigationPushButton(icon, text, selectable, this);
  87. addWidget(routeKey, button, receiver, onClick, position);
  88. }
  89. void NavigationPanel::addWidget(const QString &routeKey, NavigationWidget *widget, const QObject *receiver,
  90. const char *onClick, NavigationItemPosition position)
  91. {
  92. if (history->m_items.keys().contains(routeKey)) {
  93. return;
  94. }
  95. connect(widget, &NavigationWidget::clicked, this, &NavigationPanel::onWidgetClicked);
  96. connect(widget, SIGNAL(clicked(bool)), receiver, onClick);
  97. widget->setProperty("routeKey", routeKey);
  98. history->m_items.insert(routeKey, widget);
  99. if (displayMode == NavigationDisplayMode::EXPAND || displayMode == NavigationDisplayMode::MENU) {
  100. widget->setCompacted(false);
  101. }
  102. addWidgetToLayout(widget, position);
  103. }
  104. void NavigationPanel::removeWidget(const QString &routeKey)
  105. {
  106. if (!history->m_items.keys().contains(routeKey)) {
  107. return;
  108. }
  109. NavigationWidget *w = history->m_items.take(routeKey);
  110. w->deleteLater();
  111. history->remove(routeKey, true);
  112. }
  113. void NavigationPanel::setMenuButtonVisible(bool visible)
  114. {
  115. m_isMenuButtonVisible = visible;
  116. menuButton->setVisible(visible);
  117. }
  118. void NavigationPanel::setReturnButtonVisible(bool visible)
  119. {
  120. m_isMenuButtonVisible = visible;
  121. returnButton->setVisible(visible);
  122. }
  123. void NavigationPanel::setExpandWidth(int width)
  124. {
  125. if (width <= 42) {
  126. return;
  127. }
  128. m_expandWidth = width;
  129. NavigationWidget::EXPAND_WIDTH = width - 10;
  130. }
  131. /// expand navigation panel
  132. void NavigationPanel::expand()
  133. {
  134. setWidgetCompacted(false);
  135. m_expandAni->setProperty("expand", true);
  136. // determine the display mode according to the width of window
  137. // https://learn.microsoft.com/en-us/windows/apps/design/controls/navigationview#default
  138. int expandWidth = 1007 + this->m_expandWidth - 322;
  139. if (this->window()->width() > expandWidth && !isMinimalEnabled) {
  140. displayMode = NavigationDisplayMode::EXPAND;
  141. } else {
  142. setProperty("menu", true);
  143. setStyle(QApplication::style());
  144. displayMode = NavigationDisplayMode::MENU;
  145. if (!m_parent->isWindow()) {
  146. QPoint pos = m_parent->pos();
  147. setParent(this->window());
  148. move(pos);
  149. }
  150. this->show();
  151. }
  152. emit displayModeChanged(displayMode);
  153. m_expandAni->setStartValue(QRect(this->pos(), QSize(48, this->height())));
  154. m_expandAni->setEndValue(QRect(this->pos(), QSize(m_expandWidth, this->height())));
  155. m_expandAni->start();
  156. }
  157. /// collapse navigation panel
  158. void NavigationPanel::collapse()
  159. {
  160. if (m_expandAni->state() == QPropertyAnimation::Running) {
  161. return;
  162. }
  163. m_expandAni->setStartValue(QRect(this->pos(), QSize(this->width(), this->height())));
  164. m_expandAni->setEndValue(QRect(this->pos(), QSize(48, this->height())));
  165. m_expandAni->setProperty("expand", false);
  166. m_expandAni->start();
  167. }
  168. void NavigationPanel::onExpandAniFinished()
  169. {
  170. if (!m_expandAni->property("expand").toBool()) {
  171. if (isMinimalEnabled) {
  172. displayMode = NavigationDisplayMode::MINIMAL;
  173. } else {
  174. displayMode = NavigationDisplayMode::COMPACT;
  175. }
  176. emit displayModeChanged(displayMode);
  177. }
  178. if (displayMode == NavigationDisplayMode::MINIMAL) {
  179. this->hide();
  180. this->setProperty("menu", false);
  181. this->setStyle(QApplication::style());
  182. } else if (displayMode == NavigationDisplayMode::COMPACT) {
  183. this->setProperty("menu", false);
  184. this->setStyle(QApplication::style());
  185. for (auto item : history->m_items.values()) {
  186. item->setCompacted(true);
  187. }
  188. if (!m_parent->isWindow()) {
  189. this->setParent(m_parent);
  190. this->move(0, 0);
  191. this->show();
  192. }
  193. }
  194. }
  195. void NavigationPanel::addSeparator(NavigationItemPosition position)
  196. {
  197. NavigationSeparator *separator = new NavigationSeparator(this);
  198. addWidgetToLayout(separator, position);
  199. }
  200. void NavigationPanel::setCurrentItem(const QString &routeKey)
  201. {
  202. if (!history->m_items.keys().contains(routeKey)) {
  203. return;
  204. }
  205. history->push(routeKey);
  206. QHashIterator<QString, NavigationWidget *> i(history->m_items);
  207. while (i.hasNext()) {
  208. i.next();
  209. i.value()->setSelected(i.key() == routeKey);
  210. }
  211. }
  212. int NavigationPanel::layoutMinHeight()
  213. {
  214. int th = topLayout->minimumSize().height();
  215. int bh = bottomLayout->minimumSize().height();
  216. int sh = 0;
  217. for (auto w : this->findChildren<NavigationSeparator *>()) {
  218. sh += w->height();
  219. }
  220. int spacing = topLayout->count() * topLayout->spacing();
  221. spacing += bottomLayout->count() * bottomLayout->spacing();
  222. return 36 + th + bh + sh + spacing;
  223. }
  224. /// set the routing key to use when the navigation history is empty
  225. void NavigationPanel::setDefaultRouteKey(const QString &routeKey)
  226. {
  227. history->setDefaultRouteKey(routeKey);
  228. }
  229. bool NavigationPanel::eventFilter(QObject *watched, QEvent *event)
  230. {
  231. if (watched == this->window()) {
  232. return QFrame::eventFilter(watched, event);
  233. }
  234. if (event->type() == QEvent::MouseButtonRelease) {
  235. QMouseEvent *mouseEvt = static_cast<QMouseEvent *>(event);
  236. if (!this->geometry().contains(mouseEvt->pos()) && (displayMode == NavigationDisplayMode::MENU)) {
  237. collapse();
  238. }
  239. } else if (event->type() == QEvent::Resize) {
  240. QResizeEvent *resizeEvt = static_cast<QResizeEvent *>(event);
  241. int w = resizeEvt->size().width();
  242. if (w < 1008 && displayMode == NavigationDisplayMode::EXPAND) {
  243. collapse();
  244. } else if (w >= 1008 && displayMode == NavigationDisplayMode::COMPACT && !m_isMenuButtonVisible) {
  245. expand();
  246. }
  247. }
  248. return QFrame::eventFilter(watched, event);
  249. }
  250. void NavigationPanel::resizeEvent(QResizeEvent *event)
  251. {
  252. if (event->oldSize().height() == this->height()) {
  253. return;
  254. }
  255. int th = topLayout->minimumSize().height();
  256. int bh = bottomLayout->minimumSize().height();
  257. int h = this->height() - th - bh - 20;
  258. scrollArea->setFixedHeight(qMax(h, 36));
  259. }
  260. void NavigationPanel::addWidgetToLayout(NavigationWidget *widget, NavigationItemPosition position)
  261. {
  262. if (position == NavigationItemPosition::TOP) {
  263. widget->setParent(this);
  264. topLayout->addWidget(widget, 0, Qt::AlignTop);
  265. } else if (position == NavigationItemPosition::SCROLL) {
  266. widget->setParent(scrollWidget);
  267. scrollLayout->addWidget(widget, 0, Qt::AlignTop);
  268. } else {
  269. widget->setParent(this);
  270. bottomLayout->addWidget(widget, 0, Qt::AlignBottom);
  271. }
  272. widget->show();
  273. }
  274. void NavigationPanel::setWidgetCompacted(bool compacted)
  275. {
  276. for (auto item : this->findChildren<NavigationWidget *>()) {
  277. item->setCompacted(compacted);
  278. }
  279. }
  280. void NavigationPanel::onWidgetClicked()
  281. {
  282. NavigationWidget *widget = qobject_cast<NavigationWidget *>(sender());
  283. if (!widget->isSelectable) {
  284. return;
  285. }
  286. setCurrentItem(widget->property("routeKey").toString());
  287. if (widget != menuButton && displayMode == NavigationDisplayMode::MENU) {
  288. collapse();
  289. }
  290. }
  291. /// toggle navigation panel
  292. void NavigationPanel::toggle()
  293. {
  294. if (displayMode == NavigationDisplayMode::COMPACT || displayMode == NavigationDisplayMode::MINIMAL) {
  295. this->expand();
  296. } else {
  297. this->collapse();
  298. }
  299. }
  300. NavigationHistory::NavigationHistory(const QHash<QString, NavigationWidget *> &items, QObject *parent)
  301. : QObject(parent), m_items(items)
  302. {
  303. m_defaultRouteKey = QString();
  304. }
  305. QString NavigationHistory::defaultRouteKey() const
  306. {
  307. return m_defaultRouteKey;
  308. }
  309. void NavigationHistory::setDefaultRouteKey(const QString &key)
  310. {
  311. if (!m_items.keys().contains(key)) {
  312. qCritical() << "The route key `" + key + " ` has not been registered yet.";
  313. } else {
  314. m_defaultRouteKey = key;
  315. }
  316. }
  317. /// push history
  318. void NavigationHistory::push(const QString &routeKey)
  319. {
  320. if (m_history.isEmpty() && m_defaultRouteKey != routeKey) {
  321. m_history.append(routeKey);
  322. emit emptyChanged(false);
  323. } else if (!m_history.isEmpty() && m_history.last() != routeKey) {
  324. m_history.append(routeKey);
  325. }
  326. }
  327. /// pop history
  328. void NavigationHistory::pop()
  329. {
  330. if (m_history.isEmpty()) {
  331. return;
  332. }
  333. m_history.pop_back();
  334. navigate();
  335. }
  336. /// remove history
  337. void NavigationHistory::remove(const QString &routeKey, bool all)
  338. {
  339. if (!m_history.contains(routeKey)) {
  340. return;
  341. }
  342. if (all) {
  343. m_history.removeAll(routeKey);
  344. } else {
  345. int index = m_history.lastIndexOf(routeKey);
  346. m_history.removeAt(index);
  347. }
  348. navigate();
  349. }
  350. void NavigationHistory::navigate()
  351. {
  352. if (!m_history.isEmpty()) {
  353. emit m_items[m_history.last()]->clicked(false);
  354. } else {
  355. if (!m_defaultRouteKey.isEmpty()) {
  356. emit m_items[defaultRouteKey()]->clicked(false);
  357. }
  358. emit emptyChanged(true);
  359. }
  360. }