chromium/ash/wm_mode/wm_mode_controller.h

// 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_