Commit a4102566 authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

Mash: add support for window frame context menus on Ash-provided frames

Adds a single-item context menu for Hosted app windows (teleport
window). This is currently only supported in single process Mash
because multi user window manager doesn't work in multi process Mash.

Bug: 887051
Change-Id: Ifd1c7a08acf46615ff5fec2f0e7a614c9c99775c
Reviewed-on: https://chromium-review.googlesource.com/c/1287202Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Evan Stade <estade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601184}
parent 2baa8027
......@@ -32,13 +32,20 @@
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/view.h"
#include "ui/views/view_targeter.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
DEFINE_UI_CLASS_PROPERTY_TYPE(ash::NonClientFrameViewAsh*);
namespace ash {
DEFINE_UI_CLASS_PROPERTY_KEY(NonClientFrameViewAsh*,
kNonClientFrameViewAshKey,
nullptr);
///////////////////////////////////////////////////////////////////////////////
// NonClientFrameViewAshWindowStateDelegate
......@@ -250,6 +257,8 @@ NonClientFrameViewAsh::NonClientFrameViewAsh(views::Widget* frame)
}
Shell::Get()->AddShellObserver(this);
Shell::Get()->split_view_controller()->AddObserver(this);
frame_window->SetProperty(kNonClientFrameViewAshKey, this);
}
NonClientFrameViewAsh::~NonClientFrameViewAsh() {
......@@ -258,6 +267,11 @@ NonClientFrameViewAsh::~NonClientFrameViewAsh() {
Shell::Get()->split_view_controller()->RemoveObserver(this);
}
// static
NonClientFrameViewAsh* NonClientFrameViewAsh::Get(aura::Window* window) {
return window->GetProperty(kNonClientFrameViewAshKey);
}
void NonClientFrameViewAsh::InitImmersiveFullscreenControllerForView(
ImmersiveFullscreenController* immersive_fullscreen_controller) {
immersive_fullscreen_controller->Init(header_view_, frame_, header_view_);
......@@ -291,6 +305,23 @@ gfx::Rect NonClientFrameViewAsh::GetClientBoundsForWindowBounds(
return client_bounds;
}
void NonClientFrameViewAsh::SetWindowFrameMenuItems(
const menu_utils::MenuItemList& menu_item_list,
mojom::MenuDelegatePtr delegate) {
if (menu_item_list.empty()) {
menu_model_.reset();
menu_delegate_.reset();
} else {
menu_model_ = std::make_unique<ui::SimpleMenuModel>(this);
menu_utils::PopulateMenuFromMojoMenuItems(menu_model_.get(), nullptr,
menu_item_list, nullptr);
menu_delegate_ = std::move(delegate);
}
header_view_->set_context_menu_controller(menu_item_list.empty() ? nullptr
: this);
}
////////////////////////////////////////////////////////////////////////////////
// NonClientFrameViewAsh, views::NonClientFrameView overrides:
......@@ -456,6 +487,33 @@ void NonClientFrameViewAsh::OnSplitViewStateChanged(
UpdateHeaderView();
}
void NonClientFrameViewAsh::ShowContextMenuForView(
views::View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) {
DCHECK_EQ(header_view_, source);
DCHECK(menu_model_);
menu_runner_ = std::make_unique<views::MenuRunner>(
menu_model_.get(),
views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU);
menu_runner_->RunMenuAt(GetWidget(), nullptr,
gfx::Rect(point, gfx::Size(0, 0)),
views::MENU_ANCHOR_TOPLEFT, source_type);
}
bool NonClientFrameViewAsh::IsCommandIdChecked(int command_id) const {
return false;
}
bool NonClientFrameViewAsh::IsCommandIdEnabled(int command_id) const {
return true;
}
void NonClientFrameViewAsh::ExecuteCommand(int command_id, int event_flags) {
menu_delegate_->MenuItemActivated(command_id);
}
////////////////////////////////////////////////////////////////////////////////
// NonClientFrameViewAsh, private:
......
......@@ -9,11 +9,16 @@
#include "ash/ash_export.h"
#include "ash/frame/header_view.h"
#include "ash/public/cpp/menu_utils.h"
#include "ash/public/interfaces/menu.mojom.h"
#include "ash/shell_observer.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "base/macros.h"
#include "base/optional.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/window/non_client_view.h"
namespace views {
......@@ -36,7 +41,9 @@ class NonClientFrameViewAshImmersiveHelper;
// BrowserNonClientFrameViewAsh.
class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
public ShellObserver,
public SplitViewController::Observer {
public SplitViewController::Observer,
public views::ContextMenuController,
public ui::SimpleMenuModel::Delegate {
public:
// Internal class name.
static const char kViewClassName[];
......@@ -50,6 +57,8 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
explicit NonClientFrameViewAsh(views::Widget* frame);
~NonClientFrameViewAsh() override;
static NonClientFrameViewAsh* Get(aura::Window* window);
// Sets the caption button modeland updates the caption buttons.
void SetCaptionButtonModel(std::unique_ptr<CaptionButtonModel> model);
......@@ -75,6 +84,12 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
gfx::Rect GetClientBoundsForWindowBounds(
const gfx::Rect& window_bounds) const;
// Sets the menu items to show in the context menu. If |menu_item_list| is
// empty, no context menu will be shown. Menu item activation is dispatched to
// |delegate|.
void SetWindowFrameMenuItems(const menu_utils::MenuItemList& menu_item_list,
mojom::MenuDelegatePtr delegate);
// views::NonClientFrameView:
gfx::Rect GetBoundsForClientView() const override;
gfx::Rect GetWindowBoundsForClientBounds(
......@@ -109,6 +124,16 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
void OnSplitViewStateChanged(SplitViewController::State previous_state,
SplitViewController::State state) override;
// views::ContextMenuController:
void ShowContextMenuForView(View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) override;
// ui::SimpleMenuModel::Delegate:
bool IsCommandIdChecked(int command_id) const override;
bool IsCommandIdEnabled(int command_id) const override;
void ExecuteCommand(int command_id, int event_flags) override;
const views::View* GetAvatarIconViewForTest() const;
SkColor GetActiveFrameColorForTest() const;
......@@ -117,10 +142,10 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
views::Widget* frame() { return frame_; }
protected:
// Called when overview mode or split view state changed. If overview mode and
// split view mode are both active at the same time, the header of the window
// in split view should be visible, but the headers of other windows in
// overview are not.
// Called when overview mode or split view state changed. If overview mode
// and split view mode are both active at the same time, the header of the
// window in split view should be visible, but the headers of other windows
// in overview are not.
void UpdateHeaderView();
private:
......@@ -133,8 +158,8 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
bool DoesIntersectRect(const views::View* target,
const gfx::Rect& rect) const override;
// Returns the container for the minimize/maximize/close buttons that is held
// by the HeaderView. Used in testing.
// Returns the container for the minimize/maximize/close buttons that is
// held by the HeaderView. Used in testing.
FrameCaptionButtonContainerView* GetFrameCaptionButtonContainerViewForTest();
// Height from top of window to top of client area.
......@@ -153,10 +178,16 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView,
// Track whether the device is in overview mode. Set this to true when
// overview mode started and false when overview mode finished. Use this to
// check whether we should paint when splitview state changes instead of
// Shell::Get()->window_selector_controller()->IsSelecting() because the later
// actually may be still be false after overview mode has started.
// Shell::Get()->window_selector_controller()->IsSelecting() because the
// later actually may be still be false after overview mode has started.
bool in_overview_mode_ = false;
// Helpers for the context menu users will see when right-clicking on
// |header_view_|.
std::unique_ptr<ui::SimpleMenuModel> menu_model_;
std::unique_ptr<views::MenuRunner> menu_runner_;
mojom::MenuDelegatePtr menu_delegate_;
std::unique_ptr<NonClientFrameViewAshImmersiveHelper> immersive_helper_;
DISALLOW_COPY_AND_ASSIGN(NonClientFrameViewAsh);
......
......@@ -389,7 +389,7 @@ TEST_F(NonClientFrameViewAshTest, GetPreferredOnScreenHeightInTabletMaximzied) {
std::unique_ptr<views::Widget> widget = CreateTestWidget(delegate);
auto* frame_view = static_cast<ash::NonClientFrameViewAsh*>(
widget->non_client_view()->frame_view());
auto* header_view = static_cast<HeaderView*>(frame_view->GetHeaderView());
auto* header_view = frame_view->GetHeaderView();
ASSERT_TRUE(widget->IsMaximized());
EXPECT_TRUE(header_view->in_immersive_mode());
static_cast<ImmersiveFullscreenControllerDelegate*>(header_view)
......@@ -545,8 +545,7 @@ TEST_F(NonClientFrameViewAshTest, BackButton) {
delegate->non_client_frame_view();
non_client_frame_view->SetCaptionButtonModel(std::move(model));
HeaderView* header_view =
static_cast<HeaderView*>(non_client_frame_view->GetHeaderView());
HeaderView* header_view = non_client_frame_view->GetHeaderView();
EXPECT_FALSE(header_view->GetBackButton());
model_ptr->SetVisible(CAPTION_BUTTON_ICON_BACK, true);
non_client_frame_view->SizeConstraintsChanged();
......@@ -626,8 +625,7 @@ TEST_F(NonClientFrameViewAshTest, CustomButtonModel) {
delegate->non_client_frame_view();
non_client_frame_view->SetCaptionButtonModel(std::move(model));
HeaderView* header_view =
static_cast<HeaderView*>(non_client_frame_view->GetHeaderView());
HeaderView* header_view = non_client_frame_view->GetHeaderView();
FrameCaptionButtonContainerView::TestApi test_api(
header_view->caption_button_container());
......@@ -699,8 +697,7 @@ TEST_F(NonClientFrameViewAshTest, WideFrame) {
NonClientFrameViewAsh* non_client_frame_view =
delegate->non_client_frame_view();
HeaderView* header_view =
static_cast<HeaderView*>(non_client_frame_view->GetHeaderView());
HeaderView* header_view = non_client_frame_view->GetHeaderView();
widget->Maximize();
std::unique_ptr<WideFrameView> wide_frame_view =
......
......@@ -199,7 +199,7 @@ void WideFrameView::OnOverviewModeEnded() {
HeaderView* WideFrameView::GetTargetHeaderView() {
auto* frame_view = static_cast<NonClientFrameViewAsh*>(
target_->non_client_view()->frame_view());
return static_cast<HeaderView*>(frame_view->GetHeaderView());
return frame_view->GetHeaderView();
}
} // namespace ash
......@@ -4,6 +4,7 @@
module ash.mojom;
import "ash/public/interfaces/menu.mojom";
import "ui/events/mojo/event_constants.mojom";
// The previewed snap state for a window, corresponding to the use of a
......@@ -48,4 +49,10 @@ interface AshWindowManager {
// Plays the window bounce animation (scale the window up and down).
BounceWindow(uint64 window_id);
// Sets the context menu items to be displayed on a window's frame, for
// windows where the frame is provided by Ash. |delegate| handles interaction
// with the menu.
SetWindowFrameMenuItems(uint64 window_id, array<MenuItem> menu_items,
MenuDelegate delegate);
};
......@@ -66,3 +66,9 @@ struct MenuItem {
int64 radio_group_id; // The radio group id.
MenuSeparatorType separator_type; // The separator type.
};
// An interface implemented by clients to handle interaction with menus run in
// Ash.
interface MenuDelegate {
MenuItemActivated(int32 command_id);
};
......@@ -5,6 +5,7 @@
#include "ash/ws/ash_window_manager.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/shell.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_state.h"
......@@ -121,4 +122,24 @@ void AshWindowManager::BounceWindow(ws::Id window_id) {
::wm::AnimateWindow(window, ::wm::WINDOW_ANIMATION_TYPE_BOUNCE);
}
void AshWindowManager::SetWindowFrameMenuItems(
ws::Id window_id,
menu_utils::MenuItemList menu_item_list,
mojom::MenuDelegatePtr delegate) {
aura::Window* window = window_tree_->GetWindowByTransportId(window_id);
if (!window) {
DVLOG(1) << "SetWindowFrameMenuItems passed invalid window, id="
<< window_id;
return;
}
NonClientFrameViewAsh* frame_view = NonClientFrameViewAsh::Get(window);
if (!frame_view) {
DVLOG(1) << "SetWindowFrameMenuItems called on frameless window";
return;
}
frame_view->SetWindowFrameMenuItems(menu_item_list, std::move(delegate));
}
} // namespace ash
......@@ -6,6 +6,7 @@
#define ASH_WS_ASH_WINDOW_MANAGER_H_
#include "ash/frame/ash_frame_caption_controller.h"
#include "ash/public/cpp/menu_utils.h"
#include "ash/public/interfaces/ash_window_manager.mojom.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "services/ws/common/types.h"
......@@ -39,6 +40,9 @@ class AshWindowManager : public mojom::AshWindowManager,
void MaximizeWindowByCaptionClick(ws::Id window_id,
ui::mojom::PointerKind pointer) override;
void BounceWindow(ws::Id window_id) override;
void SetWindowFrameMenuItems(ws::Id window_id,
menu_utils::MenuItemList menu_item_list,
mojom::MenuDelegatePtr delegate) override;
private:
ws::WindowTree* window_tree_;
......
......@@ -12,10 +12,12 @@
#include "ash/public/cpp/ash_constants.h"
#include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/immersive/immersive_fullscreen_controller.h"
#include "ash/public/cpp/menu_utils.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/public/cpp/window_state_type.h"
#include "ash/public/interfaces/constants.mojom.h"
#include "ash/wm/window_state.h" // mash-ok
#include "base/logging.h"
#include "chrome/browser/chromeos/note_taking_helper.h"
......@@ -30,6 +32,8 @@
#include "services/ws/public/mojom/window_manager.mojom.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/mus/property_converter.h"
#include "ui/aura/mus/window_mus.h"
#include "ui/aura/mus/window_tree_client.h"
#include "ui/aura/mus/window_tree_host_mus.h"
#include "ui/aura/window.h"
#include "ui/base/hit_test.h"
......@@ -43,6 +47,7 @@
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/mus/mus_client.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
......@@ -53,11 +58,23 @@ ChromeNativeAppWindowViewsAuraAsh::ChromeNativeAppWindowViewsAuraAsh()
std::make_unique<ExclusiveAccessManager>(this)) {
if (TabletModeClient::Get())
TabletModeClient::Get()->AddObserver(this);
if (features::IsSingleProcessMash()) {
MultiUserWindowManager::GetInstance()->AddObserver(this);
ash_window_manager_ =
views::MusClient::Get()
->window_tree_client()
->BindWindowManagerInterface<ash::mojom::AshWindowManager>();
}
}
ChromeNativeAppWindowViewsAuraAsh::~ChromeNativeAppWindowViewsAuraAsh() {
if (TabletModeClient::Get())
TabletModeClient::Get()->RemoveObserver(this);
if (features::IsSingleProcessMash())
MultiUserWindowManager::GetInstance()->RemoveObserver(this);
}
///////////////////////////////////////////////////////////////////////////////
......@@ -190,6 +207,8 @@ void ChromeNativeAppWindowViewsAuraAsh::ShowContextMenuForView(
views::View* source,
const gfx::Point& p,
ui::MenuSourceType source_type) {
DCHECK(!features::IsUsingWindowService());
menu_model_ = CreateMultiUserContextMenu(GetNativeWindow());
if (!menu_model_.get())
return;
......@@ -503,6 +522,33 @@ void ChromeNativeAppWindowViewsAuraAsh::OnWindowDestroying(
observed_window_.Remove(window);
}
void ChromeNativeAppWindowViewsAuraAsh::OnOwnerEntryAdded(
aura::Window* window) {
OnOwnerEntryChanged(window);
}
void ChromeNativeAppWindowViewsAuraAsh::OnOwnerEntryChanged(
aura::Window* window) {
if (window != GetWidget()->GetNativeWindow())
return;
std::unique_ptr<ui::MenuModel> menu_model =
CreateMultiUserContextMenu(GetNativeWindow());
ash::mojom::MenuDelegatePtr delegate;
binding_.Close();
binding_.Bind(mojo::MakeRequest(&delegate));
ash_window_manager_->SetWindowFrameMenuItems(
aura::WindowMus::Get(GetWidget()->GetNativeWindow()->GetRootWindow())
->server_id(),
ash::menu_utils::GetMojoMenuItemsFromModel(menu_model.get()),
std::move(delegate));
}
void ChromeNativeAppWindowViewsAuraAsh::MenuItemActivated(int command_id) {
ExecuteVisitDesktopCommand(command_id, GetWidget()->GetNativeWindow());
}
void ChromeNativeAppWindowViewsAuraAsh::OnMenuClosed() {
menu_runner_.reset();
menu_model_.reset();
......
......@@ -8,13 +8,17 @@
#include <memory>
#include <vector>
#include "ash/public/interfaces/ash_window_manager.mojom.h"
#include "ash/public/interfaces/menu.mojom.h"
#include "ash/wm/window_state_observer.h" // mash-ok
#include "base/gtest_prod_util.h"
#include "base/scoped_observer.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
#include "chrome/browser/ui/ash/tablet_mode_client_observer.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.h"
#include "chrome/browser/ui/views/exclusive_access_bubble_views_context.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "ui/aura/window_observer.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/views/context_menu_controller.h"
......@@ -40,7 +44,9 @@ class ChromeNativeAppWindowViewsAuraAsh
public ExclusiveAccessContext,
public ExclusiveAccessBubbleViewsContext,
public ash::wm::WindowStateObserver,
public aura::WindowObserver {
public aura::WindowObserver,
public MultiUserWindowManager::Observer,
public ash::mojom::MenuDelegate {
public:
ChromeNativeAppWindowViewsAuraAsh();
~ChromeNativeAppWindowViewsAuraAsh() override;
......@@ -129,6 +135,13 @@ class ChromeNativeAppWindowViewsAuraAsh
intptr_t old) override;
void OnWindowDestroying(aura::Window* window) override;
// MultiUserWindowManager::Observer:
void OnOwnerEntryAdded(aura::Window* window) override;
void OnOwnerEntryChanged(aura::Window* window) override;
// ash::mojom::MenuDelegate:
void MenuItemActivated(int command_id) override;
private:
FRIEND_TEST_ALL_PREFIXES(ChromeNativeAppWindowViewsAuraAshBrowserTest,
ImmersiveWorkFlow);
......@@ -154,6 +167,9 @@ class ChromeNativeAppWindowViewsAuraAsh
// Callback for MenuRunner
void OnMenuClosed();
// Callback for Ash-controlled context menus, invoked over Mojo.
void ExecuteContextMenuItem(int command_id);
// Whether immersive mode should be enabled.
bool ShouldEnableImmersiveMode() const;
......@@ -161,7 +177,7 @@ class ChromeNativeAppWindowViewsAuraAsh
// app's and window manager's state.
void UpdateImmersiveMode();
// Used to show the system menu.
// Used to show the system menu. Only used in !Mash.
std::unique_ptr<ui::MenuModel> menu_model_;
std::unique_ptr<views::MenuRunner> menu_runner_;
......@@ -171,6 +187,10 @@ class ChromeNativeAppWindowViewsAuraAsh
bool tablet_mode_enabled_ = false;
// Only used in mash.
ash::mojom::AshWindowManagerAssociatedPtr ash_window_manager_;
mojo::Binding<ash::mojom::MenuDelegate> binding_{this};
ScopedObserver<aura::Window, aura::WindowObserver> observed_window_{this};
ScopedObserver<ash::wm::WindowState, ash::wm::WindowStateObserver>
observed_window_state_{this};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment