1 #include "BaseWindow.hpp"
2 
3 #include "BaseSettings.hpp"
4 #include "BaseTheme.hpp"
5 #include "boost/algorithm/algorithm.hpp"
6 #include "util/DebugCount.hpp"
7 #include "util/PostToThread.hpp"
8 #include "util/Shortcut.hpp"
9 #include "util/WindowsHelper.hpp"
10 #include "widgets/Label.hpp"
11 #include "widgets/TooltipWidget.hpp"
12 #include "widgets/helper/EffectLabel.hpp"
13 
14 #include <QApplication>
15 #include <QDebug>
16 #include <QDesktopWidget>
17 #include <QFont>
18 #include <QIcon>
19 #include <functional>
20 
21 #ifdef CHATTERINO
22 #    include "Application.hpp"
23 #    include "singletons/WindowManager.hpp"
24 #endif
25 
26 #ifdef USEWINSDK
27 #    include <ObjIdl.h>
28 #    include <VersionHelpers.h>
29 #    include <Windows.h>
30 #    include <dwmapi.h>
31 #    include <gdiplus.h>
32 #    include <windowsx.h>
33 
34 //#include <ShellScalingApi.h>
35 #    pragma comment(lib, "Dwmapi.lib")
36 
37 #    include <QHBoxLayout>
38 #    include <QVBoxLayout>
39 
40 #    define WM_DPICHANGED 0x02E0
41 #endif
42 
43 #include "widgets/helper/TitlebarButton.hpp"
44 
45 namespace chatterino {
46 
BaseWindow(FlagsEnum<Flags> _flags,QWidget * parent)47 BaseWindow::BaseWindow(FlagsEnum<Flags> _flags, QWidget *parent)
48     : BaseWidget(parent, (_flags.has(Dialog) ? Qt::Dialog : Qt::Window) |
49                              (_flags.has(TopMost) ? Qt::WindowStaysOnTopHint
50                                                   : Qt::WindowFlags()))
51     , enableCustomFrame_(_flags.has(EnableCustomFrame))
52     , frameless_(_flags.has(Frameless))
53     , flags_(_flags)
54 {
55     if (this->frameless_)
56     {
57         this->enableCustomFrame_ = false;
58         this->setWindowFlag(Qt::FramelessWindowHint);
59     }
60 
61     if (_flags.has(DontFocus))
62     {
63         this->setAttribute(Qt::WA_ShowWithoutActivating);
64 #ifdef Q_OS_LINUX
65         this->setWindowFlags(Qt::ToolTip);
66 #else
67         this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint |
68                              Qt::X11BypassWindowManagerHint |
69                              Qt::BypassWindowManagerHint);
70 #endif
71     }
72 
73     this->init();
74 
75     getSettings()->uiScale.connect(
76         [this]() {
77             postToThread([this] {
78                 this->updateScale();
79                 this->updateScale();
80             });
81         },
82         this->connections_);
83 
84     this->updateScale();
85 
86     createWindowShortcut(this, "CTRL+0", [] {
87         getSettings()->uiScale.setValue(1);
88     });
89 
90     this->resize(300, 150);
91 
92 #ifdef USEWINSDK
93     this->useNextBounds_.setSingleShot(true);
94     QObject::connect(&this->useNextBounds_, &QTimer::timeout, this, [this]() {
95         this->currentBounds_ = this->nextBounds_;
96     });
97 #endif
98 
99     this->themeChangedEvent();
100     DebugCount::increase("BaseWindow");
101 }
102 
~BaseWindow()103 BaseWindow::~BaseWindow()
104 {
105     DebugCount::decrease("BaseWindow");
106 }
107 
setInitialBounds(const QRect & bounds)108 void BaseWindow::setInitialBounds(const QRect &bounds)
109 {
110 #ifdef USEWINSDK
111     this->initalBounds_ = bounds;
112 #else
113     this->setGeometry(bounds);
114 #endif
115 }
116 
getBounds()117 QRect BaseWindow::getBounds()
118 {
119 #ifdef USEWINSDK
120     return this->currentBounds_;
121 #else
122     return this->geometry();
123 #endif
124 }
125 
scale() const126 float BaseWindow::scale() const
127 {
128     return std::max<float>(0.01f, this->overrideScale().value_or(this->scale_));
129 }
130 
qtFontScale() const131 float BaseWindow::qtFontScale() const
132 {
133     return this->scale() / std::max<float>(0.01, this->nativeScale_);
134 }
135 
init()136 void BaseWindow::init()
137 {
138     this->setWindowIcon(QIcon(":/images/icon.png"));
139 
140 #ifdef USEWINSDK
141     if (this->hasCustomWindowFrame())
142     {
143         // CUSTOM WINDOW FRAME
144         QVBoxLayout *layout = new QVBoxLayout();
145         this->ui_.windowLayout = layout;
146         layout->setContentsMargins(1, 1, 1, 1);
147         layout->setSpacing(0);
148         this->setLayout(layout);
149         {
150             if (!this->frameless_)
151             {
152                 QHBoxLayout *buttonLayout = this->ui_.titlebarBox =
153                     new QHBoxLayout();
154                 buttonLayout->setMargin(0);
155                 layout->addLayout(buttonLayout);
156 
157                 // title
158                 Label *title = new Label;
159                 QObject::connect(this, &QWidget::windowTitleChanged,
160                                  [title](const QString &text) {
161                                      title->setText(text);
162                                  });
163 
164                 QSizePolicy policy(QSizePolicy::Ignored,
165                                    QSizePolicy::Preferred);
166                 policy.setHorizontalStretch(1);
167                 title->setSizePolicy(policy);
168                 buttonLayout->addWidget(title);
169                 this->ui_.titleLabel = title;
170 
171                 // buttons
172                 TitleBarButton *_minButton = new TitleBarButton;
173                 _minButton->setButtonStyle(TitleBarButtonStyle::Minimize);
174                 TitleBarButton *_maxButton = new TitleBarButton;
175                 _maxButton->setButtonStyle(TitleBarButtonStyle::Maximize);
176                 TitleBarButton *_exitButton = new TitleBarButton;
177                 _exitButton->setButtonStyle(TitleBarButtonStyle::Close);
178 
179                 QObject::connect(_minButton, &TitleBarButton::leftClicked, this,
180                                  [this] {
181                                      this->setWindowState(Qt::WindowMinimized |
182                                                           this->windowState());
183                                  });
184                 QObject::connect(_maxButton, &TitleBarButton::leftClicked, this,
185                                  [this, _maxButton] {
186                                      this->setWindowState(
187                                          _maxButton->getButtonStyle() !=
188                                                  TitleBarButtonStyle::Maximize
189                                              ? Qt::WindowActive
190                                              : Qt::WindowMaximized);
191                                  });
192                 QObject::connect(_exitButton, &TitleBarButton::leftClicked,
193                                  this, [this] {
194                                      this->close();
195                                  });
196 
197                 this->ui_.minButton = _minButton;
198                 this->ui_.maxButton = _maxButton;
199                 this->ui_.exitButton = _exitButton;
200 
201                 this->ui_.buttons.push_back(_minButton);
202                 this->ui_.buttons.push_back(_maxButton);
203                 this->ui_.buttons.push_back(_exitButton);
204 
205                 //            buttonLayout->addStretch(1);
206                 buttonLayout->addWidget(_minButton);
207                 buttonLayout->addWidget(_maxButton);
208                 buttonLayout->addWidget(_exitButton);
209                 buttonLayout->setSpacing(0);
210             }
211         }
212         this->ui_.layoutBase = new BaseWidget(this);
213         this->ui_.layoutBase->setContentsMargins(1, 0, 1, 1);
214         layout->addWidget(this->ui_.layoutBase);
215     }
216 
217 // DPI
218 //    auto dpi = getWindowDpi(this->winId());
219 
220 //    if (dpi) {
221 //        this->scale = dpi.value() / 96.f;
222 //    }
223 #endif
224 
225 #ifdef USEWINSDK
226     // fourtf: don't ask me why we need to delay this
227     if (!this->flags_.has(TopMost))
228     {
229         QTimer::singleShot(1, this, [this] {
230             getSettings()->windowTopMost.connect(
231                 [this](bool topMost, auto) {
232                     ::SetWindowPos(HWND(this->winId()),
233                                    topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0,
234                                    0, 0, 0,
235                                    SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
236                 },
237                 this->managedConnections_);
238         });
239     }
240 #else
241     // TopMost flag overrides setting
242     if (!this->flags_.has(TopMost))
243     {
244         getSettings()->windowTopMost.connect(
245             [this](bool topMost, auto) {
246                 this->setWindowFlag(Qt::WindowStaysOnTopHint, topMost);
247                 if (this->isVisible())
248                 {
249                     this->show();
250                 }
251             },
252             this->managedConnections_);
253     }
254 #endif
255 }
256 
setStayInScreenRect(bool value)257 void BaseWindow::setStayInScreenRect(bool value)
258 {
259     this->stayInScreenRect_ = value;
260 
261     this->moveIntoDesktopRect(this, this->pos());
262 }
263 
getStayInScreenRect() const264 bool BaseWindow::getStayInScreenRect() const
265 {
266     return this->stayInScreenRect_;
267 }
268 
setActionOnFocusLoss(ActionOnFocusLoss value)269 void BaseWindow::setActionOnFocusLoss(ActionOnFocusLoss value)
270 {
271     this->actionOnFocusLoss_ = value;
272 }
273 
getActionOnFocusLoss() const274 BaseWindow::ActionOnFocusLoss BaseWindow::getActionOnFocusLoss() const
275 {
276     return this->actionOnFocusLoss_;
277 }
278 
getLayoutContainer()279 QWidget *BaseWindow::getLayoutContainer()
280 {
281     if (this->hasCustomWindowFrame())
282     {
283         return this->ui_.layoutBase;
284     }
285     else
286     {
287         return this;
288     }
289 }
290 
hasCustomWindowFrame()291 bool BaseWindow::hasCustomWindowFrame()
292 {
293     return BaseWindow::supportsCustomWindowFrame() && this->enableCustomFrame_;
294 }
295 
supportsCustomWindowFrame()296 bool BaseWindow::supportsCustomWindowFrame()
297 {
298 #ifdef USEWINSDK
299     static bool isWin8 = IsWindows8OrGreater();
300 
301     return isWin8;
302 #else
303     return false;
304 #endif
305 }
306 
themeChangedEvent()307 void BaseWindow::themeChangedEvent()
308 {
309     if (this->hasCustomWindowFrame())
310     {
311         QPalette palette;
312         palette.setColor(QPalette::Window, QColor(80, 80, 80, 255));
313         palette.setColor(QPalette::WindowText, this->theme->window.text);
314         this->setPalette(palette);
315 
316         if (this->ui_.titleLabel)
317         {
318             QPalette palette_title;
319             palette_title.setColor(
320                 QPalette::WindowText,
321                 this->theme->isLightTheme() ? "#333" : "#ccc");
322             this->ui_.titleLabel->setPalette(palette_title);
323         }
324 
325         for (Button *button : this->ui_.buttons)
326         {
327             button->setMouseEffectColor(this->theme->window.text);
328         }
329     }
330     else
331     {
332         QPalette palette;
333         palette.setColor(QPalette::Window, this->theme->window.background);
334         palette.setColor(QPalette::WindowText, this->theme->window.text);
335         this->setPalette(palette);
336     }
337 }
338 
event(QEvent * event)339 bool BaseWindow::event(QEvent *event)
340 {
341     if (event->type() ==
342         QEvent::WindowDeactivate /*|| event->type() == QEvent::FocusOut*/)
343     {
344         this->onFocusLost();
345     }
346 
347     return QWidget::event(event);
348 }
349 
wheelEvent(QWheelEvent * event)350 void BaseWindow::wheelEvent(QWheelEvent *event)
351 {
352     if (event->orientation() != Qt::Vertical)
353     {
354         return;
355     }
356 
357     if (event->modifiers() & Qt::ControlModifier)
358     {
359         if (event->delta() > 0)
360         {
361             getSettings()->setClampedUiScale(
362                 getSettings()->getClampedUiScale() + 0.1);
363         }
364         else
365         {
366             getSettings()->setClampedUiScale(
367                 getSettings()->getClampedUiScale() - 0.1);
368         }
369     }
370 }
371 
onFocusLost()372 void BaseWindow::onFocusLost()
373 {
374     switch (this->getActionOnFocusLoss())
375     {
376         case Delete: {
377             this->deleteLater();
378         }
379         break;
380 
381         case Close: {
382             this->close();
383         }
384         break;
385 
386         case Hide: {
387             this->hide();
388         }
389         break;
390 
391         default:;
392     }
393 }
394 
mousePressEvent(QMouseEvent * event)395 void BaseWindow::mousePressEvent(QMouseEvent *event)
396 {
397 #ifndef Q_OS_WIN
398     if (this->flags_.has(FramelessDraggable))
399     {
400         this->movingRelativePos = event->localPos();
401         if (auto widget =
402                 this->childAt(event->localPos().x(), event->localPos().y()))
403         {
404             std::function<bool(QWidget *)> recursiveCheckMouseTracking;
405             recursiveCheckMouseTracking = [&](QWidget *widget) {
406                 if (widget == nullptr)
407                 {
408                     return false;
409                 }
410 
411                 if (widget->hasMouseTracking())
412                 {
413                     return true;
414                 }
415 
416                 return recursiveCheckMouseTracking(widget->parentWidget());
417             };
418 
419             if (!recursiveCheckMouseTracking(widget))
420             {
421                 this->moving = true;
422             }
423         }
424     }
425 #endif
426 
427     BaseWidget::mousePressEvent(event);
428 }
429 
mouseReleaseEvent(QMouseEvent * event)430 void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
431 {
432 #ifndef Q_OS_WIN
433     if (this->flags_.has(FramelessDraggable))
434     {
435         if (this->moving)
436         {
437             this->moving = false;
438         }
439     }
440 #endif
441 
442     BaseWidget::mouseReleaseEvent(event);
443 }
444 
mouseMoveEvent(QMouseEvent * event)445 void BaseWindow::mouseMoveEvent(QMouseEvent *event)
446 {
447 #ifndef Q_OS_WIN
448     if (this->flags_.has(FramelessDraggable))
449     {
450         if (this->moving)
451         {
452             const auto &newPos = event->screenPos() - this->movingRelativePos;
453             this->move(newPos.x(), newPos.y());
454         }
455     }
456 #endif
457 
458     BaseWidget::mouseMoveEvent(event);
459 }
460 
addTitleBarButton(const TitleBarButtonStyle & style,std::function<void ()> onClicked)461 TitleBarButton *BaseWindow::addTitleBarButton(const TitleBarButtonStyle &style,
462                                               std::function<void()> onClicked)
463 {
464     TitleBarButton *button = new TitleBarButton;
465     button->setScaleIndependantSize(30, 30);
466 
467     this->ui_.buttons.push_back(button);
468     this->ui_.titlebarBox->insertWidget(1, button);
469     button->setButtonStyle(style);
470 
471     QObject::connect(button, &TitleBarButton::leftClicked, this, [onClicked] {
472         onClicked();
473     });
474 
475     return button;
476 }
477 
addTitleBarLabel(std::function<void ()> onClicked)478 EffectLabel *BaseWindow::addTitleBarLabel(std::function<void()> onClicked)
479 {
480     EffectLabel *button = new EffectLabel;
481     button->setScaleIndependantHeight(30);
482 
483     this->ui_.buttons.push_back(button);
484     this->ui_.titlebarBox->insertWidget(1, button);
485 
486     QObject::connect(button, &EffectLabel::leftClicked, this, [onClicked] {
487         onClicked();
488     });
489 
490     return button;
491 }
492 
changeEvent(QEvent *)493 void BaseWindow::changeEvent(QEvent *)
494 {
495     if (this->isVisible())
496     {
497         TooltipWidget::instance()->hide();
498     }
499 
500 #ifdef USEWINSDK
501     if (this->ui_.maxButton)
502     {
503         this->ui_.maxButton->setButtonStyle(
504             this->windowState() & Qt::WindowMaximized
505                 ? TitleBarButtonStyle::Unmaximize
506                 : TitleBarButtonStyle::Maximize);
507     }
508 
509     if (this->isVisible() && this->hasCustomWindowFrame())
510     {
511         auto palette = this->palette();
512         palette.setColor(QPalette::Window,
513                          GetForegroundWindow() == HWND(this->winId())
514                              ? QColor(90, 90, 90)
515                              : QColor(50, 50, 50));
516         this->setPalette(palette);
517     }
518 #endif
519 
520 #ifndef Q_OS_WIN
521     this->update();
522 #endif
523 }
524 
leaveEvent(QEvent *)525 void BaseWindow::leaveEvent(QEvent *)
526 {
527     TooltipWidget::instance()->hide();
528 }
529 
moveTo(QWidget * parent,QPoint point,bool offset)530 void BaseWindow::moveTo(QWidget *parent, QPoint point, bool offset)
531 {
532     if (offset)
533     {
534         point.rx() += 16;
535         point.ry() += 16;
536     }
537 
538     this->moveIntoDesktopRect(parent, point);
539 }
540 
resizeEvent(QResizeEvent *)541 void BaseWindow::resizeEvent(QResizeEvent *)
542 {
543     // Queue up save because: Window resized
544 #ifdef CHATTERINO
545     getApp()->windows->queueSave();
546 #endif
547 
548     //this->moveIntoDesktopRect(this);
549 
550 #ifdef USEWINSDK
551     if (this->hasCustomWindowFrame() && !this->isResizeFixing_)
552     {
553         this->isResizeFixing_ = true;
554         QTimer::singleShot(50, this, [this] {
555             RECT rect;
556             ::GetWindowRect((HWND)this->winId(), &rect);
557             ::SetWindowPos((HWND)this->winId(), nullptr, 0, 0,
558                            rect.right - rect.left + 1, rect.bottom - rect.top,
559                            SWP_NOMOVE | SWP_NOZORDER);
560             ::SetWindowPos((HWND)this->winId(), nullptr, 0, 0,
561                            rect.right - rect.left, rect.bottom - rect.top,
562                            SWP_NOMOVE | SWP_NOZORDER);
563             QTimer::singleShot(10, this, [this] {
564                 this->isResizeFixing_ = false;
565             });
566         });
567     }
568 #endif
569 
570     this->calcButtonsSizes();
571 }
572 
moveEvent(QMoveEvent * event)573 void BaseWindow::moveEvent(QMoveEvent *event)
574 {
575     // Queue up save because: Window position changed
576 #ifdef CHATTERINO
577     getApp()->windows->queueSave();
578 #endif
579 
580     BaseWidget::moveEvent(event);
581 }
582 
closeEvent(QCloseEvent *)583 void BaseWindow::closeEvent(QCloseEvent *)
584 {
585     this->closing.invoke();
586 }
587 
showEvent(QShowEvent *)588 void BaseWindow::showEvent(QShowEvent *)
589 {
590     this->moveIntoDesktopRect(this, this->pos());
591     if (this->frameless_)
592     {
593         QTimer::singleShot(30, this, [this] {
594             this->moveIntoDesktopRect(this, this->pos());
595         });
596     }
597 }
598 
moveIntoDesktopRect(QWidget * parent,QPoint point)599 void BaseWindow::moveIntoDesktopRect(QWidget *parent, QPoint point)
600 {
601     if (!this->stayInScreenRect_)
602         return;
603 
604     // move the widget into the screen geometry if it's not already in there
605     QDesktopWidget *desktop = QApplication::desktop();
606     QPoint globalCursorPos = QCursor::pos();
607 
608     QRect s = desktop->availableGeometry(parent);
609 
610     bool stickRight = false;
611     bool stickBottom = false;
612 
613     if (point.x() < s.left())
614     {
615         point.setX(s.left());
616     }
617     if (point.y() < s.top())
618     {
619         point.setY(s.top());
620     }
621     if (point.x() + this->width() > s.right())
622     {
623         stickRight = true;
624         point.setX(s.right() - this->width());
625     }
626     if (point.y() + this->height() > s.bottom())
627     {
628         stickBottom = true;
629         point.setY(s.bottom() - this->height());
630     }
631 
632     if (stickRight && stickBottom)
633     {
634         point.setY(globalCursorPos.y() - this->height() - 16);
635     }
636 
637     this->move(point);
638 }
639 
nativeEvent(const QByteArray & eventType,void * message,long * result)640 bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
641                              long *result)
642 {
643 #ifdef USEWINSDK
644     MSG *msg = reinterpret_cast<MSG *>(message);
645 
646     bool returnValue = false;
647 
648     switch (msg->message)
649     {
650         case WM_DPICHANGED:
651             returnValue = this->handleDPICHANGED(msg);
652             break;
653 
654         case WM_SHOWWINDOW:
655             returnValue = this->handleSHOWWINDOW(msg);
656             break;
657 
658         case WM_NCCALCSIZE:
659             returnValue = this->handleNCCALCSIZE(msg, result);
660             break;
661 
662         case WM_SIZE:
663             returnValue = this->handleSIZE(msg);
664             break;
665 
666         case WM_MOVE:
667             returnValue = this->handleMOVE(msg);
668             *result = 0;
669             break;
670 
671         case WM_NCHITTEST:
672             returnValue = this->handleNCHITTEST(msg, result);
673             break;
674 
675         default:
676             return QWidget::nativeEvent(eventType, message, result);
677     }
678 
679     QWidget::nativeEvent(eventType, message, result);
680 
681     return returnValue;
682 #else
683     return QWidget::nativeEvent(eventType, message, result);
684 #endif
685 }
686 
scaleChangedEvent(float scale)687 void BaseWindow::scaleChangedEvent(float scale)
688 {
689 #ifdef USEWINSDK
690     this->calcButtonsSizes();
691 #endif
692 
693     this->setFont(getFonts()->getFont(FontStyle::UiTabs, this->qtFontScale()));
694 }
695 
paintEvent(QPaintEvent *)696 void BaseWindow::paintEvent(QPaintEvent *)
697 {
698     QPainter painter(this);
699 
700     if (this->frameless_)
701     {
702         painter.setPen(QColor("#999"));
703         painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
704     }
705 
706     this->drawCustomWindowFrame(painter);
707 }
708 
updateScale()709 void BaseWindow::updateScale()
710 {
711     auto scale =
712         this->nativeScale_ * (this->flags_.has(DisableCustomScaling)
713                                   ? 1
714                                   : getABSettings()->getClampedUiScale());
715 
716     this->setScale(scale);
717 
718     for (auto child : this->findChildren<BaseWidget *>())
719     {
720         child->setScale(scale);
721     }
722 }
723 
calcButtonsSizes()724 void BaseWindow::calcButtonsSizes()
725 {
726     if (!this->shown_)
727     {
728         return;
729     }
730 
731     if ((this->width() / this->scale()) < 300)
732     {
733         if (this->ui_.minButton)
734             this->ui_.minButton->setScaleIndependantSize(30, 30);
735         if (this->ui_.maxButton)
736             this->ui_.maxButton->setScaleIndependantSize(30, 30);
737         if (this->ui_.exitButton)
738             this->ui_.exitButton->setScaleIndependantSize(30, 30);
739     }
740     else
741     {
742         if (this->ui_.minButton)
743             this->ui_.minButton->setScaleIndependantSize(46, 30);
744         if (this->ui_.maxButton)
745             this->ui_.maxButton->setScaleIndependantSize(46, 30);
746         if (this->ui_.exitButton)
747             this->ui_.exitButton->setScaleIndependantSize(46, 30);
748     }
749 }
750 
drawCustomWindowFrame(QPainter & painter)751 void BaseWindow::drawCustomWindowFrame(QPainter &painter)
752 {
753 #ifdef USEWINSDK
754     if (this->hasCustomWindowFrame())
755     {
756         QColor bg = this->overrideBackgroundColor_.value_or(
757             this->theme->window.background);
758         painter.fillRect(QRect(1, 2, this->width() - 2, this->height() - 3),
759                          bg);
760     }
761 #endif
762 }
763 
handleDPICHANGED(MSG * msg)764 bool BaseWindow::handleDPICHANGED(MSG *msg)
765 {
766 #ifdef USEWINSDK
767     int dpi = HIWORD(msg->wParam);
768 
769     float _scale = dpi / 96.f;
770 
771     auto *prcNewWindow = reinterpret_cast<RECT *>(msg->lParam);
772     SetWindowPos(msg->hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
773                  prcNewWindow->right - prcNewWindow->left,
774                  prcNewWindow->bottom - prcNewWindow->top,
775                  SWP_NOZORDER | SWP_NOACTIVATE);
776 
777     this->nativeScale_ = _scale;
778     this->updateScale();
779 
780     return true;
781 #else
782     return false;
783 #endif
784 }
785 
handleSHOWWINDOW(MSG * msg)786 bool BaseWindow::handleSHOWWINDOW(MSG *msg)
787 {
788 #ifdef USEWINSDK
789     if (auto dpi = getWindowDpi(msg->hwnd))
790     {
791         this->nativeScale_ = dpi.get() / 96.f;
792         this->updateScale();
793     }
794 
795     if (!this->shown_ && this->isVisible())
796     {
797         if (this->hasCustomWindowFrame())
798         {
799             this->shown_ = true;
800 
801             const MARGINS shadow = {8, 8, 8, 8};
802             DwmExtendFrameIntoClientArea(HWND(this->winId()), &shadow);
803         }
804         if (!this->initalBounds_.isNull())
805         {
806             ::SetWindowPos(msg->hwnd, nullptr, this->initalBounds_.x(),
807                            this->initalBounds_.y(), this->initalBounds_.width(),
808                            this->initalBounds_.height(),
809                            SWP_NOZORDER | SWP_NOACTIVATE);
810             this->currentBounds_ = this->initalBounds_;
811         }
812     }
813 
814     this->calcButtonsSizes();
815 
816     return true;
817 #else
818     return false;
819 #endif
820 }
821 
handleNCCALCSIZE(MSG * msg,long * result)822 bool BaseWindow::handleNCCALCSIZE(MSG *msg, long *result)
823 {
824 #ifdef USEWINSDK
825     if (this->hasCustomWindowFrame())
826     {
827         // int cx = GetSystemMetrics(SM_CXSIZEFRAME);
828         // int cy = GetSystemMetrics(SM_CYSIZEFRAME);
829 
830         if (msg->wParam == TRUE)
831         {
832             NCCALCSIZE_PARAMS *ncp =
833                 (reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam));
834             ncp->lppos->flags |= SWP_NOREDRAW;
835             RECT *clientRect = &ncp->rgrc[0];
836 
837             clientRect->top -= 1;
838         }
839 
840         *result = 0;
841         return true;
842     }
843     return false;
844 #else
845     return false;
846 #endif
847 }
848 
handleSIZE(MSG * msg)849 bool BaseWindow::handleSIZE(MSG *msg)
850 {
851 #ifdef USEWINSDK
852     if (this->ui_.windowLayout)
853     {
854         if (this->frameless_)
855         {
856             //
857         }
858         else if (this->hasCustomWindowFrame())
859         {
860             if (msg->wParam == SIZE_MAXIMIZED)
861             {
862                 auto offset = int(
863                     getWindowDpi(HWND(this->winId())).value_or(96) * 8 / 96);
864 
865                 this->ui_.windowLayout->setContentsMargins(offset, offset,
866                                                            offset, offset);
867             }
868             else
869             {
870                 this->ui_.windowLayout->setContentsMargins(0, 1, 0, 0);
871             }
872 
873             this->isNotMinimizedOrMaximized_ = msg->wParam == SIZE_RESTORED;
874 
875             if (this->isNotMinimizedOrMaximized_)
876             {
877                 RECT rect;
878                 ::GetWindowRect(msg->hwnd, &rect);
879                 this->currentBounds_ =
880                     QRect(QPoint(rect.left, rect.top),
881                           QPoint(rect.right - 1, rect.bottom - 1));
882             }
883             this->useNextBounds_.stop();
884         }
885     }
886     return false;
887 #else
888     return false;
889 #endif
890 }
891 
handleMOVE(MSG * msg)892 bool BaseWindow::handleMOVE(MSG *msg)
893 {
894 #ifdef USEWINSDK
895     if (this->isNotMinimizedOrMaximized_)
896     {
897         RECT rect;
898         ::GetWindowRect(msg->hwnd, &rect);
899         this->nextBounds_ = QRect(QPoint(rect.left, rect.top),
900                                   QPoint(rect.right - 1, rect.bottom - 1));
901 
902         this->useNextBounds_.start(10);
903     }
904 #endif
905     return false;
906 }
907 
handleNCHITTEST(MSG * msg,long * result)908 bool BaseWindow::handleNCHITTEST(MSG *msg, long *result)
909 {
910 #ifdef USEWINSDK
911     const LONG border_width = 8;  // in pixels
912     RECT winrect;
913     GetWindowRect(HWND(winId()), &winrect);
914 
915     long x = GET_X_LPARAM(msg->lParam);
916     long y = GET_Y_LPARAM(msg->lParam);
917 
918     QPoint point(x - winrect.left, y - winrect.top);
919 
920     if (this->hasCustomWindowFrame())
921     {
922         *result = 0;
923 
924         bool resizeWidth = minimumWidth() != maximumWidth();
925         bool resizeHeight = minimumHeight() != maximumHeight();
926 
927         if (resizeWidth)
928         {
929             // left border
930             if (x < winrect.left + border_width)
931             {
932                 *result = HTLEFT;
933             }
934             // right border
935             if (x >= winrect.right - border_width)
936             {
937                 *result = HTRIGHT;
938             }
939         }
940         if (resizeHeight)
941         {
942             // bottom border
943             if (y >= winrect.bottom - border_width)
944             {
945                 *result = HTBOTTOM;
946             }
947             // top border
948             if (y < winrect.top + border_width)
949             {
950                 *result = HTTOP;
951             }
952         }
953         if (resizeWidth && resizeHeight)
954         {
955             // bottom left corner
956             if (x >= winrect.left && x < winrect.left + border_width &&
957                 y < winrect.bottom && y >= winrect.bottom - border_width)
958             {
959                 *result = HTBOTTOMLEFT;
960             }
961             // bottom right corner
962             if (x < winrect.right && x >= winrect.right - border_width &&
963                 y < winrect.bottom && y >= winrect.bottom - border_width)
964             {
965                 *result = HTBOTTOMRIGHT;
966             }
967             // top left corner
968             if (x >= winrect.left && x < winrect.left + border_width &&
969                 y >= winrect.top && y < winrect.top + border_width)
970             {
971                 *result = HTTOPLEFT;
972             }
973             // top right corner
974             if (x < winrect.right && x >= winrect.right - border_width &&
975                 y >= winrect.top && y < winrect.top + border_width)
976             {
977                 *result = HTTOPRIGHT;
978             }
979         }
980 
981         if (*result == 0)
982         {
983             bool client = false;
984 
985             for (QWidget *widget : this->ui_.buttons)
986             {
987                 if (widget->geometry().contains(point))
988                 {
989                     client = true;
990                 }
991             }
992 
993             if (this->ui_.layoutBase->geometry().contains(point))
994             {
995                 client = true;
996             }
997 
998             if (client)
999             {
1000                 *result = HTCLIENT;
1001             }
1002             else
1003             {
1004                 *result = HTCAPTION;
1005             }
1006         }
1007 
1008         return true;
1009     }
1010     else if (this->flags_.has(FramelessDraggable))
1011     {
1012         *result = 0;
1013         bool client = false;
1014 
1015         if (auto widget = this->childAt(point))
1016         {
1017             std::function<bool(QWidget *)> recursiveCheckMouseTracking;
1018             recursiveCheckMouseTracking = [&](QWidget *widget) {
1019                 if (widget == nullptr)
1020                 {
1021                     return false;
1022                 }
1023 
1024                 if (widget->hasMouseTracking())
1025                 {
1026                     return true;
1027                 }
1028 
1029                 return recursiveCheckMouseTracking(widget->parentWidget());
1030             };
1031 
1032             if (recursiveCheckMouseTracking(widget))
1033             {
1034                 client = true;
1035             }
1036         }
1037 
1038         if (client)
1039         {
1040             *result = HTCLIENT;
1041         }
1042         else
1043         {
1044             *result = HTCAPTION;
1045         }
1046 
1047         return true;
1048     }
1049     return false;
1050 #else
1051     return false;
1052 #endif
1053 }
1054 
1055 }  // namespace chatterino
1056