diff options
author | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-15 04:54:30 +0000 |
---|---|---|
committer | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-15 04:54:30 +0000 |
commit | f3d7d9b6dc04aa5adb57dd7699b9d395e1dbf640 (patch) | |
tree | 600633c97fd77b4b280e28491c99fd1a1697b64d /apps | |
parent | a29040990b3d14ad32979a195e9f579c4ef89251 (diff) | |
download | chromium_src-f3d7d9b6dc04aa5adb57dd7699b9d395e1dbf640.zip chromium_src-f3d7d9b6dc04aa5adb57dd7699b9d395e1dbf640.tar.gz chromium_src-f3d7d9b6dc04aa5adb57dd7699b9d395e1dbf640.tar.bz2 |
apps: Move chrome's ShellWindowFrameView into apps/
BUG=306535
TEST=compiles, can still resize a v2 app (like "Text")
TBR=sky@chromium.org for moving shell_window_frame_view.h/cc out of its old location in Chrome
Review URL: https://codereview.chromium.org/27029006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@228619 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'apps')
-rw-r--r-- | apps/apps.gypi | 12 | ||||
-rw-r--r-- | apps/native_app_window.h | 7 | ||||
-rw-r--r-- | apps/ui/views/DEPS | 6 | ||||
-rw-r--r-- | apps/ui/views/shell_window_frame_view.cc | 361 | ||||
-rw-r--r-- | apps/ui/views/shell_window_frame_view.h | 94 |
5 files changed, 480 insertions, 0 deletions
diff --git a/apps/apps.gypi b/apps/apps.gypi index e44dc21..62f6d896 100644 --- a/apps/apps.gypi +++ b/apps/apps.gypi @@ -70,6 +70,8 @@ 'shell_window_registry.h', 'switches.cc', 'switches.h', + 'ui/views/shell_window_frame_view.cc', + 'ui/views/shell_window_frame_view.h', ], 'conditions': [ ['chromeos==1', @@ -86,6 +88,16 @@ ], } ], + ['toolkit_views==1', { + 'dependencies': [ + '../ui/base/strings/ui_strings.gyp:ui_strings', + '../ui/views/views.gyp:views', + ], + }, { # toolkit_views==0 + 'sources/': [ + ['exclude', 'ui/views/'], + ], + }], ], # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. 'msvs_disabled_warnings': [ 4267, ], diff --git a/apps/native_app_window.h b/apps/native_app_window.h index e5403ed..8344914 100644 --- a/apps/native_app_window.h +++ b/apps/native_app_window.h @@ -22,6 +22,9 @@ class NativeAppWindow : public ui::BaseWindow, virtual void UpdateDraggableRegions( const std::vector<extensions::DraggableRegion>& regions) = 0; + // Returns the region used by frameless windows for dragging. May return NULL. + virtual SkRegion* GetDraggableRegion() = 0; + // Called when the region that accepts input events is changed. // If |region| is NULL, then the entire window will accept input events. virtual void UpdateInputRegion(scoped_ptr<SkRegion> region) = 0; @@ -43,6 +46,10 @@ class NativeAppWindow : public ui::BaseWindow, virtual void HandleKeyboardEvent( const content::NativeWebKeyboardEvent& event) = 0; + // Returns true if the window has no frame, as for a window opened by + // chrome.app.window.create with the option 'frame' set to 'none'. + virtual bool IsFrameless() const = 0; + // Returns the difference between the window bounds (including titlebar and // borders) and the content bounds, if any. virtual gfx::Insets GetFrameInsets() const = 0; diff --git a/apps/ui/views/DEPS b/apps/ui/views/DEPS new file mode 100644 index 0000000..e0bd7db --- /dev/null +++ b/apps/ui/views/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + # TODO(jamescook): Eliminate this by introducing apps_resources.grd, see + # crbug.com/306688 + "+grit/theme_resources.h", + "+grit/ui_strings.h", +] diff --git a/apps/ui/views/shell_window_frame_view.cc b/apps/ui/views/shell_window_frame_view.cc new file mode 100644 index 0000000..971cb48 --- /dev/null +++ b/apps/ui/views/shell_window_frame_view.cc @@ -0,0 +1,361 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "apps/ui/views/shell_window_frame_view.h" + +#include "apps/native_app_window.h" +#include "base/strings/utf_string_conversions.h" +#include "extensions/common/draggable_region.h" +#include "grit/theme_resources.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/views/controls/button/image_button.h" +#include "ui/views/layout/grid_layout.h" +#include "ui/views/views_delegate.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +#if defined(USE_AURA) +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#endif + +namespace { +// Height of the chrome-style caption, in pixels. +const int kCaptionHeight = 25; +} // namespace + +namespace apps { + +const char ShellWindowFrameView::kViewClassName[] = + "browser/ui/views/extensions/ShellWindowFrameView"; + +ShellWindowFrameView::ShellWindowFrameView(NativeAppWindow* window) + : window_(window), + frame_(NULL), + close_button_(NULL), + maximize_button_(NULL), + restore_button_(NULL), + minimize_button_(NULL), + resize_inside_bounds_size_(0), + resize_area_corner_size_(0) { +} + +ShellWindowFrameView::~ShellWindowFrameView() { +} + +void ShellWindowFrameView::Init(views::Widget* frame, + int resize_inside_bounds_size, + int resize_outside_bounds_size, + int resize_outside_scale_for_touch, + int resize_area_corner_size) { + frame_ = frame; + resize_inside_bounds_size_ = resize_inside_bounds_size; + resize_area_corner_size_ = resize_area_corner_size; + + if (!window_->IsFrameless()) { + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + close_button_ = new views::ImageButton(this); + close_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia()); + close_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia()); + close_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia()); + close_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); + AddChildView(close_button_); + maximize_button_ = new views::ImageButton(this); + maximize_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia()); + maximize_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia()); + maximize_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia()); + maximize_button_->SetImage(views::CustomButton::STATE_DISABLED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia()); + maximize_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE)); + AddChildView(maximize_button_); + restore_button_ = new views::ImageButton(this); + restore_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia()); + restore_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia()); + restore_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia()); + restore_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE)); + AddChildView(restore_button_); + minimize_button_ = new views::ImageButton(this); + minimize_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia()); + minimize_button_->SetImage(views::CustomButton::STATE_HOVERED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia()); + minimize_button_->SetImage(views::CustomButton::STATE_PRESSED, + rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia()); + minimize_button_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); + AddChildView(minimize_button_); + } + +#if defined(USE_AURA) + aura::Window* window = frame->GetNativeWindow(); + // Some Aura implementations (Ash) allow resize handles outside the window. + if (resize_outside_bounds_size > 0) { + gfx::Insets mouse_insets = gfx::Insets(-resize_outside_bounds_size, + -resize_outside_bounds_size, + -resize_outside_bounds_size, + -resize_outside_bounds_size); + gfx::Insets touch_insets = + mouse_insets.Scale(resize_outside_scale_for_touch); + // Ensure we get resize cursors for a few pixels outside our bounds. + window->SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets); + } + // 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(resize_inside_bounds_size_, resize_inside_bounds_size_, + resize_inside_bounds_size_, resize_inside_bounds_size_)); +#endif +} + +// views::NonClientFrameView implementation. + +gfx::Rect ShellWindowFrameView::GetBoundsForClientView() const { + if (window_->IsFrameless() || 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 { + if (window_->IsFrameless()) { + gfx::Rect window_bounds = client_bounds; + // Enforce minimum size (1, 1) in case that client_bounds is passed with + // empty size. This could occur when the frameless window is being + // initialized. + if (window_bounds.IsEmpty()) { + window_bounds.set_width(1); + window_bounds.set_height(1); + } + return window_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); +} + +int ShellWindowFrameView::NonClientHitTest(const gfx::Point& point) { + if (frame_->IsFullscreen()) + return HTCLIENT; + + gfx::Rect expanded_bounds = bounds(); +#if defined(USE_AURA) + // Some Aura implementations (Ash) optionally allow resize handles just + // outside the window bounds. + aura::Window* window = frame_->GetNativeWindow(); + if (aura::Env::GetInstance()->is_touch_down()) + expanded_bounds.Inset(window->hit_test_bounds_override_outer_touch()); + else + expanded_bounds.Inset(window->hit_test_bounds_override_outer_mouse()); +#endif + // Points outside the (possibly expanded) bounds can be discarded. + if (!expanded_bounds.Contains(point)) + return HTNOWHERE; + + // 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 : + resize_inside_bounds_size_; + int frame_component = GetHTComponentForFrame(point, + resize_border, + resize_border, + resize_area_corner_size_, + resize_area_corner_size_, + can_ever_resize); + if (frame_component != HTNOWHERE) + return frame_component; + + // Check for possible draggable region in the client area for the frameless + // window. + if (window_->IsFrameless()) { + SkRegion* draggable_region = window_->GetDraggableRegion(); + if (draggable_region && draggable_region->contains(point.x(), point.y())) + return HTCAPTION; + } + + 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_ && 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) { + // We got nothing to say about no window mask. +} + +// views::View implementation. + +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() { + if (window_->IsFrameless()) + return; + gfx::Size close_size = close_button_->GetPreferredSize(); + const int kButtonOffsetY = 0; + const int kButtonSpacing = 1; + const int kRightMargin = 3; + + close_button_->SetBounds( + width() - kRightMargin - close_size.width(), + kButtonOffsetY, + close_size.width(), + close_size.height()); + + bool can_ever_resize = frame_->widget_delegate() ? + frame_->widget_delegate()->CanResize() : + false; + maximize_button_->SetEnabled(can_ever_resize); + gfx::Size maximize_size = maximize_button_->GetPreferredSize(); + maximize_button_->SetBounds( + close_button_->x() - kButtonSpacing - maximize_size.width(), + kButtonOffsetY, + maximize_size.width(), + maximize_size.height()); + gfx::Size restore_size = restore_button_->GetPreferredSize(); + restore_button_->SetBounds( + close_button_->x() - kButtonSpacing - restore_size.width(), + kButtonOffsetY, + restore_size.width(), + restore_size.height()); + + bool maximized = frame_->IsMaximized(); + maximize_button_->SetVisible(!maximized); + restore_button_->SetVisible(maximized); + if (maximized) + maximize_button_->SetState(views::CustomButton::STATE_NORMAL); + else + restore_button_->SetState(views::CustomButton::STATE_NORMAL); + + gfx::Size minimize_size = minimize_button_->GetPreferredSize(); + minimize_button_->SetBounds( + maximize_button_->x() - kButtonSpacing - minimize_size.width(), + kButtonOffsetY, + minimize_size.width(), + minimize_size.height()); +} + +void ShellWindowFrameView::OnPaint(gfx::Canvas* canvas) { + if (window_->IsFrameless()) + return; + + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + if (ShouldPaintAsActive()) { + close_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia()); + } else { + close_button_->SetImage(views::CustomButton::STATE_NORMAL, + rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia()); + } + + // 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 = frame_->IsMaximized() ? 0 : 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); +} + +const char* ShellWindowFrameView::GetClassName() const { + return kViewClassName; +} + +gfx::Size ShellWindowFrameView::GetMinimumSize() { + gfx::Size min_size = frame_->client_view()->GetMinimumSize(); + if (window_->IsFrameless()) + return min_size; + + // 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; +} + +gfx::Size ShellWindowFrameView::GetMaximumSize() { + gfx::Size max_size = frame_->client_view()->GetMaximumSize(); + + // Add to the client maximum size the height of any title bar and borders. + gfx::Size client_size = GetBoundsForClientView().size(); + if (max_size.width()) + max_size.Enlarge(width() - client_size.width(), 0); + if (max_size.height()) + max_size.Enlarge(0, height() - client_size.height()); + + return max_size; +} + +// views::ButtonListener implementation. + +void ShellWindowFrameView::ButtonPressed(views::Button* sender, + const ui::Event& event) { + DCHECK(!window_->IsFrameless()); + if (sender == close_button_) + frame_->Close(); + else if (sender == maximize_button_) + frame_->Maximize(); + else if (sender == restore_button_) + frame_->Restore(); + else if (sender == minimize_button_) + frame_->Minimize(); +} + +} // namespace apps diff --git a/apps/ui/views/shell_window_frame_view.h b/apps/ui/views/shell_window_frame_view.h new file mode 100644 index 0000000..7fa10f5 --- /dev/null +++ b/apps/ui/views/shell_window_frame_view.h @@ -0,0 +1,94 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef APPS_UI_VIEWS_SHELL_WINDOW_FRAME_VIEW_H_ +#define APPS_UI_VIEWS_SHELL_WINDOW_FRAME_VIEW_H_ + +#include <string> + +#include "ui/gfx/path.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/window/non_client_view.h" + +namespace gfx { +class Canvas; +class Point; +} + +namespace ui { +class Event; +} + +namespace views { +class ImageButton; +class Widget; +} + +namespace apps { + +class NativeAppWindow; + +// A frameless or non-Ash, non-panel NonClientFrameView for app windows. +class ShellWindowFrameView : public views::NonClientFrameView, + public views::ButtonListener { + public: + static const char kViewClassName[]; + + explicit ShellWindowFrameView(NativeAppWindow* window); + virtual ~ShellWindowFrameView(); + + // Initializes this for the window |frame|. Sets the number of pixels for + // which a click is interpreted as a resize for the inner and outer border of + // the window and the lower-right corner resize handle. + void Init(views::Widget* frame, + int resize_inside_bounds_size, + int resize_outside_bounds_size, + int resize_outside_scale_for_touch, + int resize_area_corner_size); + + private: + // views::NonClientFrameView implementation. + virtual gfx::Rect GetBoundsForClientView() const OVERRIDE; + virtual gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const OVERRIDE; + virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE; + virtual void GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) OVERRIDE; + virtual void ResetWindowControls() OVERRIDE {} + virtual void UpdateWindowIcon() OVERRIDE {} + virtual void UpdateWindowTitle() OVERRIDE {} + + // views::View implementation. + virtual gfx::Size GetPreferredSize() OVERRIDE; + virtual void Layout() OVERRIDE; + virtual const char* GetClassName() const OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + virtual gfx::Size GetMinimumSize() OVERRIDE; + virtual gfx::Size GetMaximumSize() OVERRIDE; + + // views::ButtonListener implementation. + virtual void ButtonPressed(views::Button* sender, const ui::Event& event) + OVERRIDE; + + NativeAppWindow* window_; + views::Widget* frame_; + views::ImageButton* close_button_; + views::ImageButton* maximize_button_; + views::ImageButton* restore_button_; + views::ImageButton* minimize_button_; + + // Allow resize for clicks this many pixels inside the bounds. + int resize_inside_bounds_size_; + + // Size in pixels of the lower-right corner resize handle. + int resize_area_corner_size_; + + DISALLOW_COPY_AND_ASSIGN(ShellWindowFrameView); +}; + +} // namespace apps + +#endif // APPS_UI_VIEWS_SHELL_WINDOW_FRAME_VIEW_H_ |