Skip to content

Commit

Permalink
Add DialogModel-to-MenuModel adapter
Browse files Browse the repository at this point in the history
This adds a DialogModelMenuItem along with a DialogModelMenuModelAdapter
which is used to be able to run a context menu using DialogModel's
simpler Builder APIs.

TODOs are left all over the place alongside NOTREACHED()s that haven't
been hit while running this.

Placeholder code in SavedTabGroupButton is left to help give an example
to dljames@ for how to add the real context-menu content. This was used
to test that both labels and icons could be added.

One real defect is that I could not figure out how to make Mac context
menus show icons. IS_NESTED is added to views::MenuRunner to force this
to render under views, which is consistent with the BookmarkBarView
context-menu items that are adjacent and semantically similar to this
item.

Bug: 1324360, 1324598
Change-Id: Ic111b77fa28b7fb2b3bd7081f26ef304b4faeffe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3642703
Auto-Submit: Peter Boström <pbos@chromium.org>
Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: Peter Boström <pbos@chromium.org>
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1002347}
  • Loading branch information
pbos authored and Chromium LUCI CQ committed May 11, 2022
1 parent f43710d commit fcd59c4
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <string>

#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/tabs/tab_group_theme.h"
#include "chrome/browser/ui/view_ids.h"
Expand All @@ -16,6 +17,8 @@
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/models/dialog_model_menu_model_adapter.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/canvas.h"
Expand All @@ -25,6 +28,7 @@
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/menu/menu_runner.h"

namespace {
constexpr float kBorderRadius = 4.5f;
Expand Down Expand Up @@ -81,6 +85,9 @@ SavedTabGroupButton::SavedTabGroupButton(PressedCallback callback,
// comfortably fit in the bookmarks bar.
SetPreferredSize(gfx::Size(button_height, button_height));
}
// TODO(crbug.com/1324360): Add this back when the ContextMenuController does
// something reasonable.
// set_context_menu_controller(&context_menu_controller_);
}

SavedTabGroupButton::~SavedTabGroupButton() = default;
Expand Down Expand Up @@ -179,5 +186,37 @@ bool SavedTabGroupButton::HasButtonOutline() const {
return is_group_in_tabstrip_;
}

SavedTabGroupButton::ContextMenuController::ContextMenuController() = default;
SavedTabGroupButton::ContextMenuController::~ContextMenuController() = default;

void SavedTabGroupButton::ContextMenuController::ShowContextMenuForViewImpl(
View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) {
// TODO(pbos): Populate with real data, this is a placeholder to show dljames@
// how the API is intended to be used. DoNothing()s need to be replaced with
// base::BindRepeating calls to open tabs.
auto dialog_model =
ui::DialogModel::Builder()
.AddMenuItem(ui::ImageModel::FromVectorIcon(kSaveGroupIcon), u"HELLO",
base::DoNothing())
.AddMenuItem(
ui::ImageModel::FromVectorIcon(kMoveGroupToNewWindowIcon),
u"HELLO AGAIN", base::DoNothing())
.Build();
menu_model_ = std::make_unique<ui::DialogModelMenuModelAdapter>(
std::move(dialog_model));

// TODO(pbos): See if there's a better way than IS_NESTED to force this to
// show icons (we need favicons, I haven't figured out why this doesn't show
// icons under Mac OS context menus).
menu_runner_ = std::make_unique<views::MenuRunner>(
menu_model_.get(),
views::MenuRunner::CONTEXT_MENU | views::MenuRunner::IS_NESTED);
menu_runner_->RunMenuAt(source->GetWidget(), /*button_controller=*/nullptr,
gfx::Rect(point, gfx::Size()),
views::MenuAnchorPosition::kTopLeft, source_type);
}

BEGIN_METADATA(SavedTabGroupButton, MenuButton)
END_METADATA
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "components/tab_groups/tab_group_color.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/button/menu_button.h"

