Skip to content

Commit

Permalink
Create BraveActionsContainer and add Shields extension button to it.
Browse files Browse the repository at this point in the history
Fix brave/brave-browser#668
Intention is for Shields 'extension' to feel built-in.
BraveActionsContainer hooks in to extension system in order to display and update the button icon when necessary.
Displays a custom badge which is sized and positioned specifically for the Shields count. Uses dynamic font sizing to maintain a target height and maximum width.
Removes the Shields extension button from main extensions area (ToolbarActionsBar / BrowserActionsContainer).
Disable context menu for shields extension button in brave actions area since there are no valid actions.
Patches the content of a method at LocationBarView::Layout since it could be more harmful to copy that functionality to our subclass because it is complex and somewhat functional logic. Attempts to minimize the patching necessary by passing in a new argument to the method.
  • Loading branch information
petemill committed Aug 30, 2018
1 parent baecd32 commit f352ef9
Show file tree
Hide file tree
Showing 19 changed files with 909 additions and 6 deletions.
15 changes: 15 additions & 0 deletions browser/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ source_set("ui") {
"brave_browser_content_setting_bubble_model_delegate.h",
"brave_pages.cc",
"brave_pages.h",
"brave_actions/shields_action_view_controller.cc",
"brave_actions/shields_action_view_controller.h",
"brave_actions/brave_action_icon_with_badge_image_source.cc",
"brave_actions/brave_action_icon_with_badge_image_source.h",
"content_settings/brave_autoplay_blocked_image_model.cc",
"content_settings/brave_autoplay_blocked_image_model.h",
"content_settings/brave_autoplay_content_setting_bubble_model.cc",
Expand All @@ -25,10 +29,16 @@ source_set("ui") {
"location_bar/brave_location_bar.h",
"toolbar/brave_app_menu_model.cc",
"toolbar/brave_app_menu_model.h",
"toolbar/brave_toolbar_actions_model.cc",
"toolbar/brave_toolbar_actions_model.h",
"views/brave_actions/brave_actions_container.cc",
"views/brave_actions/brave_actions_container.h",
"views/frame/brave_browser_view.cc",
"views/frame/brave_browser_view.h",
"views/importer/brave_import_lock_dialog_view.cc",
"views/importer/brave_import_lock_dialog_view.h",
"views/location_bar/brave_location_bar_view.cc",
"views/location_bar/brave_location_bar_view.h",
"views/toolbar/bookmark_button.cc",
"views/toolbar/bookmark_button.h",
"views/toolbar/brave_toolbar_view.cc",
Expand Down Expand Up @@ -68,12 +78,17 @@ source_set("ui") {
]

deps = [
"//base",
"//brave/app:command_ids",
"//brave/app/theme:brave_unscaled_resources",
"//brave/app/theme:brave_theme_resources",
"//brave/browser/payments",
"//brave/components/resources:brave_components_resources_grit",
"//chrome/app:command_ids",
"//skia",
"//ui/accessibility",
"//ui/base",
"//ui/gfx",
]

if (is_win && is_official_build) {
Expand Down
143 changes: 143 additions & 0 deletions browser/ui/brave_actions/brave_action_icon_with_badge_image_source.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "brave/browser/ui/brave_actions/brave_action_icon_with_badge_image_source.h"

#include "base/strings/utf_string_conversions.h"
#include "cc/paint/paint_flags.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
#include "chrome/grit/theme_resources.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/font.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/skia_paint_util.h"

void BraveActionIconWithBadgeImageSource::PaintBadge(gfx::Canvas* canvas) {
if (!badge_ || badge_->text.empty())
return;

SkColor text_color = SkColorGetA(badge_->text_color) == SK_AlphaTRANSPARENT
? SK_ColorWHITE
: badge_->text_color;

SkColor background_color = SkColorSetA(badge_->background_color, SK_AlphaOPAQUE);

// Always use same height to avoid jumping up and down with different
// characters which will differ slightly,
// but vary the width so we cover as little of the icon as possible.
constexpr int kBadgeHeight = 12;
constexpr int kBadgeMaxWidth = 14;
constexpr int kVPadding = 1;
const int kTextHeightTarget = kBadgeHeight - (kVPadding * 2);
int h_padding = 2;
int text_max_width = kBadgeMaxWidth - (h_padding * 2);

ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
gfx::FontList base_font = rb->GetFontList(ui::ResourceBundle::BaseFont)
.DeriveWithHeightUpperBound(kTextHeightTarget);
base::string16 utf16_text = base::UTF8ToUTF16(badge_->text);

// Calculate best font size to fit maximum Width and constant Height
int text_height = 0;
int text_width = 0;
gfx::Canvas::SizeStringInt(utf16_text, base_font, &text_width,
&text_height,
0, gfx::Canvas::NO_ELLIPSIS);
// Leaving extremely verbose log lines commented in case we want to change
// any sizes in this algorithm, these logs are helpful.
// LOG(ERROR) << "BraveAction badge text size initial, "
// << "w:"
// << text_width
// << " h:"
// << text_height;
if (text_width > text_max_width) {
// Too wide
// Reduce the padding
h_padding -= 1;
text_max_width += 2; // 2 * padding delta
// If still cannot squeeze it in, reduce font size
if (text_width > text_max_width) {
// Reduce font size until we find the first one that fits within the width
// TODO: Consider adding minimum font-size and adjusting
// |max_decrement_attempts| accordingly
int max_decrement_attempts = base_font.GetFontSize() - 1;
for (int i = 0; i < max_decrement_attempts; ++i) {
base_font =
base_font.Derive(-1, 0, gfx::Font::Weight::NORMAL);
gfx::Canvas::SizeStringInt(utf16_text, base_font, &text_width, &text_height, 0,
gfx::Canvas::NO_ELLIPSIS);
// LOG(ERROR) << "reducing to font size - w:" << text_width << " h:" << text_height;
if (text_width <= text_max_width)
break;
}
}
} else if (text_height < kTextHeightTarget) {
// Narrow enough, but could grow taller
// Increase font size until text fills height and is not too wide
// LOG(ERROR) << "can increase height";
constexpr int kMaxIncrementAttempts = 5;
for (size_t i = 0; i < kMaxIncrementAttempts; ++i) {
int w = 0;
int h = 0;
gfx::FontList bigger_font =
base_font.Derive(1, 0, gfx::Font::Weight::NORMAL);
gfx::Canvas::SizeStringInt(utf16_text, bigger_font, &w, &h, 0,
gfx::Canvas::NO_ELLIPSIS);
if (h > kTextHeightTarget || w > text_max_width)
break;
base_font = bigger_font;
text_width = w;
text_height = h;
// LOG(ERROR) << "increasing to font size - w:"
// << text_width
// << " h:" << text_height;
}
}

// Calculate badge size. It is clamped to a min width just because it looks
// silly if it is too skinny.
int badge_width = text_width + h_padding * 2;
// Has to at least be as wide as it is tall, otherwise it looks weird
badge_width = std::max(kBadgeHeight, badge_width);

const gfx::Rect icon_area = GetIconAreaRect();
// Force the pixel width of badge to be either odd (if the icon width is odd)
// or even otherwise. If there is a mismatch you get http://crbug.com/26400.
if (icon_area.width() != 0 && (badge_width % 2 != icon_area.width() % 2))
badge_width += 1;

// Calculate the badge background rect. It is usually right-aligned, but it
// can also be center-aligned if it is large.
const int badge_offset_x = icon_area.width() - badge_width;
const int badge_offset_y = 0;
gfx::Rect rect(icon_area.x() + badge_offset_x, icon_area.y() + badge_offset_y,
badge_width, kBadgeHeight);
cc::PaintFlags rect_flags;
rect_flags.setStyle(cc::PaintFlags::kFill_Style);
rect_flags.setAntiAlias(true);
rect_flags.setColor(background_color);

// Paint the backdrop.
constexpr int kOuterCornerRadius = 5;
canvas->DrawRoundRect(rect, kOuterCornerRadius, rect_flags);

// Paint the text.
const int kTextExtraVerticalPadding = (kTextHeightTarget - text_height) / 2;
const int kVerticalPadding = kVPadding + kTextExtraVerticalPadding;
// l, t, r, b
rect.Inset(0, kVerticalPadding, 0, kVerticalPadding);
// Draw string with ellipsis if it does not fit
canvas->DrawStringRectWithFlags(utf16_text, base_font, text_color, rect,
gfx::Canvas::TEXT_ALIGN_CENTER);
}

gfx::Rect BraveActionIconWithBadgeImageSource::GetIconAreaRect() const {
return gfx::Rect(size());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_BROWSER_UI_BRAVE_ACTIONS_BRAVE_ACTION_ICON_WITH_BADGE_IMAGE_SOURCE_H_
#define BRAVE_BROWSER_UI_BRAVE_ACTIONS_BRAVE_ACTION_ICON_WITH_BADGE_IMAGE_SOURCE_H_

#include "chrome/browser/ui/extensions/icon_with_badge_image_source.h"

namespace gfx {
class Canvas;
class Rect;
}

// The purpose of this subclass is to:
// - Paint the BraveAction badge in a custom location and with a different size
// to regular BrowserAction extensions.
class BraveActionIconWithBadgeImageSource : public IconWithBadgeImageSource {
public:
using IconWithBadgeImageSource::IconWithBadgeImageSource;
private:
void PaintBadge(gfx::Canvas* canvas) override;
gfx::Rect GetIconAreaRect() const override;
DISALLOW_COPY_AND_ASSIGN(BraveActionIconWithBadgeImageSource);
};

#endif
64 changes: 64 additions & 0 deletions browser/ui/brave_actions/shields_action_view_controller.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "brave/browser/ui/brave_actions/shields_action_view_controller.h"

#include "brave/browser/ui/brave_actions/brave_action_icon_with_badge_image_source.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/scoped_canvas.h"

void ShieldsActionViewController::HideActivePopup() {
// Usually, for an extension this should call the main extensions
// toolbar_actions_bar_->HideActivePopup(), but we don't have a reference
// to that, and it doesn't seem neccessary, whether the extension is opened
// via mouse or keyboard (if a `commands` extension property is present)
}

bool ShieldsActionViewController::DisabledClickOpensMenu() const {
// disabled is a per-tab state
return false;
}

ui::MenuModel* ShieldsActionViewController::GetContextMenu() {
// no context menu for shields button
return nullptr;
}

gfx::Image ShieldsActionViewController::GetIcon(content::WebContents* web_contents, const gfx::Size& size) {
return gfx::Image(gfx::ImageSkia(GetIconImageSource(web_contents, size), size));
}

std::unique_ptr<BraveActionIconWithBadgeImageSource> ShieldsActionViewController::GetIconImageSource(
content::WebContents* web_contents, const gfx::Size& size) {
int tab_id = SessionTabHelper::IdForTab(web_contents).id();
// generate icon
std::unique_ptr<BraveActionIconWithBadgeImageSource> image_source(
new BraveActionIconWithBadgeImageSource(size));
image_source->SetIcon(icon_factory_.GetIcon(tab_id));
// set text
std::unique_ptr<IconWithBadgeImageSource::Badge> badge;
std::string badge_text = extension_action()->GetBadgeText(tab_id);
if (!badge_text.empty()) {
badge.reset(new IconWithBadgeImageSource::Badge(
badge_text,
extension_action()->GetBadgeTextColor(tab_id),
extension_action()->GetBadgeBackgroundColor(tab_id)));
}
image_source->SetBadge(std::move(badge));
// state
// If the extension doesn't want to run on the active web contents, we
// grayscale it to indicate that.
bool is_enabled_for_tab = extension_action()->GetIsVisible(tab_id);
image_source->set_grayscale(!is_enabled_for_tab);
image_source->set_paint_page_action_decoration(false);
return image_source;
}
36 changes: 36 additions & 0 deletions browser/ui/brave_actions/shields_action_view_controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_BROWSER_UI_BRAVE_ACTIONS_SHIELDS_ACTION_VIEW_CONTROLLER_H_
#define BRAVE_BROWSER_UI_BRAVE_ACTIONS_SHIELDS_ACTION_VIEW_CONTROLLER_H_

#include "chrome/browser/ui/extensions/extension_action_view_controller.h"

class BraveActionIconWithBadgeImageSource;

namespace ui {
class MenuModel;
}

// The purposes of this subclass are to:
// - Overcome the DCHECK in HideActivePopup since a toolbar will not be provided
// - Use our custom class for painting the badge differently compared to
// user-installed extensions
// - Remove the context menu from the button since we do not allow uninstall
class ShieldsActionViewController : public ExtensionActionViewController {
public:
using ExtensionActionViewController::ExtensionActionViewController;
void HideActivePopup() override;
gfx::Image GetIcon(content::WebContents* web_contents, const gfx::Size& size) override;
bool DisabledClickOpensMenu() const override;
ui::MenuModel* GetContextMenu() override;
private:
// Returns the image source for the icon.
std::unique_ptr<BraveActionIconWithBadgeImageSource> GetIconImageSource(
content::WebContents* web_contents,
const gfx::Size& size);
DISALLOW_COPY_AND_ASSIGN(ShieldsActionViewController);
};

#endif
18 changes: 18 additions & 0 deletions browser/ui/toolbar/brave_toolbar_actions_model.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "brave/browser/ui/toolbar/brave_toolbar_actions_model.h"

#include "brave/common/extensions/extension_constants.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "extensions/common/extension.h"

bool BraveToolbarActionsModel::ShouldAddExtension(const extensions::Extension* extension) {
// Don't show the Brave 'extension' in the ToolbarActions extensions area. It
// will instead be shown in the BraveActions area.
if (extension->id() == brave_extension_id) {
return false;
}
return ToolbarActionsModel::ShouldAddExtension(extension);
}
23 changes: 23 additions & 0 deletions browser/ui/toolbar/brave_toolbar_actions_model.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef BRAVE_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_MODEL_H_
#define BRAVE_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_MODEL_H_

#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"

#include "extensions/common/extension.h"

// The purposes of this subclass are to:
// - Hide the Brave 'extension' item from the |ToolbarActionsBar|, since it is
// displayed in the |BraveActionsContainer|
class BraveToolbarActionsModel : public ToolbarActionsModel {
public:
using ToolbarActionsModel::ToolbarActionsModel;
bool ShouldAddExtension(const extensions::Extension* extension) override;
private:
DISALLOW_COPY_AND_ASSIGN(BraveToolbarActionsModel);
};

#endif
Loading

0 comments on commit f352ef9

Please sign in to comment.