chromium/ash/style/drop_down_checkbox.h

// Copyright 2023 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_STYLE_DROP_DOWN_CHECKBOX_H_
#define ASH_STYLE_DROP_DOWN_CHECKBOX_H_

#include <memory>

#include "ash/ash_export.h"
#include "base/scoped_observation.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/list_model.h"
#include "ui/base/models/list_selection_model.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/widget/unique_widget_ptr.h"

namespace views {
class ImageView;
class Label;
}  // namespace views

namespace ash {

// A label button has a drop-down list of checkboxes that can be accessed by
// clicking the arrow button. The data model of the drop down checkboxes is a
// `ui::ListModel` with `std::u16string` type.
class ASH_EXPORT DropDownCheckbox : public views::Button,
                                    public views::WidgetObserver {
  METADATA_HEADER(DropDownCheckbox, views::Button)

 public:
  using ItemModel = ui::ListModel<std::u16string>;
  using SelectedIndices = ui::ListSelectionModel::SelectedIndices;
  using SelectedItems = std::vector<std::u16string>;

  DropDownCheckbox(const std::u16string& title, ItemModel* model);
  DropDownCheckbox(const DropDownCheckbox&) = delete;
  DropDownCheckbox& operator=(const DropDownCheckbox&) = delete;
  ~DropDownCheckbox() override;

  // The action which will be taken after the drop-down menu is closed.
  void SetSelectedAction(base::RepeatingClosure callback);

  SelectedIndices GetSelectedIndices() const;
  SelectedItems GetSelectedItems() const;

  // Returns whether or not the menu is currently running.
  bool IsMenuRunning() const;

  // views::Button:
  void SetCallback(PressedCallback callback) override;
  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
  void OnBlur() override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void Layout(PassKey) override;

  // WidgetObserver:
  void OnWidgetBoundsChanged(views::Widget* widget,
                             const gfx::Rect& bounds) override;

 private:
  class SelectionModel;
  class MenuView;
  class EventHandler;

  // Gets expected menu bounds according to combox location.
  gfx::Rect GetExpectedMenuBounds() const;

  // Called when the arrow button is pressed.
  void OnDropDownCheckboxPressed();

  // Shows/Closes the drop down menu.
  void ShowDropDownMenu();
  void CloseDropDownMenu();

  // Called when the selections are made and the menu is closed.
  void OnPerformAction();

  // Reference to the data model.
  raw_ptr<ItemModel> model_;

  const raw_ptr<views::Label> title_ = nullptr;
  const raw_ptr<views::ImageView> drop_down_arrow_ = nullptr;

  // The selection model that manages the selections.
  std::unique_ptr<SelectionModel> selection_model_;

  // The action that will be taken after closing the drop down menu.
  base::RepeatingClosure callback_;

  // A handler handles mouse and touch event happening outside drop down
  // checkbox. This is mainly used to decide if we should close it.
  std::unique_ptr<EventHandler> event_handler_;

  // Drop down menu view owned by menu widget.
  raw_ptr<MenuView> menu_view_ = nullptr;

  // Drop down menu widget.
  views::UniqueWidgetPtr menu_;

  // The last time that the menu closed. This is used to avoid re-opening the
  // menu while clicking on the arrow button to close the menu.
  base::TimeTicks closed_time_;

  base::ScopedObservation<views::Widget, views::WidgetObserver>
      widget_observer_{this};
};

}  // namespace ash

#endif  // ASH_STYLE_DROP_DOWN_CHECKBOX_H_