diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn index 92a1ab7252000..49942cf973c10 100644 --- a/chrome/app/vector_icons/BUILD.gn +++ b/chrome/app/vector_icons/BUILD.gn @@ -49,8 +49,6 @@ aggregate_vector_icons("chrome_vector_icons") { "drag_handle.icon", "eol.icon", "extension_crashed.icon", - "eye.icon", - "eye_crossed.icon", "file_download_shelf.icon", "fingerprint.icon", "forward_arrow_touch.icon", diff --git a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc index 84deabb49ceb6..2be40e118bbda 100644 --- a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc +++ b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc @@ -7,7 +7,6 @@ #include #include "base/strings/utf_string_conversions.h" -#include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/view_ids.h" @@ -20,6 +19,7 @@ #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/paint_vector_icon.h" +#include "ui/views/vector_icons.h" CookieControlsIconView::CookieControlsIconView( IconLabelBubbleView::Delegate* icon_label_bubble_delegate, @@ -116,9 +116,9 @@ views::BubbleDialogDelegate* CookieControlsIconView::GetBubble() const { } const gfx::VectorIcon& CookieControlsIconView::GetVectorIcon() const { - if (status_ == CookieControlsStatus::kDisabledForSite) - return kEyeIcon; - return kEyeCrossedIcon; + return status_ == CookieControlsStatus::kDisabledForSite + ? views::kEyeIcon + : views::kEyeCrossedIcon; } std::u16string CookieControlsIconView::GetTextForTooltipAndAccessibleName() diff --git a/chrome/browser/ui/views/page_info/page_info_view_factory.cc b/chrome/browser/ui/views/page_info/page_info_view_factory.cc index 03d3aab3ac592..3de6a59bde0e5 100644 --- a/chrome/browser/ui/views/page_info/page_info_view_factory.cc +++ b/chrome/browser/ui/views/page_info/page_info_view_factory.cc @@ -481,7 +481,7 @@ const ui::ImageModel PageInfoViewFactory::GetManagedPermissionIcon( // static const ui::ImageModel PageInfoViewFactory::GetBlockingThirdPartyCookiesIcon() { - return ui::ImageModel::FromVectorIcon(kEyeCrossedIcon, ui::kColorIcon, + return ui::ImageModel::FromVectorIcon(views::kEyeCrossedIcon, ui::kColorIcon, GetIconSize()); } diff --git a/chrome/browser/ui/views/passwords/password_save_update_view.cc b/chrome/browser/ui/views/passwords/password_save_update_view.cc index 98b24b5d71790..2e36fad0d126e 100644 --- a/chrome/browser/ui/views/passwords/password_save_update_view.cc +++ b/chrome/browser/ui/views/passwords/password_save_update_view.cc @@ -52,11 +52,10 @@ #include "ui/gfx/vector_icon_utils.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/bubble/bubble_frame_view.h" -#include "ui/views/controls/button/image_button.h" -#include "ui/views/controls/button/image_button_factory.h" #include "ui/views/controls/button/md_text_button.h" #include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/editable_combobox/editable_combobox.h" +#include "ui/views/controls/editable_combobox/editable_password_combobox.h" #include "ui/views/controls/styled_label.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/interaction/element_tracker_views.h" @@ -93,13 +92,10 @@ std::unique_ptr CreateRow() { // Builds a credential row, adds the given elements to the layout. // |destination_field| is nullptr if the destination field shouldn't be shown. -// |password_view_button| is an optional field. -void BuildCredentialRows( - views::View* parent_view, - std::unique_ptr destination_field, - std::unique_ptr username_field, - std::unique_ptr password_field, - std::unique_ptr password_view_button) { +void BuildCredentialRows(views::View* parent_view, + std::unique_ptr destination_field, + std::unique_ptr username_field, + std::unique_ptr password_field) { std::unique_ptr username_label(new views::Label( l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL), views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY)); @@ -151,11 +147,6 @@ void BuildCredentialRows( views::MaximumFlexSizeRule::kUnbounded)); password_row->AddChildView(std::move(password_field)); - // The eye icon is also added to the layout if it was passed. - if (password_view_button) { - password_row->AddChildView(std::move(password_view_button)); - } - parent_view->AddChildView(std::move(password_row)); } @@ -169,22 +160,6 @@ std::vector ToValues( return passwords; } -std::unique_ptr CreatePasswordViewButton( - views::Button::PressedCallback callback, - bool are_passwords_revealed) { - auto button = std::make_unique(std::move(callback)); - button->SetInstallFocusRingOnFocus(true); - button->SetRequestFocusOnPress(true); - button->SetTooltipText( - l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_SHOW_PASSWORD)); - button->SetToggledTooltipText( - l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_HIDE_PASSWORD)); - button->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER); - button->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE); - button->SetToggled(are_passwords_revealed); - return button; -} - // Creates an EditableCombobox from |PasswordForm.all_possible_usernames| or // even just |PasswordForm.username_value|. std::unique_ptr CreateUsernameEditableCombobox( @@ -204,8 +179,7 @@ std::unique_ptr CreateUsernameEditableCombobox( std::vector(usernames.begin(), usernames.end())), /*filter_on_edit=*/false, /*show_on_empty=*/true, - views::EditableCombobox::Type::kRegular, views::style::CONTEXT_BUTTON, - views::style::STYLE_PRIMARY, display_arrow); + views::style::CONTEXT_BUTTON, views::style::STYLE_PRIMARY, display_arrow); combobox->SetText(form.username_value); combobox->SetAccessibleName( l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL)); @@ -214,11 +188,14 @@ std::unique_ptr CreateUsernameEditableCombobox( return combobox; } -// Creates an EditableCombobox from |PasswordForm.all_possible_passwords| or -// even just |PasswordForm.password_value|. -std::unique_ptr CreatePasswordEditableCombobox( +// Creates an EditablePasswordCombobox from +// `PasswordForm.all_possible_passwords` or even just +// `PasswordForm.password_value`. +std::unique_ptr CreateEditablePasswordCombobox( const password_manager::PasswordForm& form, - bool are_passwords_revealed) { + bool are_passwords_revealed, + views::EditablePasswordCombobox::IsPasswordRevealPermittedCheck + reveal_permitted_check) { DCHECK(!form.IsFederatedCredential()); std::vector passwords = form.all_possible_passwords.empty() @@ -228,14 +205,16 @@ std::unique_ptr CreatePasswordEditableCombobox( return password.empty(); }); bool display_arrow = !passwords.empty(); - auto combobox = std::make_unique( + auto combobox = std::make_unique( std::make_unique( std::vector(passwords.begin(), passwords.end())), - /*filter_on_edit=*/false, /*show_on_empty=*/true, - views::EditableCombobox::Type::kPassword, views::style::CONTEXT_BUTTON, - STYLE_PRIMARY_MONOSPACED, display_arrow); + views::style::CONTEXT_BUTTON, STYLE_PRIMARY_MONOSPACED, display_arrow); combobox->SetText(form.password_value); + combobox->SetPasswordIconTooltips( + l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_SHOW_PASSWORD), + l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_HIDE_PASSWORD)); + combobox->SetIsPasswordRevealPermittedCheck(std::move(reveal_permitted_check)); combobox->RevealPasswords(are_passwords_revealed); combobox->SetAccessibleName( l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL)); @@ -311,9 +290,7 @@ PasswordSaveUpdateView::PasswordSaveUpdateView( ? PasswordBubbleControllerBase::DisplayReason::kAutomatic : PasswordBubbleControllerBase::DisplayReason::kUserAction), is_update_bubble_(controller_.state() == - password_manager::ui::PENDING_PASSWORD_UPDATE_STATE), - are_passwords_revealed_( - controller_.are_passwords_revealed_when_bubble_is_opened()) { + password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) { DCHECK(controller_.state() == password_manager::ui::PENDING_PASSWORD_STATE || controller_.state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE); @@ -359,16 +336,12 @@ PasswordSaveUpdateView::PasswordSaveUpdateView( CreateUsernameEditableCombobox(password_form); username_dropdown->SetCallback(base::BindRepeating( &PasswordSaveUpdateView::OnContentChanged, base::Unretained(this))); - std::unique_ptr password_dropdown = - CreatePasswordEditableCombobox(password_form, are_passwords_revealed_); - password_dropdown->SetCallback(base::BindRepeating( - &PasswordSaveUpdateView::OnContentChanged, base::Unretained(this))); - std::unique_ptr password_view_button = - CreatePasswordViewButton( - base::BindRepeating( - &PasswordSaveUpdateView::TogglePasswordVisibility, - base::Unretained(this)), - are_passwords_revealed_); + std::unique_ptr password_dropdown = + CreateEditablePasswordCombobox( + password_form, + controller_.are_passwords_revealed_when_bubble_is_opened(), + base::BindRepeating(&SaveUpdateBubbleController::RevealPasswords, + base::Unretained(&controller_))); // Set up layout: SetLayoutManager(std::make_unique()); views::View* root_view = AddChildView(std::make_unique()); @@ -393,11 +366,9 @@ PasswordSaveUpdateView::PasswordSaveUpdateView( username_dropdown_ = username_dropdown.get(); password_dropdown_ = password_dropdown.get(); - password_view_button_ = password_view_button.get(); BuildCredentialRows(root_view, std::move(destination_dropdown), std::move(username_dropdown), - std::move(password_dropdown), - std::move(password_view_button)); + std::move(password_dropdown)); // The |username_dropdown_| should observe the animating layout manager to // close the dropdown menu when the animation starts. @@ -522,23 +493,6 @@ void PasswordSaveUpdateView::AddedToWidget() { MaybeShowIPH(IPHType::kRegular); } -void PasswordSaveUpdateView::OnThemeChanged() { - PasswordBubbleViewBase::OnThemeChanged(); - if (password_view_button_) { - const auto* color_provider = GetColorProvider(); - const SkColor icon_color = color_provider->GetColor(ui::kColorIcon); - const SkColor disabled_icon_color = - color_provider->GetColor(ui::kColorIconDisabled); - views::SetImageFromVectorIconWithColor(password_view_button_, kEyeIcon, - GetDefaultSizeOfVectorIcon(kEyeIcon), - icon_color, disabled_icon_color); - views::SetToggledImageFromVectorIconWithColor( - password_view_button_, kEyeCrossedIcon, - GetDefaultSizeOfVectorIcon(kEyeCrossedIcon), icon_color, - disabled_icon_color); - } -} - void PasswordSaveUpdateView::OnLayoutIsAnimatingChanged( views::AnimatingLayoutManager* source, bool is_animating) { @@ -546,16 +500,6 @@ void PasswordSaveUpdateView::OnLayoutIsAnimatingChanged( MaybeShowIPH(IPHType::kRegular); } -void PasswordSaveUpdateView::TogglePasswordVisibility() { - if (!are_passwords_revealed_ && !controller_.RevealPasswords()) - return; - - are_passwords_revealed_ = !are_passwords_revealed_; - password_view_button_->SetToggled(are_passwords_revealed_); - DCHECK(password_dropdown_); - password_dropdown_->RevealPasswords(are_passwords_revealed_); -} - void PasswordSaveUpdateView::UpdateUsernameAndPasswordInModel() { if (!username_dropdown_ && !password_dropdown_) return; diff --git a/chrome/browser/ui/views/passwords/password_save_update_view.h b/chrome/browser/ui/views/passwords/password_save_update_view.h index ca5eff8bae1f8..8f516b22efa73 100644 --- a/chrome/browser/ui/views/passwords/password_save_update_view.h +++ b/chrome/browser/ui/views/passwords/password_save_update_view.h @@ -19,7 +19,7 @@ namespace views { class AnimatingLayoutManager; class Combobox; class EditableCombobox; -class ToggleImageButton; +class EditablePasswordCombobox; } // namespace views // A view offering the user the ability to save or update credentials (depending @@ -64,13 +64,10 @@ class PasswordSaveUpdateView : public PasswordBubbleViewBase, // View: void AddedToWidget() override; - void OnThemeChanged() override; // views::AnimatingLayoutManager::Observer: void OnLayoutIsAnimatingChanged(views::AnimatingLayoutManager* source, bool is_animating) override; - - void TogglePasswordVisibility(); void UpdateUsernameAndPasswordInModel(); void UpdateBubbleUIElements(); std::unique_ptr CreateFooterView(); @@ -105,12 +102,9 @@ class PasswordSaveUpdateView : public PasswordBubbleViewBase, raw_ptr destination_dropdown_ = nullptr; + // The views for the username and password dropdown elements. raw_ptr username_dropdown_ = nullptr; - raw_ptr password_view_button_ = nullptr; - - // The view for the password value. - raw_ptr password_dropdown_ = nullptr; - bool are_passwords_revealed_; + raw_ptr password_dropdown_ = nullptr; // When showing kReauthFailure IPH, the promo controller gives back an // ID. This is used to close the bubble later. diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn index 36bc22a122193..accb869030e59 100644 --- a/ui/views/BUILD.gn +++ b/ui/views/BUILD.gn @@ -27,6 +27,8 @@ aggregate_vector_icons("views_vector_icons") { "checkbox_normal.icon", "close.icon", "drag_general_selection.icon", + "eye.icon", + "eye_crossed.icon", "ic_close.icon", "info.icon", "launch.icon", @@ -129,6 +131,7 @@ component("views") { "controls/combobox/combobox_util.h", "controls/dot_indicator.h", "controls/editable_combobox/editable_combobox.h", + "controls/editable_combobox/editable_password_combobox.h", "controls/focus_ring.h", "controls/focusable_border.h", "controls/highlight_path_generator.h", @@ -360,6 +363,7 @@ component("views") { "controls/combobox/empty_combobox_model.h", "controls/dot_indicator.cc", "controls/editable_combobox/editable_combobox.cc", + "controls/editable_combobox/editable_password_combobox.cc", "controls/focus_ring.cc", "controls/focusable_border.cc", "controls/highlight_path_generator.cc", @@ -1171,6 +1175,7 @@ test("views_unittests") { "controls/button/toggle_button_unittest.cc", "controls/combobox/combobox_unittest.cc", "controls/editable_combobox/editable_combobox_unittest.cc", + "controls/editable_combobox/editable_password_combobox_unittest.cc", "controls/image_view_unittest.cc", "controls/label_unittest.cc", "controls/link_fragment_unittest.cc", diff --git a/ui/views/controls/button/image_button_factory.cc b/ui/views/controls/button/image_button_factory.cc index 24cb3356464f8..3af61856ef964 100644 --- a/ui/views/controls/button/image_button_factory.cc +++ b/ui/views/controls/button/image_button_factory.cc @@ -141,4 +141,19 @@ void SetImageFromVectorIconWithColorId(ImageButton* button, InkDrop::Get(button)->SetBaseColorId(icon_color_id); } +void SetToggledImageFromVectorIconWithColorId( + ToggleImageButton* button, + const gfx::VectorIcon& icon, + ui::ColorId icon_color_id, + ui::ColorId icon_disabled_color_id) { + int dip_size = GetDefaultSizeOfVectorIcon(icon); + const ui::ImageModel& normal_image = + ui::ImageModel::FromVectorIcon(icon, icon_color_id, dip_size); + const ui::ImageModel& disabled_image = + ui::ImageModel::FromVectorIcon(icon, icon_disabled_color_id, dip_size); + + button->SetToggledImageModel(Button::STATE_NORMAL, normal_image); + button->SetToggledImageModel(Button::STATE_DISABLED, disabled_image); +} + } // namespace views diff --git a/ui/views/controls/button/image_button_factory.h b/ui/views/controls/button/image_button_factory.h index 31d29b56f912f..5d1a412a36451 100644 --- a/ui/views/controls/button/image_button_factory.h +++ b/ui/views/controls/button/image_button_factory.h @@ -75,6 +75,14 @@ VIEWS_EXPORT void SetImageFromVectorIconWithColorId( ui::ColorId icon_color_id, ui::ColorId icon_disabled_color_id); +// Sets images on a `ToggleImageButton` |button| for STATE_NORMAL and +// STATE_DISABLED with the default size from the given vector icon and colors. +VIEWS_EXPORT void SetToggledImageFromVectorIconWithColorId( + ToggleImageButton* button, + const gfx::VectorIcon& icon, + ui::ColorId icon_color_id, + ui::ColorId icon_disabled_color_id); + } // namespace views #endif // UI_VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_FACTORY_H_ diff --git a/ui/views/controls/editable_combobox/editable_combobox.cc b/ui/views/controls/editable_combobox/editable_combobox.cc index 583b72fc8bcad..3b3df954850c9 100644 --- a/ui/views/controls/editable_combobox/editable_combobox.cc +++ b/ui/views/controls/editable_combobox/editable_combobox.cc @@ -4,6 +4,7 @@ #include "ui/views/controls/editable_combobox/editable_combobox.h" +#include #include #include #include @@ -18,7 +19,6 @@ #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/accelerators/accelerator.h" -#include "ui/base/ime/text_input_type.h" #include "ui/base/menu_source_utils.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/base/metadata/metadata_impl_macros.h" @@ -38,7 +38,6 @@ #include "ui/gfx/geometry/size.h" #include "ui/gfx/image/image.h" #include "ui/gfx/range/range.h" -#include "ui/gfx/render_text.h" #include "ui/gfx/scoped_canvas.h" #include "ui/views/animation/flood_fill_ink_drop_ripple.h" #include "ui/views/animation/ink_drop.h" @@ -53,11 +52,14 @@ #include "ui/views/controls/menu/menu_runner.h" #include "ui/views/controls/menu/menu_types.h" #include "ui/views/controls/textfield/textfield.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/box_layout_view.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/layout/layout_manager.h" #include "ui/views/layout/layout_provider.h" #include "ui/views/style/platform_style.h" #include "ui/views/style/typography.h" +#include "ui/views/vector_icons.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" @@ -70,6 +72,8 @@ class Arrow : public Button { METADATA_HEADER(Arrow); explicit Arrow(PressedCallback callback) : Button(std::move(callback)) { + SetPreferredSize(gfx::Size(GetComboboxArrowContainerWidthAndMargins(), + ComboboxArrowSize().height())); // Similar to Combobox's TransparentButton. SetFocusBehavior(FocusBehavior::NEVER); button_controller()->set_notify_action( @@ -102,7 +106,6 @@ class Arrow : public Button { } private: - // Button: void PaintButtonContents(gfx::Canvas* canvas) override { gfx::ScopedCanvas scoped_canvas(canvas); canvas->ClipRect(GetContentsBounds()); @@ -119,8 +122,9 @@ class Arrow : public Button { node_data->role = ax::mojom::Role::kButton; node_data->SetName(GetAccessibleName()); node_data->SetHasPopup(ax::mojom::HasPopup::kMenu); - if (GetEnabled()) + if (GetEnabled()) { node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen); + } } }; @@ -129,6 +133,11 @@ END_METADATA } // namespace +std::u16string EditableCombobox::MenuDecorationStrategy::DecorateItemText( + std::u16string text) const { + return text; +} + // Adapts a ui::ComboboxModel to a ui::MenuModel to be used by EditableCombobox. // Also provides a filtering capability. class EditableCombobox::EditableComboboxMenuModel @@ -136,11 +145,12 @@ class EditableCombobox::EditableComboboxMenuModel public ui::ComboboxModelObserver { public: EditableComboboxMenuModel(EditableCombobox* owner, - ui::ComboboxModel* combobox_model, + std::unique_ptr combobox_model, const bool filter_on_edit, const bool show_on_empty) - : owner_(owner), - combobox_model_(combobox_model), + : decoration_strategy_(std::make_unique()), + owner_(owner), + combobox_model_(std::move(combobox_model)), filter_on_edit_(filter_on_edit), show_on_empty_(show_on_empty) { UpdateItemsShown(); @@ -154,8 +164,9 @@ class EditableCombobox::EditableComboboxMenuModel ~EditableComboboxMenuModel() override = default; void UpdateItemsShown() { - if (!update_items_shown_enabled_) + if (!update_items_shown_enabled_) { return; + } items_shown_.clear(); if (show_on_empty_ || !owner_->GetText().empty()) { for (size_t i = 0; i < combobox_model_->GetItemCount(); ++i) { @@ -166,8 +177,16 @@ class EditableCombobox::EditableComboboxMenuModel } } } - if (menu_model_delegate()) + if (menu_model_delegate()) { menu_model_delegate()->OnMenuStructureChanged(); + } + } + + void SetDecorationStrategy( + std::unique_ptr strategy) { + DCHECK(strategy); + decoration_strategy_ = std::move(strategy); + UpdateItemsShown(); } void DisableUpdateItemsShown() { update_items_shown_enabled_ = false; } @@ -178,13 +197,12 @@ class EditableCombobox::EditableComboboxMenuModel return MenuConfig::instance().check_selected_combobox_item; } - std::u16string GetItemTextAt(size_t index, bool showing_password_text) const { - size_t index_in_model = items_shown_[index].index; - std::u16string text = combobox_model_->GetItemAt(index_in_model); - return showing_password_text - ? text - : std::u16string(text.length(), - gfx::RenderText::kPasswordReplacementChar); + std::u16string GetItemTextAt(size_t index) const { + return combobox_model_->GetItemAt(items_shown_[index].index); + } + + const ui::ComboboxModel* GetComboboxModel() const { + return combobox_model_.get(); } ui::ImageModel GetIconAt(size_t index) const override { @@ -202,6 +220,14 @@ class EditableCombobox::EditableComboboxMenuModel size_t GetItemCount() const override { return items_shown_.size(); } + // ui::MenuModel: + std::u16string GetLabelAt(size_t index) const override { + std::u16string text = + decoration_strategy_->DecorateItemText(GetItemTextAt(index)); + base::i18n::AdjustStringForLocaleDirection(&text); + return text; + } + private: struct ShownItem { size_t index; @@ -209,8 +235,9 @@ class EditableCombobox::EditableComboboxMenuModel }; bool HasIcons() const override { for (size_t i = 0; i < GetItemCount(); ++i) { - if (!GetIconAt(i).IsEmpty()) + if (!GetIconAt(i).IsEmpty()) { return true; + } } return false; } @@ -228,12 +255,6 @@ class EditableCombobox::EditableComboboxMenuModel return static_cast(index) + kFirstMenuItemId; } - std::u16string GetLabelAt(size_t index) const override { - std::u16string text = GetItemTextAt(index, owner_->showing_password_text_); - base::i18n::AdjustStringForLocaleDirection(&text); - return text; - } - bool IsItemDynamicAt(size_t index) const override { return false; } const gfx::FontList* GetLabelFontListAt(size_t index) const override { @@ -265,8 +286,11 @@ class EditableCombobox::EditableComboboxMenuModel MenuModel* GetSubmenuModelAt(size_t index) const override { return nullptr; } + // The strategy used to customize the display of the dropdown menu. + std::unique_ptr decoration_strategy_; + raw_ptr owner_; // Weak. Owns |this|. - raw_ptr combobox_model_; // Weak. + std::unique_ptr combobox_model_; // Whether to adapt the items shown to the textfield content. const bool filter_on_edit_; @@ -306,26 +330,30 @@ class EditableCombobox::EditableComboboxPreTargetHandler // ui::EventHandler overrides. void OnMouseEvent(ui::MouseEvent* event) override { if (event->type() == ui::ET_MOUSE_PRESSED && - event->button_flags() == event->changed_button_flags()) + event->button_flags() == event->changed_button_flags()) { HandlePressEvent(event->root_location()); + } } void OnTouchEvent(ui::TouchEvent* event) override { - if (event->type() == ui::ET_TOUCH_PRESSED) + if (event->type() == ui::ET_TOUCH_PRESSED) { HandlePressEvent(event->root_location()); + } } private: void HandlePressEvent(const gfx::Point& root_location) { View* handler = root_view_->GetEventHandlerForPoint(root_location); - if (handler == owner_->textfield_ || handler == owner_->arrow_) + if (handler == owner_->textfield_ || handler == owner_->arrow_) { return; + } owner_->CloseMenu(); } void StopObserving() { - if (!root_view_) + if (!root_view_) { return; + } root_view_->RemovePreTargetHandler(this); root_view_ = nullptr; } @@ -341,36 +369,32 @@ EditableCombobox::EditableCombobox( std::unique_ptr combobox_model, const bool filter_on_edit, const bool show_on_empty, - const Type type, const int text_context, const int text_style, const bool display_arrow) : textfield_(new Textfield()), text_context_(text_context), text_style_(text_style), - type_(type), filter_on_edit_(filter_on_edit), - show_on_empty_(show_on_empty), - showing_password_text_(type != Type::kPassword) { + show_on_empty_(show_on_empty) { SetModel(std::move(combobox_model)); observation_.Observe(textfield_.get()); textfield_->set_controller(this); textfield_->SetFontList(GetFontList()); - textfield_->SetTextInputType((type == Type::kPassword) - ? ui::TEXT_INPUT_TYPE_PASSWORD - : ui::TEXT_INPUT_TYPE_TEXT); AddChildView(textfield_.get()); + + control_elements_container_ = AddChildView(std::make_unique()); + control_elements_container_->SetInsideBorderInsets( + gfx::Insets::TLBR(0, 0, 0, + GetComboboxArrowContainerWidthAndMargins() - + GetComboboxArrowContainerWidth())); + if (display_arrow) { - textfield_->SetExtraInsets( - gfx::Insets::TLBR(0, 0, 0, - GetComboboxArrowContainerWidthAndMargins() - - (features::IsChromeRefresh2023() - ? kComboboxArrowPaddingWidthChromeRefresh2023 - : kComboboxArrowPaddingWidth))); - arrow_ = AddChildView(std::make_unique(base::BindRepeating( + arrow_ = AddControlElement(std::make_unique(base::BindRepeating( &EditableCombobox::ArrowButtonPressed, base::Unretained(this)))); } - SetLayoutManager(std::make_unique()); + + SetLayoutManager(std::make_unique()); } EditableCombobox::~EditableCombobox() { @@ -380,9 +404,8 @@ EditableCombobox::~EditableCombobox() { void EditableCombobox::SetModel(std::unique_ptr model) { CloseMenu(); - combobox_model_.swap(model); menu_model_ = std::make_unique( - this, combobox_model_.get(), filter_on_edit_, show_on_empty_); + this, std::move(model), filter_on_edit_, show_on_empty_); } const std::u16string& EditableCombobox::GetText() const { @@ -406,32 +429,30 @@ void EditableCombobox::SelectRange(const gfx::Range& range) { void EditableCombobox::OnAccessibleNameChanged(const std::u16string& new_name) { textfield_->SetAccessibleName(new_name); - if (arrow_) + if (arrow_) { arrow_->SetAccessibleName(new_name); + } } void EditableCombobox::SetAssociatedLabel(View* labelling_view) { textfield_->SetAssociatedLabel(labelling_view); } -void EditableCombobox::RevealPasswords(bool revealed) { - DCHECK_EQ(Type::kPassword, type_); - if (revealed == showing_password_text_) - return; - showing_password_text_ = revealed; - textfield_->SetTextInputType(revealed ? ui::TEXT_INPUT_TYPE_TEXT - : ui::TEXT_INPUT_TYPE_PASSWORD); +void EditableCombobox::SetMenuDecorationStrategy( + std::unique_ptr strategy) { + DCHECK(menu_model_); + menu_model_->SetDecorationStrategy(std::move(strategy)); +} + +void EditableCombobox::UpdateMenu() { menu_model_->UpdateItemsShown(); } void EditableCombobox::Layout() { View::Layout(); - if (arrow_) { - gfx::Rect arrow_bounds( - /*x=*/width() - GetComboboxArrowContainerWidthAndMargins(), - /*y=*/0, GetComboboxArrowContainerWidth(), height()); - arrow_->SetBoundsRect(arrow_bounds); - } + int preferred_width = control_elements_container_->GetPreferredSize().width(); + control_elements_container_->SetBounds(width() - preferred_width, 0, + preferred_width, height()); } void EditableCombobox::GetAccessibleNodeData(ui::AXNodeData* node_data) { @@ -478,8 +499,9 @@ void EditableCombobox::OnLayoutIsAnimatingChanged( views::AnimatingLayoutManager* source, bool is_animating) { dropdown_blocked_for_animation_ = is_animating; - if (dropdown_blocked_for_animation_) + if (dropdown_blocked_for_animation_) { CloseMenu(); + } } void EditableCombobox::CloseMenu() { @@ -488,10 +510,7 @@ void EditableCombobox::CloseMenu() { } void EditableCombobox::OnItemSelected(size_t index) { - // |textfield_| can hide the characters on its own so we read the actual - // characters instead of gfx::RenderText::kPasswordReplacementChar characters. - std::u16string selected_item_text = - menu_model_->GetItemTextAt(index, /*showing_password_text=*/true); + std::u16string selected_item_text = menu_model_->GetItemTextAt(index); textfield_->SetText(selected_item_text); // SetText does not actually notify the TextfieldController, so we call the // handling code directly. @@ -513,31 +532,35 @@ void EditableCombobox::HandleNewContent(const std::u16string& new_content) { content_changed_callback_.Run(); menu_model_->EnableUpdateItemsShown(); } - menu_model_->UpdateItemsShown(); + UpdateMenu(); } void EditableCombobox::ArrowButtonPressed(const ui::Event& event) { textfield_->RequestFocus(); - if (menu_runner_ && menu_runner_->IsRunning()) + if (menu_runner_ && menu_runner_->IsRunning()) { CloseMenu(); - else + } else { ShowDropDownMenu(ui::GetMenuSourceTypeForEvent(event)); + } } void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) { constexpr int kMenuBorderWidthTop = 1; - if (dropdown_blocked_for_animation_) + if (dropdown_blocked_for_animation_) { return; + } if (!menu_model_->GetItemCount()) { CloseMenu(); return; } - if (menu_runner_ && menu_runner_->IsRunning()) + if (menu_runner_ && menu_runner_->IsRunning()) { return; - if (!GetWidget()) + } + if (!GetWidget()) { return; + } // Since we don't capture the mouse, we want to see the events that happen in // the EditableCombobox's RootView to get a chance to close the menu if they @@ -567,12 +590,27 @@ void EditableCombobox::ShowDropDownMenu(ui::MenuSourceType source_type) { MenuAnchorPosition::kTopLeft, source_type); } +void EditableCombobox::UpdateTextfieldInsets() { + textfield_->SetExtraInsets(gfx::Insets::TLBR( + 0, 0, 0, + std::max(control_elements_container_->GetPreferredSize().width() - + (features::IsChromeRefresh2023() + ? kComboboxArrowPaddingWidthChromeRefresh2023 + : kComboboxArrowPaddingWidth), + 0))); +} + const ui::MenuModel* EditableCombobox::GetMenuModelForTesting() const { return menu_model_.get(); } std::u16string EditableCombobox::GetItemTextForTesting(size_t index) const { - return menu_model_->GetItemTextAt(index, showing_password_text_); + return menu_model_->GetLabelAt(index); +} + +const ui::ComboboxModel* EditableCombobox::GetComboboxModel() const { + DCHECK(menu_model_); + return menu_model_->GetComboboxModel(); } BEGIN_METADATA(EditableCombobox, View) diff --git a/ui/views/controls/editable_combobox/editable_combobox.h b/ui/views/controls/editable_combobox/editable_combobox.h index 85c86a56369e0..8eebbda2cd1a8 100644 --- a/ui/views/controls/editable_combobox/editable_combobox.h +++ b/ui/views/controls/editable_combobox/editable_combobox.h @@ -18,6 +18,7 @@ #include "ui/base/ui_base_types.h" #include "ui/views/controls/textfield/textfield_controller.h" #include "ui/views/layout/animating_layout_manager.h" +#include "ui/views/layout/box_layout_view.h" #include "ui/views/style/typography.h" #include "ui/views/view.h" #include "ui/views/view_observer.h" @@ -40,6 +41,7 @@ class EditableComboboxMenuModel; class EditableComboboxPreTargetHandler; class MenuRunner; class Textfield; +class ToggleImageButton; namespace test { class InteractionTestUtilSimulatorViews; @@ -54,9 +56,13 @@ class VIEWS_EXPORT EditableCombobox public: METADATA_HEADER(EditableCombobox); - enum class Type { - kRegular, - kPassword, + // A strategy that can be used to customize the display of the drop-down menu. + // It is only intended to be used by classes that extend `EditableCombobox`. + class MenuDecorationStrategy { + public: + virtual ~MenuDecorationStrategy() = default; + + virtual std::u16string DecorateItemText(std::u16string text) const; }; static constexpr int kDefaultTextContext = style::CONTEXT_BUTTON; @@ -70,14 +76,12 @@ class VIEWS_EXPORT EditableCombobox // completions of the current textfield content. // |show_on_empty|: Whether to show the drop-down list when there is no // textfield content. - // |type|: The EditableCombobox type. // |text_context| and |text_style|: Together these indicate the font to use. // |display_arrow|: Whether to display an arrow in the combobox to indicate // that there is a drop-down list. explicit EditableCombobox(std::unique_ptr combobox_model, bool filter_on_edit = false, bool show_on_empty = true, - Type type = Type::kRegular, int text_context = kDefaultTextContext, int text_style = kDefaultTextStyle, bool display_arrow = true); @@ -105,12 +109,30 @@ class VIEWS_EXPORT EditableCombobox // is a label associated with this combobox. void SetAssociatedLabel(View* labelling_view); - // For Type::kPassword, sets whether the textfield and - // drop-down menu will reveal their current content. - void RevealPasswords(bool revealed); + protected: + // Sets the menu decoration strategy. Setting it triggers an update to the + // menu. + void SetMenuDecorationStrategy( + std::unique_ptr strategy); + + // Forces an update of the drop-down menu. + void UpdateMenu(); + + // Adds `view` to the set of controls. The ordering is such that views are + // added to the front (i.e. to the left in LTR set-ups). + template + T* AddControlElement(std::unique_ptr view) { + T* raw_view = + control_elements_container_->AddChildViewAt(std::move(view), 0); + UpdateTextfieldInsets(); + return raw_view; + } + + Textfield& GetTextfield() { return *textfield_; } private: friend class EditableComboboxTest; + friend class EditablePasswordComboboxTest; friend class test::InteractionTestUtilSimulatorViews; class EditableComboboxMenuModel; class EditableComboboxPreTargetHandler; @@ -129,10 +151,18 @@ class VIEWS_EXPORT EditableCombobox // Shows the drop-down menu. void ShowDropDownMenu(ui::MenuSourceType source_type = ui::MENU_SOURCE_NONE); + // Recalculates the extra insets of the textfield based on the size of the + // controls container. + void UpdateTextfieldInsets(); + // These are for unit tests to get data from private implementation classes. const ui::MenuModel* GetMenuModelForTesting() const; std::u16string GetItemTextForTesting(size_t index) const; + // Returns the underlying combobox model. Used only by + // `ui::test::InteractionTestUtil`. + const ui::ComboboxModel* GetComboboxModel() const; + // Overridden from View: void Layout() override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override; @@ -155,8 +185,9 @@ class VIEWS_EXPORT EditableCombobox bool is_animating) override; raw_ptr textfield_; + raw_ptr control_elements_container_ = nullptr; raw_ptr