win_frame_view.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // Copyright (c) 2014 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. //
  5. // Portions of this file are sourced from
  6. // chrome/browser/ui/views/frame/glass_browser_frame_view.cc,
  7. // Copyright (c) 2012 The Chromium Authors,
  8. // which is governed by a BSD-style license
  9. #include "shell/browser/ui/views/win_frame_view.h"
  10. #include <dwmapi.h>
  11. #include <memory>
  12. #include "base/win/windows_version.h"
  13. #include "shell/browser/native_window_views.h"
  14. #include "shell/browser/ui/views/win_caption_button_container.h"
  15. #include "ui/base/win/hwnd_metrics.h"
  16. #include "ui/display/win/dpi.h"
  17. #include "ui/display/win/screen_win.h"
  18. #include "ui/gfx/geometry/dip_util.h"
  19. #include "ui/views/background.h"
  20. #include "ui/views/widget/widget.h"
  21. #include "ui/views/win/hwnd_util.h"
  22. namespace electron {
  23. const char WinFrameView::kViewClassName[] = "WinFrameView";
  24. WinFrameView::WinFrameView() = default;
  25. WinFrameView::~WinFrameView() = default;
  26. void WinFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
  27. window_ = window;
  28. frame_ = frame;
  29. if (window->IsWindowControlsOverlayEnabled()) {
  30. caption_button_container_ =
  31. AddChildView(std::make_unique<WinCaptionButtonContainer>(this));
  32. } else {
  33. caption_button_container_ = nullptr;
  34. }
  35. }
  36. SkColor WinFrameView::GetReadableFeatureColor(SkColor background_color) {
  37. // color_utils::GetColorWithMaxContrast()/IsDark() aren't used here because
  38. // they switch based on the Chrome light/dark endpoints, while we want to use
  39. // the system native behavior below.
  40. const auto windows_luma = [](SkColor c) {
  41. return 0.25f * SkColorGetR(c) + 0.625f * SkColorGetG(c) +
  42. 0.125f * SkColorGetB(c);
  43. };
  44. return windows_luma(background_color) <= 128.0f ? SK_ColorWHITE
  45. : SK_ColorBLACK;
  46. }
  47. void WinFrameView::InvalidateCaptionButtons() {
  48. if (!caption_button_container_)
  49. return;
  50. caption_button_container_->SetBackground(
  51. views::CreateSolidBackground(window()->overlay_button_color()));
  52. caption_button_container_->InvalidateLayout();
  53. caption_button_container_->SchedulePaint();
  54. }
  55. gfx::Rect WinFrameView::GetWindowBoundsForClientBounds(
  56. const gfx::Rect& client_bounds) const {
  57. return views::GetWindowBoundsForClientBounds(
  58. static_cast<views::View*>(const_cast<WinFrameView*>(this)),
  59. client_bounds);
  60. }
  61. int WinFrameView::FrameBorderThickness() const {
  62. return (IsMaximized() || frame()->IsFullscreen())
  63. ? 0
  64. : display::win::ScreenWin::GetSystemMetricsInDIP(SM_CXSIZEFRAME);
  65. }
  66. views::View* WinFrameView::TargetForRect(views::View* root,
  67. const gfx::Rect& rect) {
  68. if (NonClientHitTest(rect.origin()) != HTCLIENT) {
  69. // Custom system titlebar returns non HTCLIENT value, however event should
  70. // be handled by the view, not by the system, because there are no system
  71. // buttons underneath.
  72. if (!ShouldCustomDrawSystemTitlebar()) {
  73. return this;
  74. }
  75. auto local_point = rect.origin();
  76. ConvertPointToTarget(parent(), caption_button_container_, &local_point);
  77. if (!caption_button_container_->HitTestPoint(local_point)) {
  78. return this;
  79. }
  80. }
  81. return NonClientFrameView::TargetForRect(root, rect);
  82. }
  83. int WinFrameView::NonClientHitTest(const gfx::Point& point) {
  84. if (window_->has_frame())
  85. return frame_->client_view()->NonClientHitTest(point);
  86. if (ShouldCustomDrawSystemTitlebar()) {
  87. // See if the point is within any of the window controls.
  88. if (caption_button_container_) {
  89. gfx::Point local_point = point;
  90. ConvertPointToTarget(parent(), caption_button_container_, &local_point);
  91. if (caption_button_container_->HitTestPoint(local_point)) {
  92. const int hit_test_result =
  93. caption_button_container_->NonClientHitTest(local_point);
  94. if (hit_test_result != HTNOWHERE)
  95. return hit_test_result;
  96. }
  97. }
  98. // On Windows 8+, the caption buttons are almost butted up to the top right
  99. // corner of the window. This code ensures the mouse isn't set to a size
  100. // cursor while hovering over the caption buttons, thus giving the incorrect
  101. // impression that the user can resize the window.
  102. if (base::win::GetVersion() >= base::win::Version::WIN8) {
  103. RECT button_bounds = {0};
  104. if (SUCCEEDED(DwmGetWindowAttribute(
  105. views::HWNDForWidget(frame()), DWMWA_CAPTION_BUTTON_BOUNDS,
  106. &button_bounds, sizeof(button_bounds)))) {
  107. gfx::RectF button_bounds_in_dips = gfx::ConvertRectToDips(
  108. gfx::Rect(button_bounds), display::win::GetDPIScale());
  109. // TODO(crbug.com/1131681): GetMirroredRect() requires an integer rect,
  110. // but the size in DIPs may not be an integer with a fractional device
  111. // scale factor. If we want to keep using integers, the choice to use
  112. // ToFlooredRectDeprecated() seems to be doing the wrong thing given the
  113. // comment below about insetting 1 DIP instead of 1 physical pixel. We
  114. // should probably use ToEnclosedRect() and then we could have inset 1
  115. // physical pixel here.
  116. gfx::Rect buttons = GetMirroredRect(
  117. gfx::ToFlooredRectDeprecated(button_bounds_in_dips));
  118. // There is a small one-pixel strip right above the caption buttons in
  119. // which the resize border "peeks" through.
  120. constexpr int kCaptionButtonTopInset = 1;
  121. // The sizing region at the window edge above the caption buttons is
  122. // 1 px regardless of scale factor. If we inset by 1 before converting
  123. // to DIPs, the precision loss might eliminate this region entirely. The
  124. // best we can do is to inset after conversion. This guarantees we'll
  125. // show the resize cursor when resizing is possible. The cost of which
  126. // is also maybe showing it over the portion of the DIP that isn't the
  127. // outermost pixel.
  128. buttons.Inset(gfx::Insets::TLBR(0, kCaptionButtonTopInset, 0, 0));
  129. if (buttons.Contains(point))
  130. return HTNOWHERE;
  131. }
  132. }
  133. int top_border_thickness = FrameTopBorderThickness(false);
  134. // At the window corners the resize area is not actually bigger, but the 16
  135. // pixels at the end of the top and bottom edges trigger diagonal resizing.
  136. constexpr int kResizeCornerWidth = 16;
  137. int window_component = GetHTComponentForFrame(
  138. point, gfx::Insets::TLBR(top_border_thickness, 0, 0, 0),
  139. top_border_thickness, kResizeCornerWidth - FrameBorderThickness(),
  140. frame()->widget_delegate()->CanResize());
  141. if (window_component != HTNOWHERE)
  142. return window_component;
  143. }
  144. // Use the parent class's hittest last
  145. return FramelessView::NonClientHitTest(point);
  146. }
  147. const char* WinFrameView::GetClassName() const {
  148. return kViewClassName;
  149. }
  150. bool WinFrameView::IsMaximized() const {
  151. return frame()->IsMaximized();
  152. }
  153. bool WinFrameView::ShouldCustomDrawSystemTitlebar() const {
  154. return window()->IsWindowControlsOverlayEnabled();
  155. }
  156. void WinFrameView::Layout() {
  157. LayoutCaptionButtons();
  158. if (window()->IsWindowControlsOverlayEnabled()) {
  159. LayoutWindowControlsOverlay();
  160. }
  161. NonClientFrameView::Layout();
  162. }
  163. int WinFrameView::FrameTopBorderThickness(bool restored) const {
  164. // Mouse and touch locations are floored but GetSystemMetricsInDIP is rounded,
  165. // so we need to floor instead or else the difference will cause the hittest
  166. // to fail when it ought to succeed.
  167. return std::floor(
  168. FrameTopBorderThicknessPx(restored) /
  169. display::win::ScreenWin::GetScaleFactorForHWND(HWNDForView(this)));
  170. }
  171. int WinFrameView::FrameTopBorderThicknessPx(bool restored) const {
  172. // Distinct from FrameBorderThickness() because we can't inset the top
  173. // border, otherwise Windows will give us a standard titlebar.
  174. // For maximized windows this is not true, and the top border must be
  175. // inset in order to avoid overlapping the monitor above.
  176. // See comments in BrowserDesktopWindowTreeHostWin::GetClientAreaInsets().
  177. const bool needs_no_border =
  178. (ShouldCustomDrawSystemTitlebar() && frame()->IsMaximized()) ||
  179. frame()->IsFullscreen();
  180. if (needs_no_border && !restored)
  181. return 0;
  182. // Note that this method assumes an equal resize handle thickness on all
  183. // sides of the window.
  184. // TODO(dfried): Consider having it return a gfx::Insets object instead.
  185. return ui::GetFrameThickness(
  186. MonitorFromWindow(HWNDForView(this), MONITOR_DEFAULTTONEAREST));
  187. }
  188. int WinFrameView::TitlebarMaximizedVisualHeight() const {
  189. int maximized_height =
  190. display::win::ScreenWin::GetSystemMetricsInDIP(SM_CYCAPTION);
  191. return maximized_height;
  192. }
  193. // NOTE(@mlaurencin): Usage of IsWebUITabStrip simplified out from Chromium
  194. int WinFrameView::TitlebarHeight(int custom_height) const {
  195. if (frame()->IsFullscreen() && !IsMaximized())
  196. return 0;
  197. int height = TitlebarMaximizedVisualHeight() +
  198. FrameTopBorderThickness(false) - WindowTopY();
  199. if (custom_height > TitlebarMaximizedVisualHeight())
  200. height = custom_height - WindowTopY();
  201. return height;
  202. }
  203. // NOTE(@mlaurencin): Usage of IsWebUITabStrip simplified out from Chromium
  204. int WinFrameView::WindowTopY() const {
  205. // The window top is SM_CYSIZEFRAME pixels when maximized (see the comment in
  206. // FrameTopBorderThickness()) and floor(system dsf) pixels when restored.
  207. // Unfortunately we can't represent either of those at hidpi without using
  208. // non-integral dips, so we return the closest reasonable values instead.
  209. if (IsMaximized())
  210. return FrameTopBorderThickness(false);
  211. return 1;
  212. }
  213. void WinFrameView::LayoutCaptionButtons() {
  214. if (!caption_button_container_)
  215. return;
  216. // Non-custom system titlebar already contains caption buttons.
  217. if (!ShouldCustomDrawSystemTitlebar()) {
  218. caption_button_container_->SetVisible(false);
  219. return;
  220. }
  221. caption_button_container_->SetVisible(true);
  222. const gfx::Size preferred_size =
  223. caption_button_container_->GetPreferredSize();
  224. int custom_height = window()->titlebar_overlay_height();
  225. int height = TitlebarHeight(custom_height);
  226. // TODO(mlaurencin): This -1 creates a 1 pixel margin between the right
  227. // edge of the button container and the edge of the window, allowing for this
  228. // edge portion to return the correct hit test and be manually resized
  229. // properly. Alternatives can be explored, but the differences in view
  230. // structures between Electron and Chromium may result in this as the best
  231. // option.
  232. int variable_width =
  233. IsMaximized() ? preferred_size.width() : preferred_size.width() - 1;
  234. caption_button_container_->SetBounds(width() - preferred_size.width(),
  235. WindowTopY(), variable_width, height);
  236. // Needed for heights larger than default
  237. caption_button_container_->SetButtonSize(gfx::Size(0, height));
  238. }
  239. void WinFrameView::LayoutWindowControlsOverlay() {
  240. int overlay_height = window()->titlebar_overlay_height();
  241. if (overlay_height == 0) {
  242. // Accounting for the 1 pixel margin at the top of the button container
  243. overlay_height = IsMaximized()
  244. ? caption_button_container_->size().height()
  245. : caption_button_container_->size().height() + 1;
  246. }
  247. int overlay_width = caption_button_container_->size().width();
  248. int bounding_rect_width = width() - overlay_width;
  249. auto bounding_rect =
  250. GetMirroredRect(gfx::Rect(0, 0, bounding_rect_width, overlay_height));
  251. window()->SetWindowControlsOverlayRect(bounding_rect);
  252. window()->NotifyLayoutWindowControlsOverlay();
  253. }
  254. } // namespace electron