Index: chrome/browser/ui/views/intent_picker_bubble_view.cc |
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view.cc b/chrome/browser/ui/views/intent_picker_bubble_view.cc |
index 18f8127a957d45e993a11c3fb6219f79060d989c..f805497fdfcbe26a0f4d2c9f66b2797e7893ca6e 100644 |
--- a/chrome/browser/ui/views/intent_picker_bubble_view.cc |
+++ b/chrome/browser/ui/views/intent_picker_bubble_view.cc |
@@ -15,9 +15,7 @@ |
#include "content/public/browser/navigation_handle.h" |
#include "third_party/skia/include/core/SkColor.h" |
#include "ui/base/l10n/l10n_util.h" |
-#include "ui/base/material_design/material_design_controller.h" |
#include "ui/gfx/canvas.h" |
-#include "ui/views/animation/ink_drop_host_view.h" |
#include "ui/views/border.h" |
#include "ui/views/controls/button/image_button.h" |
#include "ui/views/controls/scroll_view.h" |
@@ -31,81 +29,46 @@ namespace { |
// Using |kMaxAppResults| as a measure of how many apps we want to show. |
constexpr size_t kMaxAppResults = arc::ArcNavigationThrottle::kMaxAppResults; |
// Main components sizes |
-constexpr int kDialogDelegateInsets = 16; |
constexpr int kRowHeight = 40; |
constexpr int kMaxWidth = 320; |
+constexpr int kHeaderHeight = 60; |
+constexpr int kFooterHeight = 68; |
+// Inter components padding |
+constexpr int kButtonSeparation = 8; |
+constexpr int kLabelImageSeparation = 12; |
// UI position wrt the Top Container |
constexpr int kTopContainerMerge = 3; |
+constexpr size_t kAppTagNoneSelected = static_cast<size_t>(-1); |
-constexpr char kInvalidPackageName[] = ""; |
+// Arbitrary negative values to use as tags on the |always_button_| and |
+// |just_once_button_|. These are negative to differentiate from the app's tags |
+// which are always >= 0. |
+enum class Option : int { ALWAYS = -2, JUST_ONCE }; |
} // namespace |
-// IntentPickerLabelButton |
- |
-// A button that represents a candidate intent handler. |
-class IntentPickerLabelButton : public views::LabelButton { |
- public: |
- IntentPickerLabelButton(views::ButtonListener* listener, |
- gfx::Image* icon, |
- const std::string& package_name, |
- const std::string& activity_name) |
- : LabelButton(listener, |
- base::UTF8ToUTF16(base::StringPiece(activity_name))), |
- package_name_(package_name) { |
- SetHorizontalAlignment(gfx::ALIGN_LEFT); |
- SetFocusBehavior(View::FocusBehavior::ALWAYS); |
- SetMinSize(gfx::Size(kMaxWidth, kRowHeight)); |
- SetInkDropMode(InkDropMode::ON); |
- if (!icon->IsEmpty()) |
- SetImage(views::ImageButton::STATE_NORMAL, *icon->ToImageSkia()); |
- SetBorder(views::Border::CreateEmptyBorder(10, 16, 10, 0)); |
- } |
- |
- SkColor GetInkDropBaseColor() const override { return SK_ColorBLACK; } |
- |
- void MarkAsUnselected(const ui::Event* event) { |
- AnimateInkDrop(views::InkDropState::DEACTIVATED, |
- ui::LocatedEvent::FromIfValid(event)); |
- } |
- |
- void MarkAsSelected(const ui::Event* event) { |
- AnimateInkDrop(views::InkDropState::ACTIVATED, |
- ui::LocatedEvent::FromIfValid(event)); |
- } |
- |
- views::InkDropState GetTargetInkDropState() { |
- return ink_drop()->GetTargetInkDropState(); |
- } |
- |
- private: |
- std::string package_name_; |
- |
- DISALLOW_COPY_AND_ASSIGN(IntentPickerLabelButton); |
-}; |
- |
// static |
void IntentPickerBubbleView::ShowBubble( |
content::WebContents* web_contents, |
- const std::vector<AppInfo>& app_info, |
- const IntentPickerResponse& intent_picker_cb) { |
+ const std::vector<NameAndIcon>& app_info, |
+ const ThrottleCallback& throttle_cb) { |
Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
- if (!browser || !BrowserView::GetBrowserViewForBrowser(browser)) { |
- intent_picker_cb.Run(kInvalidPackageName, |
- arc::ArcNavigationThrottle::CloseReason::ERROR); |
+ if (!browser) { |
+ throttle_cb.Run(kAppTagNoneSelected, |
+ arc::ArcNavigationThrottle::CloseReason::ERROR); |
return; |
} |
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); |
+ if (!browser_view) { |
+ throttle_cb.Run(kAppTagNoneSelected, |
+ arc::ArcNavigationThrottle::CloseReason::ERROR); |
+ return; |
+ } |
+ |
IntentPickerBubbleView* delegate = |
- new IntentPickerBubbleView(app_info, intent_picker_cb, web_contents); |
- // TODO(djacobo): Remove the left and right insets when |
- // http://crbug.com/656662 gets fixed. |
- // Add a 1-pixel extra boundary left and right when using secondary UI. |
- if (ui::MaterialDesignController::IsSecondaryUiMaterial()) |
- delegate->set_margins(gfx::Insets(16, 1, 0, 1)); |
- else |
- delegate->set_margins(gfx::Insets(16, 0, 0, 0)); |
+ new IntentPickerBubbleView(app_info, throttle_cb, web_contents); |
+ delegate->set_margins(gfx::Insets()); |
delegate->set_parent_window(browser_view->GetNativeWindow()); |
views::Widget* widget = |
views::BubbleDialogDelegateView::CreateBubble(delegate); |
@@ -122,65 +85,104 @@ void IntentPickerBubbleView::ShowBubble( |
browser_view->GetTopContainerBoundsInScreen().width(), |
browser_view->GetTopContainerBoundsInScreen().height() - |
kTopContainerMerge)); |
- delegate->GetDialogClientView()->set_button_row_insets( |
- gfx::Insets(kDialogDelegateInsets)); |
- delegate->GetDialogClientView()->Layout(); |
- delegate->GetIntentPickerLabelButtonAt(0)->MarkAsSelected(nullptr); |
widget->Show(); |
} |
// static |
std::unique_ptr<IntentPickerBubbleView> |
IntentPickerBubbleView::CreateBubbleView( |
- const std::vector<AppInfo>& app_info, |
- const IntentPickerResponse& intent_picker_cb, |
+ const std::vector<NameAndIcon>& app_info, |
+ const ThrottleCallback& throttle_cb, |
content::WebContents* web_contents) { |
std::unique_ptr<IntentPickerBubbleView> bubble( |
- new IntentPickerBubbleView(app_info, intent_picker_cb, web_contents)); |
+ new IntentPickerBubbleView(app_info, throttle_cb, web_contents)); |
bubble->Init(); |
return bubble; |
} |
-bool IntentPickerBubbleView::Accept() { |
- RunCallback(app_info_[selected_app_tag_].package_name, |
- arc::ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED); |
- return true; |
-} |
- |
-bool IntentPickerBubbleView::Cancel() { |
- RunCallback(app_info_[selected_app_tag_].package_name, |
- arc::ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED); |
- return true; |
-} |
- |
-bool IntentPickerBubbleView::Close() { |
- // Whenever closing the bubble without pressing |Just once| or |Always| we |
- // need to report back that the user didn't select anything. |
- RunCallback(kInvalidPackageName, |
- arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED); |
- return true; |
-} |
- |
void IntentPickerBubbleView::Init() { |
+ SkColor button_text_color = SkColorSetRGB(0x42, 0x85, 0xf4); |
+ |
views::BoxLayout* general_layout = |
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); |
SetLayoutManager(general_layout); |
+ // Create a view for the upper part of the UI, managed by a GridLayout to |
+ // allow precise padding. |
+ View* header = new View(); |
+ views::GridLayout* header_layout = new views::GridLayout(header); |
+ header->SetLayoutManager(header_layout); |
+ views::Label* open_with = new views::Label( |
+ l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH)); |
+ open_with->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
+ open_with->SetFontList(gfx::FontList("Roboto-medium, 15px")); |
+ |
+ views::ColumnSet* column_set = header_layout->AddColumnSet(0); |
+ column_set->AddPaddingColumn(0, 16); |
+ column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1, |
+ views::GridLayout::FIXED, kMaxWidth, kMaxWidth); |
+ column_set->AddPaddingColumn(0, 16); |
+ // Tell the GridLayout where to start, then proceed to place the views. |
+ header_layout->AddPaddingRow(0.0, 21); |
+ header_layout->StartRow(0, 0); |
+ header_layout->AddView(open_with); |
+ header_layout->AddPaddingRow(0.0, 24); |
+ |
+ always_button_ = new views::LabelButton( |
+ this, l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_ALWAYS)); |
+ always_button_->SetFocusBehavior(View::FocusBehavior::ALWAYS); |
+ always_button_->SetFontList(gfx::FontList("Roboto-medium, 13px")); |
+ always_button_->set_tag(static_cast<int>(Option::ALWAYS)); |
+ always_button_->SetMinSize(gfx::Size(80, 32)); |
+ always_button_->SetTextColor(views::Button::STATE_DISABLED, SK_ColorGRAY); |
+ always_button_->SetTextColor(views::Button::STATE_NORMAL, button_text_color); |
+ always_button_->SetTextColor(views::Button::STATE_HOVERED, button_text_color); |
+ always_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
+ always_button_->SetState(views::Button::STATE_DISABLED); |
+ always_button_->SetBorder(views::Border::CreateEmptyBorder(gfx::Insets(16))); |
+ |
+ just_once_button_ = new views::LabelButton( |
+ this, l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_JUST_ONCE)); |
+ just_once_button_->SetFocusBehavior(View::FocusBehavior::ALWAYS); |
+ just_once_button_->SetFontList(gfx::FontList("Roboto-medium, 13px")); |
+ just_once_button_->set_tag(static_cast<int>(Option::JUST_ONCE)); |
+ just_once_button_->SetMinSize(gfx::Size(80, 32)); |
+ just_once_button_->SetState(views::Button::STATE_DISABLED); |
+ just_once_button_->SetTextColor(views::Button::STATE_DISABLED, SK_ColorGRAY); |
+ just_once_button_->SetTextColor(views::Button::STATE_NORMAL, |
+ button_text_color); |
+ just_once_button_->SetTextColor(views::Button::STATE_HOVERED, |
+ button_text_color); |
+ just_once_button_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
+ just_once_button_->SetBorder( |
+ views::Border::CreateEmptyBorder(gfx::Insets(16))); |
+ |
+ header_layout->StartRow(0, 0); |
+ AddChildView(header); |
+ |
// Creates a view to hold the views for each app. |
views::View* scrollable_view = new views::View(); |
views::BoxLayout* scrollable_layout = |
new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); |
scrollable_view->SetLayoutManager(scrollable_layout); |
for (size_t i = 0; i < app_info_.size(); ++i) { |
- IntentPickerLabelButton* app_button = new IntentPickerLabelButton( |
- this, &app_info_[i].icon, app_info_[i].package_name, |
- app_info_[i].activity_name); |
- app_button->set_tag(i); |
- scrollable_view->AddChildViewAt(app_button, i); |
+ views::LabelButton* tmp_label = new views::LabelButton( |
+ this, base::UTF8ToUTF16(base::StringPiece(app_info_[i].first))); |
+ tmp_label->SetFocusBehavior(View::FocusBehavior::ALWAYS); |
+ tmp_label->SetFontList(gfx::FontList("Roboto-regular, 13px")); |
+ tmp_label->SetImageLabelSpacing(kLabelImageSeparation); |
+ tmp_label->SetMinSize(gfx::Size(kMaxWidth, kRowHeight)); |
+ tmp_label->SetMaxSize(gfx::Size(kMaxWidth, kRowHeight)); |
+ tmp_label->set_tag(i); |
+ if (!app_info_[i].second.IsEmpty()) { |
+ tmp_label->SetImage(views::ImageButton::STATE_NORMAL, |
+ *app_info_[i].second.ToImageSkia()); |
+ } |
+ tmp_label->SetBorder(views::Border::CreateEmptyBorder(10, 16, 10, 0)); |
+ scrollable_view->AddChildViewAt(tmp_label, i); |
} |
scroll_view_ = new views::ScrollView(); |
- scroll_view_->EnableViewPortLayer(); |
scroll_view_->SetContents(scrollable_view); |
// Setting a customized ScrollBar which is shown only when the mouse pointer |
// is inside the ScrollView. |
@@ -195,28 +197,34 @@ void IntentPickerBubbleView::Init() { |
scroll_view_->ClipHeightTo(kRowHeight, (kMaxAppResults + 0.5) * kRowHeight); |
} |
AddChildView(scroll_view_); |
-} |
- |
-base::string16 IntentPickerBubbleView::GetWindowTitle() const { |
- return l10n_util::GetStringUTF16(IDS_INTENT_PICKER_BUBBLE_VIEW_OPEN_WITH); |
-} |
-base::string16 IntentPickerBubbleView::GetDialogButtonLabel( |
- ui::DialogButton button) const { |
- return l10n_util::GetStringUTF16(button == ui::DIALOG_BUTTON_OK |
- ? IDS_INTENT_PICKER_BUBBLE_VIEW_JUST_ONCE |
- : IDS_INTENT_PICKER_BUBBLE_VIEW_ALWAYS); |
+ // The lower part of the Picker contains only the 2 buttons |
+ // |just_once_button_| and |always_button_|. |
+ View* footer = new View(); |
+ footer->SetBorder(views::Border::CreateEmptyBorder(24, 0, 12, 12)); |
+ views::BoxLayout* buttons_layout = new views::BoxLayout( |
+ views::BoxLayout::kHorizontal, 0, 0, kButtonSeparation); |
+ footer->SetLayoutManager(buttons_layout); |
+ |
+ buttons_layout->set_main_axis_alignment( |
+ views::BoxLayout::MAIN_AXIS_ALIGNMENT_END); |
+ footer->AddChildView(just_once_button_); |
+ footer->AddChildView(always_button_); |
+ AddChildView(footer); |
} |
IntentPickerBubbleView::IntentPickerBubbleView( |
- const std::vector<AppInfo>& app_info, |
- IntentPickerResponse intent_picker_cb, |
+ const std::vector<NameAndIcon>& app_info, |
+ ThrottleCallback throttle_cb, |
content::WebContents* web_contents) |
: views::BubbleDialogDelegateView(nullptr /* anchor_view */, |
views::BubbleBorder::TOP_CENTER), |
WebContentsObserver(web_contents), |
- intent_picker_cb_(intent_picker_cb), |
- selected_app_tag_(0), |
+ was_callback_run_(false), |
+ throttle_cb_(throttle_cb), |
+ selected_app_tag_(kAppTagNoneSelected), |
+ always_button_(nullptr), |
+ just_once_button_(nullptr), |
scroll_view_(nullptr), |
app_info_(app_info) {} |
@@ -227,18 +235,47 @@ IntentPickerBubbleView::~IntentPickerBubbleView() { |
// If the widget gets closed without an app being selected we still need to use |
// the callback so the caller can Resume the navigation. |
void IntentPickerBubbleView::OnWidgetDestroying(views::Widget* widget) { |
- RunCallback(kInvalidPackageName, |
- arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED); |
+ if (!was_callback_run_) { |
+ was_callback_run_ = true; |
+ throttle_cb_.Run( |
+ kAppTagNoneSelected, |
+ arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED); |
+ } |
+} |
+ |
+int IntentPickerBubbleView::GetDialogButtons() const { |
+ return ui::DIALOG_BUTTON_NONE; |
} |
void IntentPickerBubbleView::ButtonPressed(views::Button* sender, |
const ui::Event& event) { |
- // The selected app must be a value in the range [0, app_info_.size()-1]. |
- DCHECK_LT(static_cast<size_t>(sender->tag()), app_info_.size()); |
- GetIntentPickerLabelButtonAt(selected_app_tag_)->MarkAsUnselected(&event); |
- |
- selected_app_tag_ = sender->tag(); |
- GetIntentPickerLabelButtonAt(selected_app_tag_)->MarkAsSelected(&event); |
+ switch (sender->tag()) { |
+ case static_cast<int>(Option::ALWAYS): |
+ was_callback_run_ = true; |
+ throttle_cb_.Run(selected_app_tag_, |
+ arc::ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED); |
+ GetWidget()->Close(); |
+ break; |
+ case static_cast<int>(Option::JUST_ONCE): |
+ was_callback_run_ = true; |
+ throttle_cb_.Run( |
+ selected_app_tag_, |
+ arc::ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED); |
+ GetWidget()->Close(); |
+ break; |
+ default: |
+ // The selected app must be a value in the range [0, app_info_.size()-1]. |
+ DCHECK(static_cast<size_t>(sender->tag()) < app_info_.size()); |
+ // The user cannot click on the |always_button_| or |just_once_button_| |
+ // unless an explicit app has been selected. |
+ if (selected_app_tag_ != kAppTagNoneSelected) |
+ SetLabelButtonBackgroundColor(selected_app_tag_, SK_ColorWHITE); |
+ selected_app_tag_ = sender->tag(); |
+ SetLabelButtonBackgroundColor(selected_app_tag_, |
+ SkColorSetRGB(0xeb, 0xeb, 0xeb)); |
+ always_button_->SetState(views::Button::STATE_NORMAL); |
+ just_once_button_->SetState(views::Button::STATE_NORMAL); |
+ } |
} |
gfx::Size IntentPickerBubbleView::GetPreferredSize() const { |
@@ -252,48 +289,29 @@ gfx::Size IntentPickerBubbleView::GetPreferredSize() const { |
} else { |
apps_height *= kRowHeight; |
} |
- ps.set_height(apps_height + kDialogDelegateInsets); |
+ ps.set_height(apps_height + kHeaderHeight + kFooterHeight); |
return ps; |
} |
// If the actual web_contents gets destroyed in the middle of the process we |
// should inform the caller about this error. |
void IntentPickerBubbleView::WebContentsDestroyed() { |
+ if (!was_callback_run_) { |
+ was_callback_run_ = true; |
+ throttle_cb_.Run(kAppTagNoneSelected, |
+ arc::ArcNavigationThrottle::CloseReason::ERROR); |
+ } |
GetWidget()->Close(); |
} |
-IntentPickerLabelButton* IntentPickerBubbleView::GetIntentPickerLabelButtonAt( |
- size_t index) { |
+views::LabelButton* IntentPickerBubbleView::GetLabelButtonAt(size_t index) { |
views::View* temp_contents = scroll_view_->contents(); |
- return static_cast<IntentPickerLabelButton*>(temp_contents->child_at(index)); |
-} |
- |
-void IntentPickerBubbleView::RunCallback( |
- std::string package, |
- arc::ArcNavigationThrottle::CloseReason close_reason) { |
- if (!intent_picker_cb_.is_null()) { |
- // We must ensure |intent_picker_cb_| is only Run() once, this is why we |
- // have a temporary |callback| helper, so we can set the original callback |
- // to null and still report back to whoever started the UI. |
- auto callback = intent_picker_cb_; |
- intent_picker_cb_.Reset(); |
- callback.Run(package, close_reason); |
- } |
-} |
- |
-gfx::ImageSkia IntentPickerBubbleView::GetAppImageForTesting(size_t index) { |
- return GetIntentPickerLabelButtonAt(index)->GetImage( |
- views::Button::ButtonState::STATE_NORMAL); |
-} |
- |
-views::InkDropState IntentPickerBubbleView::GetInkDropStateForTesting( |
- size_t index) { |
- return GetIntentPickerLabelButtonAt(index)->GetTargetInkDropState(); |
+ return static_cast<views::LabelButton*>(temp_contents->child_at(index)); |
} |
-void IntentPickerBubbleView::PressButtonForTesting(size_t index, |
- const ui::Event& event) { |
- views::Button* button = |
- static_cast<views::Button*>(GetIntentPickerLabelButtonAt(index)); |
- ButtonPressed(button, event); |
+void IntentPickerBubbleView::SetLabelButtonBackgroundColor(size_t index, |
+ SkColor color) { |
+ views::LabelButton* temp_lb = GetLabelButtonAt(index); |
+ temp_lb->set_background(views::Background::CreateSolidBackground(color)); |
+ temp_lb->SchedulePaint(); |
} |