From b115199f93847fae02f0c96bde5be768440876e3 Mon Sep 17 00:00:00 2001 From: "jeremya@chromium.org" Date: Wed, 20 Jun 2012 00:49:30 +0000 Subject: [PATCH] Refactor the shell window frame view on win/cros. This brings the structure of ShellWindowFrameView closer to that of CustomFrameViewAsh, and fixes a bunch of bugs with the shell window on Aura. (Notably, in aura, shell windows can now be resized outside the window bounds like other windows can, and they no longer show two title bars.) BUG=132857,132858 R=ben@chromium.org Review URL: https://chromiumcodereview.appspot.com/10563013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@143104 0039d316-1c4b-4281-b951-d872f2087c98 --- .../ui/views/extensions/shell_window_views.cc | 353 +++++++++++------- .../ui/views/extensions/shell_window_views.h | 11 +- 2 files changed, 219 insertions(+), 145 deletions(-) diff --git a/chrome/browser/ui/views/extensions/shell_window_views.cc b/chrome/browser/ui/views/extensions/shell_window_views.cc index b0a8ce60c7c784..8abcd93fd64fc8 100644 --- a/chrome/browser/ui/views/extensions/shell_window_views.cc +++ b/chrome/browser/ui/views/extensions/shell_window_views.cc @@ -12,13 +12,16 @@ #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "grit/ui_resources_standard.h" +#include "grit/ui_strings.h" // Accessibility names #include "third_party/skia/include/core/SkPaint.h" #include "ui/base/hit_test.h" +#include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/canvas.h" #include "ui/gfx/image/image.h" #include "ui/gfx/path.h" #include "ui/gfx/scoped_sk_region.h" +#include "ui/views/controls/button/button.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/webview/webview.h" #include "ui/views/layout/grid_layout.h" @@ -33,19 +36,38 @@ #if defined(USE_ASH) #include "ash/wm/custom_frame_view_ash.h" +#include "ui/aura/window.h" +#endif + +namespace { +// TODO(jeremya): these are copy/pasted from ash/wm/frame_painter.cc, and I'd +// like to find a way to avoid duplicating the constants. +#if defined(USE_ASH) +const int kResizeOutsideBoundsSizeTouch = 30; +const int kResizeOutsideBoundsSize = 6; +const int kResizeInsideBoundsSize = 1; +const int kResizeAreaCornerSize = 16; +#else +const int kResizeOutsideBoundsSizeTouch = 0; +const int kResizeOutsideBoundsSize = 0; +const int kResizeInsideBoundsSize = 5; +const int kResizeAreaCornerSize = 16; #endif -// Number of pixels around the edge of the window that can be dragged to -// resize the window. -static const int kResizeBorderWidth = 5; // Height of the chrome-style caption, in pixels. -static const int kCaptionHeight = 25; +const int kCaptionHeight = 25; +} // namespace -class ShellWindowFrameView : public views::NonClientFrameView { +class ShellWindowFrameView : public views::NonClientFrameView, + public views::ButtonListener { public: - explicit ShellWindowFrameView(ShellWindowViews* window); + static const char kViewClassName[]; + + ShellWindowFrameView(); virtual ~ShellWindowFrameView(); + void Init(views::Widget* frame); + // views::NonClientFrameView implementation. virtual gfx::Rect GetBoundsForClientView() const OVERRIDE; virtual gfx::Rect GetWindowBoundsForClientBounds( @@ -55,137 +77,213 @@ class ShellWindowFrameView : public views::NonClientFrameView { gfx::Path* window_mask) OVERRIDE; virtual void ResetWindowControls() OVERRIDE {} virtual void UpdateWindowIcon() OVERRIDE {} + + // views::View implementation. + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual void Layout() OVERRIDE; + virtual std::string GetClassName() const OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; virtual gfx::Size GetMinimumSize() OVERRIDE; virtual gfx::Size GetMaximumSize() OVERRIDE; private: - DISALLOW_COPY_AND_ASSIGN(ShellWindowFrameView); + // views::ButtonListener implementation. + virtual void ButtonPressed(views::Button* sender, const views::Event& event) + OVERRIDE; + + views::Widget* frame_; + views::ImageButton* close_button_; - ShellWindowViews* window_; + DISALLOW_COPY_AND_ASSIGN(ShellWindowFrameView); }; -ShellWindowFrameView::ShellWindowFrameView(ShellWindowViews* window) - : window_(window) { +const char ShellWindowFrameView::kViewClassName[] = + "browser/ui/views/extensions/ShellWindowFrameView"; + +ShellWindowFrameView::ShellWindowFrameView() + : frame_(NULL), + close_button_(NULL) { } ShellWindowFrameView::~ShellWindowFrameView() { } +void ShellWindowFrameView::Init(views::Widget* frame) { + frame_ = frame; + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + close_button_ = new views::ImageButton(this); + close_button_->SetImage(views::CustomButton::BS_NORMAL, + rb.GetNativeImageNamed(IDR_CLOSE_BAR).ToImageSkia()); + close_button_->SetImage(views::CustomButton::BS_HOT, + rb.GetNativeImageNamed(IDR_CLOSE_BAR_H).ToImageSkia()); + close_button_->SetImage(views::CustomButton::BS_PUSHED, + rb.GetNativeImageNamed(IDR_CLOSE_BAR_P).ToImageSkia()); + close_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); + AddChildView(close_button_); + +#if defined(USE_ASH) + aura::Window* window = frame->GetNativeWindow(); + // Ensure we get resize cursors for a few pixels outside our bounds. + int outside_bounds = ui::GetDisplayLayout() == ui::LAYOUT_TOUCH ? + kResizeOutsideBoundsSizeTouch : + kResizeOutsideBoundsSize; + window->set_hit_test_bounds_override_outer( + gfx::Insets(-outside_bounds, -outside_bounds, + -outside_bounds, -outside_bounds)); + // Ensure we get resize cursors just inside our bounds as well. + // TODO(jeremya): do we need to update these when in fullscreen/maximized? + window->set_hit_test_bounds_override_inner( + gfx::Insets(kResizeInsideBoundsSize, kResizeInsideBoundsSize, + kResizeInsideBoundsSize, kResizeInsideBoundsSize)); +#endif +} + gfx::Rect ShellWindowFrameView::GetBoundsForClientView() const { - return gfx::Rect(0, 0, width(), height()); + if (frame_->IsFullscreen()) + return bounds(); + return gfx::Rect(0, kCaptionHeight, width(), + std::max(0, height() - kCaptionHeight)); } gfx::Rect ShellWindowFrameView::GetWindowBoundsForClientBounds( const gfx::Rect& client_bounds) const { - return client_bounds; + int closeButtonOffsetX = + (kCaptionHeight - close_button_->height()) / 2; + int header_width = close_button_->width() + closeButtonOffsetX * 2; + return gfx::Rect(client_bounds.x(), + std::max(0, client_bounds.y() - kCaptionHeight), + std::max(header_width, client_bounds.width()), + client_bounds.height() + kCaptionHeight); } -gfx::Size ShellWindowFrameView::GetMinimumSize() { - return window_->minimum_size_; -} +int ShellWindowFrameView::NonClientHitTest(const gfx::Point& point) { + if (frame_->IsFullscreen()) + return HTCLIENT; -gfx::Size ShellWindowFrameView::GetMaximumSize() { - return window_->maximum_size_; -} +#if defined(USE_ASH) + gfx::Rect expanded_bounds = bounds(); + int outside_bounds = ui::GetDisplayLayout() == ui::LAYOUT_TOUCH ? + kResizeOutsideBoundsSizeTouch : + kResizeOutsideBoundsSize; + expanded_bounds.Inset(-outside_bounds, -outside_bounds); + if (!expanded_bounds.Contains(point)) + return HTNOWHERE; +#endif -int ShellWindowFrameView::NonClientHitTest(const gfx::Point& point) { - // No resize border when maximized. - if (GetWidget()->IsMaximized()) - return HTCAPTION; - int x = point.x(); - int y = point.y(); - if (x <= kResizeBorderWidth) { - if (y <= kResizeBorderWidth) - return HTTOPLEFT; - if (y >= height() - kResizeBorderWidth) - return HTBOTTOMLEFT; - return HTLEFT; - } - if (x >= width() - kResizeBorderWidth) { - if (y <= kResizeBorderWidth) - return HTTOPRIGHT; - if (y >= height() - kResizeBorderWidth) - return HTBOTTOMRIGHT; - return HTRIGHT; - } - if (y <= kResizeBorderWidth) - return HTTOP; - if (y >= height() - kResizeBorderWidth) - return HTBOTTOM; + // Check the frame first, as we allow a small area overlapping the contents + // to be used for resize handles. + bool can_ever_resize = frame_->widget_delegate() ? + frame_->widget_delegate()->CanResize() : + false; + // Don't allow overlapping resize handles when the window is maximized or + // fullscreen, as it can't be resized in those states. + int resize_border = + frame_->IsMaximized() || frame_->IsFullscreen() ? 0 : + kResizeInsideBoundsSize; + int frame_component = GetHTComponentForFrame(point, + resize_border, + resize_border, + kResizeAreaCornerSize, + kResizeAreaCornerSize, + can_ever_resize); + if (frame_component != HTNOWHERE) + return frame_component; + + int client_component = frame_->client_view()->NonClientHitTest(point); + if (client_component != HTNOWHERE) + return client_component; + + // Then see if the point is within any of the window controls. + if (close_button_->visible() && + close_button_->GetMirroredBounds().Contains(point)) + return HTCLOSE; + + // Caption is a safe default. return HTCAPTION; } void ShellWindowFrameView::GetWindowMask(const gfx::Size& size, gfx::Path* window_mask) { - // Don't touch it. + // We got nothing to say about no window mask. +} + +gfx::Size ShellWindowFrameView::GetPreferredSize() { + gfx::Size pref = frame_->client_view()->GetPreferredSize(); + gfx::Rect bounds(0, 0, pref.width(), pref.height()); + return frame_->non_client_view()->GetWindowBoundsForClientBounds( + bounds).size(); +} + +void ShellWindowFrameView::Layout() { + gfx::Size close_size = close_button_->GetPreferredSize(); + int closeButtonOffsetY = + (kCaptionHeight - close_size.height()) / 2; + int closeButtonOffsetX = closeButtonOffsetY; + close_button_->SetBounds( + width() - closeButtonOffsetX - close_size.width(), + closeButtonOffsetY, + close_size.width(), + close_size.height()); +} + +void ShellWindowFrameView::OnPaint(gfx::Canvas* canvas) { + // TODO(jeremya): different look for inactive? + SkPaint paint; + paint.setAntiAlias(false); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(SK_ColorWHITE); + gfx::Path path; + const int radius = 1; + path.moveTo(0, radius); + path.lineTo(radius, 0); + path.lineTo(width() - radius - 1, 0); + path.lineTo(width(), radius + 1); + path.lineTo(width(), kCaptionHeight); + path.lineTo(0, kCaptionHeight); + path.close(); + canvas->DrawPath(path, paint); } -namespace { - -class TitleBarBackground : public views::Background { - public: - TitleBarBackground() {} - virtual ~TitleBarBackground() {} - virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE { - SkPaint paint; - paint.setAntiAlias(false); - paint.setStyle(SkPaint::kFill_Style); - paint.setColor(SK_ColorWHITE); - gfx::Path path; - int radius = 1; - int width = view->width(); - int height = view->height(); - path.moveTo(0, radius); - path.lineTo(radius, 0); - path.lineTo(width - radius - 1, 0); - path.lineTo(width, radius + 1); - path.lineTo(width, height); - path.lineTo(0, height); - path.close(); - canvas->DrawPath(path, paint); - } -}; - -class TitleBarView : public views::View { - public: - explicit TitleBarView(views::ButtonListener* listener); - virtual ~TitleBarView(); -}; +std::string ShellWindowFrameView::GetClassName() const { + return kViewClassName; +} -TitleBarView::TitleBarView(views::ButtonListener* listener) { - set_background(new TitleBarBackground()); - ResourceBundle& rb = ResourceBundle::GetSharedInstance(); - views::ImageButton* close_button = new views::ImageButton(listener); - close_button->SetImage(views::CustomButton::BS_NORMAL, - rb.GetNativeImageNamed(IDR_CLOSE_BAR).ToImageSkia()); - close_button->SetImage(views::CustomButton::BS_HOT, - rb.GetNativeImageNamed(IDR_CLOSE_BAR_H).ToImageSkia()); - close_button->SetImage(views::CustomButton::BS_PUSHED, - rb.GetNativeImageNamed(IDR_CLOSE_BAR_P).ToImageSkia()); - views::GridLayout* layout = new views::GridLayout(this); - layout->SetInsets(0, 0, 0, - (kCaptionHeight - close_button->GetPreferredSize().height()) / 2); - SetLayoutManager(layout); - views::ColumnSet* columns = layout->AddColumnSet(0); - columns->AddPaddingColumn(1, 1); - columns->AddColumn( - views::GridLayout::TRAILING, views::GridLayout::CENTER, 0, - views::GridLayout::USE_PREF, 0, 0); - layout->StartRow(1, 0); - layout->AddView(close_button); +gfx::Size ShellWindowFrameView::GetMinimumSize() { + gfx::Size min_size = frame_->client_view()->GetMinimumSize(); + // Ensure we can display the top of the caption area. + gfx::Rect client_bounds = GetBoundsForClientView(); + min_size.Enlarge(0, client_bounds.y()); + // Ensure we have enough space for the window icon and buttons. We allow + // the title string to collapse to zero width. + int closeButtonOffsetX = + (kCaptionHeight - close_button_->height()) / 2; + int header_width = close_button_->width() + closeButtonOffsetX * 2; + if (header_width > min_size.width()) + min_size.set_width(header_width); + return min_size; } -TitleBarView::~TitleBarView() { +gfx::Size ShellWindowFrameView::GetMaximumSize() { + gfx::Size max_size = frame_->client_view()->GetMaximumSize(); + if (!max_size.IsEmpty()) { + gfx::Rect client_bounds = GetBoundsForClientView(); + max_size.Enlarge(0, client_bounds.y()); + } + return max_size; } -}; // namespace +void ShellWindowFrameView::ButtonPressed(views::Button* sender, + const views::Event& event) { + if (sender == close_button_) + frame_->Close(); +} ShellWindowViews::ShellWindowViews(Profile* profile, const extensions::Extension* extension, const GURL& url, const ShellWindow::CreateParams& win_params) : ShellWindow(profile, extension, url), - title_view_(NULL), web_view_(NULL), is_fullscreen_(false), use_custom_frame_( @@ -194,12 +292,13 @@ ShellWindowViews::ShellWindowViews(Profile* profile, views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); params.delegate = this; params.remove_standard_frame = true; - params.bounds = win_params.bounds; minimum_size_ = win_params.minimum_size; maximum_size_ = win_params.maximum_size; - if (!use_custom_frame_) - params.bounds.set_height(params.bounds.height() + kCaptionHeight); window_->Init(params); + gfx::Rect window_bounds = + window_->non_client_view()->GetWindowBoundsForClientBounds( + win_params.bounds); + window_->SetBounds(window_bounds); #if defined(OS_WIN) && !defined(USE_AURA) std::string app_name = web_app::GenerateApplicationNameFromExtensionId( extension->id()); @@ -216,16 +315,20 @@ ShellWindowViews::ShellWindowViews(Profile* profile, void ShellWindowViews::ViewHierarchyChanged( bool is_add, views::View *parent, views::View *child) { if (is_add && child == this) { - if (!use_custom_frame_) { - title_view_ = new TitleBarView(this); - AddChildView(title_view_); - } web_view_ = new views::WebView(NULL); AddChildView(web_view_); web_view_->SetWebContents(web_contents()); } } +gfx::Size ShellWindowViews::GetMinimumSize() { + return minimum_size_; +} + +gfx::Size ShellWindowViews::GetMaximumSize() { + return maximum_size_; +} + void ShellWindowViews::SetFullscreen(bool fullscreen) { is_fullscreen_ = fullscreen; window_->SetFullscreen(fullscreen); @@ -330,11 +433,6 @@ void ShellWindowViews::DeleteDelegate() { OnNativeClose(); } -void ShellWindowViews::ButtonPressed( - views::Button* sender, const views::Event& event) { - Close(); -} - bool ShellWindowViews::CanResize() const { return true; } @@ -349,15 +447,9 @@ views::View* ShellWindowViews::GetContentsView() { views::NonClientFrameView* ShellWindowViews::CreateNonClientFrameView( views::Widget* widget) { -#if defined(USE_ASH) - // TODO(jeremya): make this an option when we have HTTRANSPARENT handling in - // aura. - ash::CustomFrameViewAsh* frame = new ash::CustomFrameViewAsh(); - frame->Init(widget); - return frame; -#else - return new ShellWindowFrameView(this); -#endif + ShellWindowFrameView* frame_view = new ShellWindowFrameView(); + frame_view->Init(window_); + return frame_view; } string16 ShellWindowViews::GetWindowTitle() const { @@ -411,11 +503,11 @@ void ShellWindowViews::OnViewWasResized() { rgn->op(*caption_region_.Get(), SkRegion::kUnion_Op); if (!window_->IsMaximized()) { if (use_custom_frame_) - rgn->op(0, 0, width, kResizeBorderWidth, SkRegion::kUnion_Op); - rgn->op(0, 0, kResizeBorderWidth, height, SkRegion::kUnion_Op); - rgn->op(width - kResizeBorderWidth, 0, width, height, + rgn->op(0, 0, width, kResizeInsideBoundsSize, SkRegion::kUnion_Op); + rgn->op(0, 0, kResizeInsideBoundsSize, height, SkRegion::kUnion_Op); + rgn->op(width - kResizeInsideBoundsSize, 0, width, height, SkRegion::kUnion_Op); - rgn->op(0, height - kResizeBorderWidth, width, height, + rgn->op(0, height - kResizeInsideBoundsSize, width, height, SkRegion::kUnion_Op); } } @@ -425,20 +517,7 @@ void ShellWindowViews::OnViewWasResized() { void ShellWindowViews::Layout() { DCHECK(web_view_); - if (use_custom_frame_) { - web_view_->SetBounds(0, 0, width(), height()); - } else { - DCHECK(title_view_); - if (window_->IsFullscreen()) { - title_view_->SetVisible(false); - web_view_->SetBounds(0, 0, width(), height()); - } else { - title_view_->SetVisible(true); - title_view_->SetBounds(0, 0, width(), kCaptionHeight); - web_view_->SetBounds(0, kCaptionHeight, - width(), height() - kCaptionHeight); - } - } + web_view_->SetBounds(0, 0, width(), height()); OnViewWasResized(); } diff --git a/chrome/browser/ui/views/extensions/shell_window_views.h b/chrome/browser/ui/views/extensions/shell_window_views.h index 8edbd3416a215f..f6316590575e35 100644 --- a/chrome/browser/ui/views/extensions/shell_window_views.h +++ b/chrome/browser/ui/views/extensions/shell_window_views.h @@ -9,7 +9,6 @@ #include "chrome/browser/ui/extensions/shell_window.h" #include "ui/gfx/rect.h" #include "ui/gfx/scoped_sk_region.h" -#include "ui/views/controls/button/button.h" #include "ui/views/widget/widget_delegate.h" class Profile; @@ -23,8 +22,7 @@ class WebView; } class ShellWindowViews : public ShellWindow, - public views::WidgetDelegateView, - public views::ButtonListener { + public views::WidgetDelegateView { public: ShellWindowViews(Profile* profile, const extensions::Extension* extension, @@ -68,6 +66,8 @@ class ShellWindowViews : public ShellWindow, virtual void Layout() OVERRIDE; virtual void ViewHierarchyChanged( bool is_add, views::View *parent, views::View *child) OVERRIDE; + virtual gfx::Size GetMinimumSize() OVERRIDE; + virtual gfx::Size GetMaximumSize() OVERRIDE; // ShellWindow implementation. virtual void UpdateWindowTitle() OVERRIDE; @@ -81,11 +81,6 @@ class ShellWindowViews : public ShellWindow, void OnViewWasResized(); - // views::ButtonListener implementation. - virtual void ButtonPressed(views::Button* sender, const views::Event& event) - OVERRIDE; - - views::View* title_view_; views::WebView* web_view_; views::Widget* window_; bool is_fullscreen_;