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