namespace gfx {
Expand All @@ -31,15 +32,12 @@ class SavedTabGroupButton : public views::MenuButton {
SavedTabGroupButton& operator=(const SavedTabGroupButton&) = delete;
~SavedTabGroupButton() override;

// views::MenuButton:
std::u16string GetTooltipText(const gfx::Point& p) const override;

void GetAccessibleNodeData(ui::AXNodeData* node_data) override;

void OnPaintBackground(gfx::Canvas* canvas) override;

std::unique_ptr<views::LabelButtonBorder> CreateDefaultBorder()
const override;

void OnThemeChanged() override;

void RemoveButtonOutline();
Expand All @@ -50,6 +48,21 @@ class SavedTabGroupButton : public views::MenuButton {
}

private:
class ContextMenuController : public views::ContextMenuController {
public:
ContextMenuController();
~ContextMenuController() override;

private:
void ShowContextMenuForViewImpl(View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) override;

// TODO(pbos): Comment
std::unique_ptr<ui::MenuModel> menu_model_;
std::unique_ptr<views::MenuRunner> menu_runner_;
};

// The animations for button movement.
std::unique_ptr<gfx::SlideAnimation> show_animation_;

Expand All @@ -58,6 +71,8 @@ class SavedTabGroupButton : public views::MenuButton {

// Denotes if the tabgroup is currently open in the tabstrip.
bool is_group_in_tabstrip_;

ContextMenuController context_menu_controller_;
};

#endif // CHROME_BROWSER_UI_VIEWS_BOOKMARKS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_BUTTON_H_
2 changes: 2 additions & 0 deletions ui/base/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ component("base") {
"models/dialog_model_field.cc",
"models/dialog_model_field.h",
"models/dialog_model_host.h",
"models/dialog_model_menu_model_adapter.cc",
"models/dialog_model_menu_model_adapter.h",
"models/image_model.cc",
"models/image_model.h",
"models/list_model.h",
Expand Down
8 changes: 8 additions & 0 deletions ui/base/models/dialog_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ void DialogModel::AddSeparator() {
AddField(std::make_unique<DialogModelSeparator>(GetPassKey(), this));
}

void DialogModel::AddMenuItem(ImageModel icon,
std::u16string label,
base::RepeatingCallback<void(int)> callback) {
AddField(std::make_unique<DialogModelMenuItem>(
GetPassKey(), this, std::move(icon), std::move(label),
std::move(callback)));
}

void DialogModel::AddTextfield(std::u16string label,
std::u16string text,
const DialogModelTextfield::Params& params) {
Expand Down
15 changes: 15 additions & 0 deletions ui/base/models/dialog_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <string>

#include "base/callback.h"
#include "base/callback_forward.h"
#include "base/component_export.h"
#include "base/memory/raw_ptr.h"
#include "base/types/pass_key.h"
Expand Down Expand Up @@ -220,6 +221,15 @@ class COMPONENT_EXPORT(UI_BASE) DialogModel final {
return *this;
}

// Adds a menu item. See DialogModel::AddMenuItem().
Builder& AddMenuItem(ImageModel icon,
std::u16string label,
base::RepeatingCallback<void(int)> callback) {
model_->AddMenuItem(std::move(icon), std::move(label),
std::move(callback));
return *this;
}

// Adds a separator. See DialogModel::AddSeparator().
Builder& AddSeparator() {
model_->AddSeparator();
Expand Down Expand Up @@ -278,6 +288,11 @@ class COMPONENT_EXPORT(UI_BASE) DialogModel final {
const DialogModelCombobox::Params& params =
DialogModelCombobox::Params());

// Adds a menu item at the end of the dialog model.
void AddMenuItem(ImageModel icon,
std::u16string label,
base::RepeatingCallback<void(int)> callback);

// Adds a separator at the end of the dialog model.
void AddSeparator();

Expand Down
34 changes: 34 additions & 0 deletions ui/base/models/dialog_model_field.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ DialogModelTextfield* DialogModelField::AsTextfield(
return AsTextfield();
}

const DialogModelMenuItem* DialogModelField::AsMenuItem(
base::PassKey<DialogModelHost>) const {
return AsMenuItem();
}

DialogModelMenuItem* DialogModelField::AsMenuItem(
base::PassKey<DialogModelHost>) {
return const_cast<DialogModelMenuItem*>(AsMenuItem());
}

DialogModelCustomField* DialogModelField::AsCustomField(
base::PassKey<DialogModelHost>) {
return AsCustomField();
Expand All @@ -114,6 +124,11 @@ DialogModelCombobox* DialogModelField::AsCombobox() {
return static_cast<DialogModelCombobox*>(this);
}

const DialogModelMenuItem* DialogModelField::AsMenuItem() const {
DCHECK_EQ(type_, kMenuItem);
return static_cast<const DialogModelMenuItem*>(this);
}

DialogModelTextfield* DialogModelField::AsTextfield() {
DCHECK_EQ(type_, kTextfield);
return static_cast<DialogModelTextfield*>(this);
Expand Down Expand Up @@ -238,6 +253,25 @@ void DialogModelCombobox::OnPerformAction(base::PassKey<DialogModelHost>) {
callback_.Run();
}

DialogModelMenuItem::DialogModelMenuItem(
base::PassKey<DialogModel> pass_key,
DialogModel* model,
ImageModel icon,
std::u16string label,
base::RepeatingCallback<void(int)> callback)
: DialogModelField(pass_key, model, kMenuItem, -1, {}),
icon_(std::move(icon)),
label_(std::move(label)),
callback_(std::move(callback)) {}

DialogModelMenuItem::~DialogModelMenuItem() = default;

void DialogModelMenuItem::OnActivated(base::PassKey<DialogModelHost> pass_key,
int event_flags) {
DCHECK(callback_);
callback_.Run(event_flags);
}

DialogModelSeparator::DialogModelSeparator(base::PassKey<DialogModel> pass_key,
DialogModel* model)
: DialogModelField(pass_key, model, kSeparator, -1, {}) {}
Expand Down
37 changes: 37 additions & 0 deletions ui/base/models/dialog_model_field.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "base/types/pass_key.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/image_model.h"

namespace ui {

Expand All @@ -25,6 +26,7 @@ class DialogModelCheckbox;
class DialogModelCombobox;
class DialogModelCustomField;
class DialogModelHost;
class DialogModelMenuItem;
class DialogModelTextfield;
class Event;

Expand Down Expand Up @@ -113,6 +115,7 @@ class COMPONENT_EXPORT(UI_BASE) DialogModelField {
kCheckbox,
kCombobox,
kCustom,
kMenuItem,
kSeparator,
kTextfield
};
Expand All @@ -133,6 +136,8 @@ class COMPONENT_EXPORT(UI_BASE) DialogModelField {
DialogModelBodyText* AsBodyText(base::PassKey<DialogModelHost>);
DialogModelCheckbox* AsCheckbox(base::PassKey<DialogModelHost>);
DialogModelCombobox* AsCombobox(base::PassKey<DialogModelHost>);
DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>);
const DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>) const;
DialogModelTextfield* AsTextfield(base::PassKey<DialogModelHost>);
DialogModelCustomField* AsCustomField(base::PassKey<DialogModelHost>);

Expand All @@ -149,6 +154,7 @@ class COMPONENT_EXPORT(UI_BASE) DialogModelField {
DialogModelBodyText* AsBodyText();
DialogModelCheckbox* AsCheckbox();
DialogModelCombobox* AsCombobox();
const DialogModelMenuItem* AsMenuItem() const;
DialogModelTextfield* AsTextfield();
DialogModelCustomField* AsCustomField();

Expand Down Expand Up @@ -351,6 +357,37 @@ class COMPONENT_EXPORT(UI_BASE) DialogModelCombobox : public DialogModelField {
base::RepeatingClosure callback_;
};

// Field class representing a menu item:
//
// <icon> <label>
// Ex: [icon] Open URL
class COMPONENT_EXPORT(UI_BASE) DialogModelMenuItem : public DialogModelField {
public:
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelMenuItem(base::PassKey<DialogModel> pass_key,
DialogModel* model,
ImageModel icon,
std::u16string label,
base::RepeatingCallback<void(int)> callback);
DialogModelMenuItem(const DialogModelMenuItem&) = delete;
DialogModelMenuItem& operator=(const DialogModelMenuItem&) = delete;
~DialogModelMenuItem() override;

// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
const ImageModel& icon(base::PassKey<DialogModelHost>) const { return icon_; }
const std::u16string& label(base::PassKey<DialogModelHost>) const {
return label_;
}
void OnActivated(base::PassKey<DialogModelHost>, int event_flags);

private:
const ImageModel icon_;
const std::u16string label_;
base::RepeatingCallback<void(int)> callback_;
};

// Field class representing a separator.
class COMPONENT_EXPORT(UI_BASE) DialogModelSeparator : public DialogModelField {
public:
Expand Down

0 comments on commit fcd59c4

Please sign in to comment.