// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_WM_MODE_WM_MODE_CONTROLLER_H_
#define ASH_WM_MODE_WM_MODE_CONTROLLER_H_
#include <memory>
#include <optional>
#include <string_view>
#include "ash/ash_export.h"
#include "ash/shell_observer.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm_mode/pie_menu_view.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/window_observer.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/compositor/layer_owner.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/geometry/point.h"
#include "ui/views/widget/unique_widget_ptr.h"
namespace aura {
class Window;
} // namespace aura
namespace ui {
class LocatedEvent;
} // namespace ui
namespace ash {
class WindowDimmer;
// Controls an *experimental* feature that allows users to easily layout, resize
// and position their windows using only mouse and touch gestures without having
// to be very precise at dragging, or targeting certain buttons. A demo of an
// exploration prototype can be watched at https://crbug.com/1348416.
// Please note this feature may never be released.
class ASH_EXPORT WmModeController : public ShellObserver,
public ui::EventHandler,
public ui::LayerOwner,
public ui::LayerDelegate,
public aura::WindowObserver,
public PieMenuView::Delegate,
public DesksController::Observer {
public:
enum PieMenuButtonIds {
kSnapButtonId = 0,
kMoveToDeskButtonId = 1,
kResizeButtonId = 2,
kDeskButtonIdStart = 3,
// Keep this range reserved for desk button IDs.
kDeskButtonIdEnd = kDeskButtonIdStart + desks_util::kDesksUpperLimit - 1,
};
WmModeController();
WmModeController(const WmModeController&) = delete;
WmModeController& operator=(const WmModeController&) = delete;
~WmModeController() override;
static WmModeController* Get();
bool is_active() const { return is_active_; }
aura::Window* selected_window() { return selected_window_; }
views::Widget* pie_menu_widget() { return pie_menu_widget_.get(); }
// Toggles the active state of this mode.
void Toggle();
// ShellObserver:
void OnRootWindowAdded(aura::Window* root_window) override;
void OnRootWindowWillShutdown(aura::Window* root_window) override;
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override;
void OnTouchEvent(ui::TouchEvent* event) override;
std::string_view GetLogContext() const override;
// ui::LayerDelegate:
void OnPaintLayer(const ui::PaintContext& context) override;
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override;
// PieMenuView::Delegate:
void OnPieMenuButtonPressed(int button_id) override;
// DesksController::Observer:
void OnDeskAdded(const Desk* desk, bool from_undo) override;
void OnDeskRemoved(const Desk* desk) override;
void OnDeskReordered(int old_index, int new_index) override;
void OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) override;
void OnDeskNameChanged(const Desk* desk,
const std::u16string& new_name) override;
private:
friend class WmModeTests;
void UpdateDimmers();
// Updates the state of all the WM Mode tray buttons on all displays.
void UpdateTrayButtons();
// Handles both mouse and touch events.
void OnLocatedEvent(ui::LocatedEvent* event);
// Creates the layer owned by `this`, but doesn't attach it to the layer
// hierarchy. This can be done by calling `MaybeChangeRoot()`. This function
// can only be called when WM Mode is active.
void CreateLayer();
// Adds the layer owned by `this` to the layer hierarchy of the given
// `new_root` if it's different than `current_root_`. This function can only
// be called when WM Mode is active `layer()` is valid.
void MaybeChangeRoot(aura::Window* new_root);
// Sets the given `window` as the currently selected window. If `window` is
// nullptr, the selected window is cleared.
void SetSelectedWindow(aura::Window* window);
// Schedules repainting the contents of the layer owned by `this`.
void ScheduleRepaint();
// Builds the pie menu widget.
void BuildPieMenu();
// If the pie menu is available, it rebuilds the move-to-desk sub menu items.
void MaybeRebuildMoveToDeskSubMenu();
// Returns true if the given `event_target` is contained within the window
// tree of the pie menu if it exists.
bool IsTargetingPieMenu(aura::Window* event_target) const;
// Gets the top-most window that can be selected for WM Mode operations at the
// given `screen_location`.
aura::Window* GetTopMostWindowAtPoint(
const gfx::Point& screen_location) const;
// Refreshes the visibility and the bounds of the pie menu (if it exists).
void MaybeRefreshPieMenu();
// Moves the `selected_window_` to the desk at the given `index`.
void MoveSelectedWindowToDeskAtIndex(int index);
bool is_active_ = false;
// The current root window the layer of `this` belongs to. It's always nullptr
// when WM Mode is inactive.
raw_ptr<aura::Window> current_root_ = nullptr;
// The window that got selected as the top-most one at the most recent
// received located event. This window (if available) will be the one that
// receives all the gestures supported by this mode.
raw_ptr<aura::Window, DanglingUntriaged> selected_window_ = nullptr;
views::UniqueWidgetPtr pie_menu_widget_;
raw_ptr<PieMenuView> pie_menu_view_ = nullptr;
// When WM Mode is enabled, we dim all the displays as an indication of this
// special mode being active, which disallows the normal interaction with
// windows and their contents, and enables the various gestures supported by
// this mode.
// `dimmers_` maps each root window to its associated dimmer.
base::flat_map<aura::Window*, std::unique_ptr<WindowDimmer>> dimmers_;
// The screen location of the last received release located event.
// Valid only if we receive a release located event, and only until
// `OnLocatedEvent()` returns.
std::optional<gfx::Point> last_release_event_screen_point_;
};
} // namespace ash
#endif // ASH_WM_MODE_WM_MODE_CONTROLLER_H_