blob: 605399fd211996e4a4895b85958712a13be6169e [file] [log] [blame]
// Copyright 2017 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 "ash/wm/splitview/split_view_overview_overlay.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/root_window_finder.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/display_observer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// Width in DIP of each highlight view.
constexpr int kHighlightScreenWidth = 200;
// An alpha value for the highlight views.
constexpr SkColor kHighlightBackgroundAlpha = 0x80;
// Color of the labels' background/text.
constexpr SkColor kLabelBackgroundColor = SK_ColorBLACK;
constexpr SkColor kLabelEnabledColor = SK_ColorWHITE;
// The size of the warning icon in the when a window incompatible with
// splitscreen is dragged.
constexpr int kWarningIconSizeDp = 24;
// The amount of inset to be applied on a rotated image label view.
constexpr int kRotatedViewInsetDp = 8;
// The amount of round applied to the corners of a rotated image label view.
constexpr int kRotatedViewVerticalRoundRectRadius = 20;
// The color for the rotated image label views.
constexpr SkColor kRotatedViewBackgroundColor =
SkColorSetA(SK_ColorBLACK, 0xB0);
// Creates the widget responsible for displaying the indicators.
std::unique_ptr<views::Widget> CreateWidget() {
std::unique_ptr<views::Widget> widget(new views::Widget);
views::Widget::InitParams params;
params.type = views::Widget::InitParams::TYPE_POPUP;
params.keep_on_top = false;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
params.accept_events = false;
params.parent = Shell::GetContainer(Shell::Get()->GetPrimaryRootWindow(),
kShellWindowId_OverlayContainer);
widget->set_focus_on_creation(false);
widget->Init(params);
return widget;
}
// Computes the transform which rotates the labels |angle| degrees. The point
// of rotation is the relative center point of |bounds|.
gfx::Transform ComputeRotateAroundCenterTransform(const gfx::Rect& bounds,
double angle) {
gfx::Transform transform;
const gfx::Vector2dF center_point_vector =
bounds.CenterPoint() - bounds.origin();
transform.Translate(center_point_vector);
transform.Rotate(angle);
transform.Translate(-center_point_vector);
return transform;
}
} // namespace
// View which contains a label and an optional icon. Used by and rotated by
// SplitViewOverviewOverlayView.
class SplitViewOverviewOverlay::RotatedImageLabelView : public views::View {
public:
RotatedImageLabelView() {
icon_ = new views::ImageView();
icon_->SetPaintToLayer();
icon_->layer()->SetFillsBoundsOpaquely(false);
icon_->SetPreferredSize(gfx::Size(kWarningIconSizeDp, kWarningIconSizeDp));
icon_->SetImage(
gfx::CreateVectorIcon(kSplitviewNosnapWarningIcon, SK_ColorWHITE));
icon_->SetVisible(false);
label_ = new views::Label(base::string16(), views::style::CONTEXT_LABEL);
label_->SetPaintToLayer();
label_->layer()->SetFillsBoundsOpaquely(false);
label_->SetEnabledColor(kLabelEnabledColor);
label_->SetBackgroundColor(kLabelBackgroundColor);
auto* layout = new views::BoxLayout(views::BoxLayout::kVertical);
SetLayoutManager(layout);
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
AddChildView(icon_);
AddChildView(label_);
layout->SetFlexForView(label_, 1);
SetBorder(views::CreateEmptyBorder(
gfx::Insets(kRotatedViewInsetDp, kRotatedViewInsetDp)));
}
~RotatedImageLabelView() override = default;
// views::View:
void OnPaint(gfx::Canvas* canvas) override {
views::View::OnPaint(canvas);
// Clip into a rounded rectangle.
constexpr SkScalar radius =
SkIntToScalar(kRotatedViewVerticalRoundRectRadius);
constexpr SkScalar kRadius[8] = {radius, radius, radius, radius,
radius, radius, radius, radius};
SkPath path;
path.addRoundRect(gfx::RectToSkRect(gfx::Rect(size())), kRadius);
canvas->ClipPath(path, true);
canvas->DrawColor(kRotatedViewBackgroundColor);
}
void SetLabelText(const base::string16& text) { label_->SetText(text); }
bool icon_visible() const { return icon_->visible(); }
void SetIconVisible(bool visible) { icon_->SetVisible(visible); }
// Called when the view's bounds are altered. Rotates the view by |angle|
// degrees.
void OnBoundsUpdated(const gfx::Rect& bounds, double angle) {
SetBoundsRect(bounds);
SetTransform(ComputeRotateAroundCenterTransform(bounds, angle));
}
private:
views::ImageView* icon_ = nullptr;
views::Label* label_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(RotatedImageLabelView);
};
// View which contains two highlights on each side indicator where a user should
// drag a selected window in order to initiate splitview. Each highlight has a
// label with instructions to further guide users. The highlights are on the
// left and right of the display in landscape mode, and on the top and bottom of
// the display in landscape mode.
class SplitViewOverviewOverlay::SplitViewOverviewOverlayView
: public views::View {
public:
SplitViewOverviewOverlayView() {
left_hightlight_view_ = new views::View();
left_hightlight_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
left_hightlight_view_->layer()->SetColor(
SkColorSetA(SK_ColorWHITE, kHighlightBackgroundAlpha));
right_hightlight_view_ = new views::View();
right_hightlight_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
right_hightlight_view_->layer()->SetColor(
SkColorSetA(SK_ColorWHITE, kHighlightBackgroundAlpha));
AddChildView(left_hightlight_view_);
AddChildView(right_hightlight_view_);
left_rotated_view_ = new RotatedImageLabelView();
right_rotated_view_ = new RotatedImageLabelView();
left_hightlight_view_->AddChildView(left_rotated_view_);
right_hightlight_view_->AddChildView(right_rotated_view_);
}
~SplitViewOverviewOverlayView() override = default;
void OnIndicatorTypeChanged(IndicatorType indicator_type) {
if (indicator_type_ == indicator_type)
return;
indicator_type_ = indicator_type;
switch (indicator_type) {
case IndicatorType::NONE:
SetHighlightsVisible(false);
return;
case IndicatorType::DRAG_AREA:
case IndicatorType::CANNOT_SNAP:
SetHighlightsVisible(true);
SetIconsVisible(indicator_type == IndicatorType::CANNOT_SNAP);
SetLabelsText(l10n_util::GetStringUTF16(
indicator_type == IndicatorType::CANNOT_SNAP
? IDS_ASH_SPLIT_VIEW_CANNOT_SNAP
: IDS_ASH_SPLIT_VIEW_GUIDANCE));
const SkColor color = indicator_type == IndicatorType::CANNOT_SNAP
? SK_ColorBLACK
: SK_ColorWHITE;
left_hightlight_view_->layer()->SetColor(
SkColorSetA(color, kHighlightBackgroundAlpha));
right_hightlight_view_->layer()->SetColor(
SkColorSetA(color, kHighlightBackgroundAlpha));
return;
}
NOTREACHED();
}
// views::View:
void Layout() override {
const bool landscape = Shell::Get()
->split_view_controller()
->IsCurrentScreenOrientationLandscape();
// Calculate the bounds of the two highlight regions.
const int highlight_width = landscape ? kHighlightScreenWidth : width();
const int highlight_height = landscape ? height() : kHighlightScreenWidth;
const gfx::Point right_bottom_origin(
landscape ? width() - kHighlightScreenWidth : 0,
landscape ? 0 : height() - kHighlightScreenWidth);
left_hightlight_view_->SetBounds(0, 0, highlight_width, highlight_height);
right_hightlight_view_->SetBounds(right_bottom_origin.x(),
right_bottom_origin.y(), highlight_width,
highlight_height);
// Calculate the bounds of the views which contain the guidance text and
// icon. Rotate the two views in landscape mode.
const gfx::Size size = left_rotated_view_->GetPreferredSize();
const gfx::Rect rotated_bounds(highlight_width / 2 - size.width() / 2,
highlight_height / 2 - size.height() / 2,
size.width(), size.height());
// In portrait mode, there is no need to rotate the text and warning icon.
// In landscape mode, rotate the left text 90 degrees clockwise in rtl and
// 90 degress anti clockwise in ltr. The right text is rotated 90 degrees in
// the opposite direction of the left text.
double left_rotation_angle = 0.0;
if (landscape)
left_rotation_angle = 90.0 * (base::i18n::IsRTL() ? 1 : -1);
left_rotated_view_->OnBoundsUpdated(rotated_bounds,
left_rotation_angle /* angle */);
right_rotated_view_->OnBoundsUpdated(rotated_bounds,
-left_rotation_angle /* angle */);
}
private:
void SetHighlightsVisible(bool visible) {
left_hightlight_view_->SetVisible(visible);
right_hightlight_view_->SetVisible(visible);
}
void SetLabelsText(const base::string16& text) {
left_rotated_view_->SetLabelText(text);
right_rotated_view_->SetLabelText(text);
Layout();
}
void SetIconsVisible(bool visible) {
left_rotated_view_->SetIconVisible(visible);
right_rotated_view_->SetIconVisible(visible);
Layout();
}
views::View* left_hightlight_view_ = nullptr;
views::View* right_hightlight_view_ = nullptr;
RotatedImageLabelView* left_rotated_view_ = nullptr;
RotatedImageLabelView* right_rotated_view_ = nullptr;
IndicatorType indicator_type_ = IndicatorType::NONE;
DISALLOW_COPY_AND_ASSIGN(SplitViewOverviewOverlayView);
};
SplitViewOverviewOverlay::SplitViewOverviewOverlay() {
overlay_view_ = new SplitViewOverviewOverlayView();
widget_ = CreateWidget();
widget_->SetContentsView(overlay_view_);
}
SplitViewOverviewOverlay::~SplitViewOverviewOverlay() = default;
void SplitViewOverviewOverlay::SetIndicatorType(
IndicatorType indicator_type,
const gfx::Point& event_location) {
if (indicator_type == current_indicator_type_)
return;
current_indicator_type_ = indicator_type;
// Only show the overlay if nothing is snapped.
if (Shell::Get()->split_view_controller()->state() !=
SplitViewController::NO_SNAP) {
current_indicator_type_ = IndicatorType::NONE;
}
const bool visible = current_indicator_type_ != IndicatorType::NONE;
if (!visible) {
widget_->Hide();
return;
}
// Reparent the widget if needed.
aura::Window* target = ash::wm::GetRootWindowAt(event_location);
aura::Window* root_window = target->GetRootWindow();
if (widget_->GetNativeView()->GetRootWindow() != root_window) {
views::Widget::ReparentNativeView(
widget_->GetNativeView(),
Shell::GetContainer(root_window, kShellWindowId_OverlayContainer));
widget_->SetContentsView(overlay_view_);
}
gfx::Rect bounds = ScreenUtil::GetDisplayWorkAreaBoundsInParent(
root_window->GetChildById(kShellWindowId_OverlayContainer));
::wm::ConvertRectToScreen(root_window, &bounds);
widget_->SetBounds(bounds);
widget_->Show();
overlay_view_->OnIndicatorTypeChanged(current_indicator_type_);
}
void SplitViewOverviewOverlay::OnDisplayBoundsChanged() {
overlay_view_->Layout();
}
} // namespace ash