Commit 406c22f3 authored by Peter Kasting's avatar Peter Kasting Committed by Commit Bot

Clean up TabbedPane.

Tab and TabStrip are only ever instantiated as MdTab and MdTabStrip.  Combine
the code for the subclasses with the parents, eliminating unreachable code.

Inline various constants that are only used once and whose names added no
real clarity.  Rewrite a few places for brevity.  Reorder definitions to
match declarations.

Bug: 934273
Change-Id: I7853a96d0929e8eeedc60f421b8426e171befec7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1809632
Auto-Submit: Peter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: Peter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#697619}
parent 0656b286
......@@ -17,8 +17,6 @@
#include "ui/base/default_style.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
......@@ -36,120 +34,41 @@
namespace views {
namespace {
// TODO(markusheintz|msw): Use NativeTheme colors.
constexpr SkColor kTabTitleColor_InactiveBorder =
SkColorSetARGB(0xFF, 0x64, 0x64, 0x64);
constexpr SkColor kTabTitleColor_InactiveHighlight = gfx::kGoogleGrey700;
constexpr SkColor kTabTitleColor_ActiveBorder = SK_ColorBLACK;
constexpr SkColor kTabTitleColor_ActiveHighlight = gfx::kGoogleBlue600;
constexpr SkColor kTabTitleColor_Hovered = SK_ColorBLACK;
constexpr SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8);
constexpr SkScalar kTabBorderThickness = 1.0f;
constexpr SkColor kTabHighlightBackgroundColor_Active =
SkColorSetARGB(0xFF, 0xE8, 0xF0, 0xFE);
constexpr SkColor kTabHighlightBackgroundColor_Focused =
SkColorSetARGB(0xFF, 0xD2, 0xE3, 0xFC);
constexpr int kTabHighlightBorderRadius = 32;
constexpr gfx::Font::Weight kHoverWeightBorder = gfx::Font::Weight::NORMAL;
constexpr gfx::Font::Weight kHoverWeightHighlight = gfx::Font::Weight::MEDIUM;
constexpr gfx::Font::Weight kActiveWeight = gfx::Font::Weight::BOLD;
constexpr gfx::Font::Weight kInactiveWeightBorder = gfx::Font::Weight::NORMAL;
constexpr gfx::Font::Weight kInactiveWeightHighlight =
gfx::Font::Weight::MEDIUM;
constexpr int kLabelFontSizeDeltaHighlight = 1;
constexpr int kHarmonyTabStripTabHeight = 32;
} // namespace
// A subclass of Tab that implements the Harmony visual styling.
class MdTab : public Tab {
public:
MdTab(TabbedPane* tabbed_pane, const base::string16& title, View* contents);
~MdTab() override;
// Overridden from Tab:
void OnStateChanged() override;
// Overridden from View:
gfx::Size CalculatePreferredSize() const override;
void OnFocus() override;
void OnBlur() override;
private:
DISALLOW_COPY_AND_ASSIGN(MdTab);
};
// A subclass of TabStrip that implements the Harmony visual styling. This
// class uses a BoxLayout to position tabs.
class MdTabStrip : public TabStrip, public gfx::AnimationDelegate {
public:
MdTabStrip(TabbedPane::Orientation orientation,
TabbedPane::TabStripStyle style);
~MdTabStrip() override;
// Overridden from TabStrip:
void OnSelectedTabChanged(Tab* from_tab, Tab* to_tab) override;
// Overridden from View:
void OnPaintBorder(gfx::Canvas* canvas) override;
// Overridden from AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override;
private:
// Animations for expanding and contracting the selection bar. When changing
// selections, the selection bar first grows to encompass both the old and new
// selections, then shrinks to encompass only the new selection. The rates of
// expansion and contraction each follow the cubic bezier curves used in
// gfx::Tween; see MdTabStrip::OnPaintBorder for details.
std::unique_ptr<gfx::LinearAnimation> expand_animation_;
std::unique_ptr<gfx::LinearAnimation> contract_animation_;
// The x-coordinate ranges of the old selection and the new selection.
gfx::Range animating_from_;
gfx::Range animating_to_;
DISALLOW_COPY_AND_ASSIGN(MdTabStrip);
};
Tab::Tab(TabbedPane* tabbed_pane, const base::string16& title, View* contents)
: tabbed_pane_(tabbed_pane),
state_(State::kActive),
contents_(contents) {
// Calculate the size while the font list is bold.
auto title_label = std::make_unique<Label>(title, style::CONTEXT_LABEL,
style::STYLE_TAB_ACTIVE);
title_ = title_label.get();
preferred_title_size_ = title_label->GetPreferredSize();
const bool is_vertical =
tabbed_pane_->GetOrientation() == TabbedPane::Orientation::kVertical;
const bool is_highlight_style =
tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight;
preferred_title_width_ = title_label->GetPreferredSize().width();
if (is_vertical)
if (tabbed_pane_->GetOrientation() == TabbedPane::Orientation::kVertical) {
title_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
constexpr auto kTabPadding = gfx::Insets(5, 10);
constexpr auto kTabPaddingVerticalHighlight = gfx::Insets(8, 32, 8, 0);
SetBorder(CreateEmptyBorder((is_highlight_style && is_vertical)
? kTabPaddingVerticalHighlight
: kTabPadding));
const bool is_highlight_style =
tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight;
constexpr auto kTabPadding = gfx::Insets(5, 10);
constexpr auto kTabPaddingHighlight = gfx::Insets(8, 32, 8, 0);
SetBorder(CreateEmptyBorder(is_highlight_style ? kTabPaddingHighlight
: kTabPadding));
} else {
constexpr auto kBorderThickness = gfx::Insets(2);
SetBorder(CreateEmptyBorder(kBorderThickness));
}
SetLayoutManager(std::make_unique<FillLayout>());
SetState(State::kInactive);
// Calculate the size while the font list is normal and set the max size.
preferred_title_size_.SetToMax(title_label->GetPreferredSize());
preferred_title_width_ =
std::max(preferred_title_width_, title_label->GetPreferredSize().width());
AddChildView(std::move(title_label));
// Use leaf so that name is spoken by screen reader without exposing the
// children.
GetViewAccessibility().OverrideIsLeaf(true);
OnStateChanged();
}
Tab::~Tab() = default;
......@@ -176,49 +95,15 @@ void Tab::SetTitleText(const base::string16& text) {
// and reserve that amount of space.
State old_state = state_;
SetState(State::kActive);
preferred_title_size_ = GetPreferredSize();
preferred_title_width_ = GetPreferredSize().width();
SetState(State::kInactive);
preferred_title_size_.SetToMax(GetPreferredSize());
preferred_title_width_ =
std::max(preferred_title_width_, GetPreferredSize().width());
SetState(old_state);
InvalidateLayout();
}
void Tab::OnStateChanged() {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
const bool is_highlight_mode =
tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight;
const int font_size_delta = is_highlight_mode ? kLabelFontSizeDeltaHighlight
: ui::kLabelFontSizeDelta;
switch (state_) {
case State::kInactive:
// Notify assistive tools to update this tab's selected status.
// The way Chrome OS accessibility is implemented right now, firing almost
// any event will work, we just need to trigger its state to be refreshed.
NotifyAccessibilityEvent(ax::mojom::Event::kCheckedStateChanged, true);
title_->SetEnabledColor(is_highlight_mode
? kTabTitleColor_InactiveHighlight
: kTabTitleColor_InactiveBorder);
title_->SetFontList(
rb.GetFontListWithDelta(font_size_delta, gfx::Font::NORMAL,
is_highlight_mode ? kInactiveWeightHighlight
: kInactiveWeightBorder));
break;
case State::kActive:
title_->SetEnabledColor(is_highlight_mode ? kTabTitleColor_ActiveHighlight
: kTabTitleColor_ActiveBorder);
title_->SetFontList(rb.GetFontListWithDelta(
font_size_delta, gfx::Font::NORMAL, kActiveWeight));
break;
case State::kHovered:
title_->SetEnabledColor(kTabTitleColor_Hovered);
title_->SetFontList(rb.GetFontListWithDelta(
font_size_delta, gfx::Font::NORMAL,
is_highlight_mode ? kHoverWeightHighlight : kHoverWeightBorder));
break;
}
}
bool Tab::OnMousePressed(const ui::MouseEvent& event) {
if (GetEnabled() && event.IsOnlyLeftMouseButton())
tabbed_pane_->SelectTab(this);
......@@ -236,7 +121,6 @@ void Tab::OnMouseExited(const ui::MouseEvent& event) {
void Tab::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_TAP_DOWN:
// Fallthrough.
case ui::ET_GESTURE_TAP:
// SelectTab also sets the right tab color.
tabbed_pane_->SelectTab(this);
......@@ -251,56 +135,16 @@ void Tab::OnGestureEvent(ui::GestureEvent* event) {
}
gfx::Size Tab::CalculatePreferredSize() const {
gfx::Size size(preferred_title_size_);
size.Enlarge(GetInsets().width(), GetInsets().height());
int width = preferred_title_width_ + GetInsets().width();
if (tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight &&
tabbed_pane_->GetOrientation() == TabbedPane::Orientation::kVertical) {
constexpr gfx::Size kTabHighlightPreferredSize = gfx::Size(192, 32);
size.SetToMax(kTabHighlightPreferredSize);
}
return size;
}
void Tab::SetState(State state) {
if (state == state_)
return;
state_ = state;
OnStateChanged();
SchedulePaint();
}
void Tab::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
if (!selected())
return;
if (tabbed_pane_->GetOrientation() != TabbedPane::Orientation::kVertical ||
tabbed_pane_->GetStyle() != TabbedPane::TabStripStyle::kHighlight) {
return;
}
SkScalar radius = SkIntToScalar(kTabHighlightBorderRadius);
SkPath path;
gfx::Rect bounds(size());
if (base::i18n::IsRTL()) {
const SkScalar kRadius[8] = {radius, radius, 0, 0, 0, 0, radius, radius};
path.addRoundRect(gfx::RectToSkRect(bounds), kRadius);
} else {
const SkScalar kRadius[8] = {0, 0, radius, radius, radius, radius, 0, 0};
path.addRoundRect(gfx::RectToSkRect(bounds), kRadius);
}
cc::PaintFlags fill_flags;
fill_flags.setAntiAlias(true);
if (HasFocus())
fill_flags.setColor(kTabHighlightBackgroundColor_Focused);
else
fill_flags.setColor(kTabHighlightBackgroundColor_Active);
canvas->DrawPath(path, fill_flags);
tabbed_pane_->GetOrientation() == TabbedPane::Orientation::kVertical)
width = std::max(width, 192);
return gfx::Size(width, 32);
}
void Tab::GetAccessibleNodeData(ui::AXNodeData* data) {
data->role = ax::mojom::Role::kTab;
data->SetName(title()->GetText());
data->SetName(title_->GetText());
data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected());
}
......@@ -314,7 +158,13 @@ bool Tab::HandleAccessibleAction(const ui::AXActionData& action_data) {
}
void Tab::OnFocus() {
OnStateChanged();
// Do not draw focus ring in kHighlight mode.
if (tabbed_pane_->GetStyle() != TabbedPane::TabStripStyle::kHighlight) {
SetBorder(CreateSolidBorder(
GetInsets().top(), GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FocusedBorderColor)));
}
// When the tab gains focus, send an accessibility event indicating that the
// contents are focused. When the tab loses focus, whichever new View ends up
// with focus will send an ax::mojom::Event::kFocus of its own, so there's no
......@@ -325,16 +175,16 @@ void Tab::OnFocus() {
}
void Tab::OnBlur() {
OnStateChanged();
// Do not draw focus ring in kHighlight mode.
if (tabbed_pane_->GetStyle() != TabbedPane::TabStripStyle::kHighlight)
SetBorder(CreateEmptyBorder(GetInsets()));
SchedulePaint();
}
bool Tab::OnKeyPressed(const ui::KeyEvent& event) {
ui::KeyboardCode key = event.key_code();
const bool is_horizontal =
tabbed_pane_->GetOrientation() == TabbedPane::Orientation::kHorizontal;
// Use left and right arrows to navigate tabs in horizontal orientation.
if (is_horizontal) {
const ui::KeyboardCode key = event.key_code();
if (tabbed_pane_->GetOrientation() == TabbedPane::Orientation::kHorizontal) {
// Use left and right arrows to navigate tabs in horizontal orientation.
return (key == ui::VKEY_LEFT || key == ui::VKEY_RIGHT) &&
tabbed_pane_->MoveSelectionBy(key == ui::VKEY_RIGHT ? 1 : -1);
}
......@@ -343,38 +193,48 @@ bool Tab::OnKeyPressed(const ui::KeyEvent& event) {
tabbed_pane_->MoveSelectionBy(key == ui::VKEY_DOWN ? 1 : -1);
}
BEGIN_METADATA(Tab)
METADATA_PARENT_CLASS(View)
END_METADATA()
MdTab::MdTab(TabbedPane* tabbed_pane,
const base::string16& title,
View* contents)
: Tab(tabbed_pane, title, contents) {
if (tabbed_pane->GetOrientation() == TabbedPane::Orientation::kHorizontal) {
constexpr auto kBorderThickness = gfx::Insets(2);
SetBorder(CreateEmptyBorder(kBorderThickness));
}
void Tab::SetState(State state) {
if (state == state_)
return;
state_ = state;
OnStateChanged();
SchedulePaint();
}
MdTab::~MdTab() = default;
void Tab::OnStateChanged() {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
void MdTab::OnStateChanged() {
// kHighlight mode has different color theme.
if (tabbed_pane()->GetStyle() == TabbedPane::TabStripStyle::kHighlight) {
Tab::OnStateChanged();
return;
if (tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight) {
constexpr int kFontSizeDelta = 1;
switch (state_) {
case State::kInactive:
// Notify assistive tools to update this tab's selected status.
// The way Chrome OS accessibility is implemented right now, firing
// almost any event will work, we just need to trigger its state to be
// refreshed.
NotifyAccessibilityEvent(ax::mojom::Event::kCheckedStateChanged, true);
title_->SetEnabledColor(gfx::kGoogleGrey700);
title_->SetFontList(rb.GetFontListWithDelta(
kFontSizeDelta, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
return;
case State::kActive:
title_->SetEnabledColor(gfx::kGoogleBlue600);
title_->SetFontList(rb.GetFontListWithDelta(
kFontSizeDelta, gfx::Font::NORMAL, gfx::Font::Weight::BOLD));
return;
case State::kHovered:
title_->SetEnabledColor(SK_ColorBLACK);
title_->SetFontList(rb.GetFontListWithDelta(
kFontSizeDelta, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
return;
}
}
ui::NativeTheme* theme = GetNativeTheme();
SkColor font_color =
selected()
? theme->GetSystemColor(ui::NativeTheme::kColorId_TabTitleColorActive)
: theme->GetSystemColor(
ui::NativeTheme::kColorId_TabTitleColorInactive);
title()->SetEnabledColor(font_color);
SkColor font_color = GetNativeTheme()->GetSystemColor(
selected() ? ui::NativeTheme::kColorId_TabTitleColorActive
: ui::NativeTheme::kColorId_TabTitleColorInactive);
title_->SetEnabledColor(font_color);
gfx::Font::Weight font_weight = gfx::Font::Weight::MEDIUM;
#if defined(OS_WIN)
......@@ -382,39 +242,36 @@ void MdTab::OnStateChanged() {
font_weight = gfx::Font::Weight::BOLD;
#endif
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
title()->SetFontList(rb.GetFontListWithDelta(ui::kLabelFontSizeDelta,
gfx::Font::NORMAL, font_weight));
title_->SetFontList(rb.GetFontListWithDelta(ui::kLabelFontSizeDelta,
gfx::Font::NORMAL, font_weight));
}
gfx::Size MdTab::CalculatePreferredSize() const {
return gfx::Size(Tab::CalculatePreferredSize().width(),
kHarmonyTabStripTabHeight);
}
void Tab::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
if (!selected() ||
tabbed_pane_->GetOrientation() != TabbedPane::Orientation::kVertical ||
tabbed_pane_->GetStyle() != TabbedPane::TabStripStyle::kHighlight)
return;
void MdTab::OnFocus() {
// Do not draw focus ring in kHighlight mode.
if (tabbed_pane()->GetStyle() != TabbedPane::TabStripStyle::kHighlight) {
SetBorder(CreateSolidBorder(
GetInsets().top(), GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FocusedBorderColor)));
}
constexpr SkScalar kRadius = SkIntToScalar(32);
constexpr SkScalar kLTRRadii[8] = {0, 0, kRadius, kRadius,
kRadius, kRadius, 0, 0};
constexpr SkScalar kRTLRadii[8] = {kRadius, kRadius, 0, 0,
0, 0, kRadius, kRadius};
SkPath path;
path.addRoundRect(gfx::RectToSkRect(GetLocalBounds()),
base::i18n::IsRTL() ? kRTLRadii : kLTRRadii);
// When the tab gains focus, send an accessibility event indicating that the
// contents are focused. When the tab loses focus, whichever new View ends up
// with focus will send an ax::mojom::Event::kFocus of its own, so there's no
// need to send one in OnBlur().
if (contents())
contents()->NotifyAccessibilityEvent(ax::mojom::Event::kFocus, true);
SchedulePaint();
cc::PaintFlags fill_flags;
fill_flags.setAntiAlias(true);
fill_flags.setColor(HasFocus() ? SkColorSetRGB(0xD2, 0xE3, 0xFC)
: SkColorSetRGB(0xE8, 0xF0, 0xFE));
canvas->DrawPath(path, fill_flags);
}
void MdTab::OnBlur() {
// Do not draw focus ring in kHighlight mode.
if (tabbed_pane()->GetStyle() != TabbedPane::TabStripStyle::kHighlight)
SetBorder(CreateEmptyBorder(GetInsets()));
SchedulePaint();
}
BEGIN_METADATA(Tab)
METADATA_PARENT_CLASS(View)
END_METADATA()
// static
constexpr size_t TabStrip::kNoSelectedTab;
......@@ -424,210 +281,45 @@ TabStrip::TabStrip(TabbedPane::Orientation orientation,
: orientation_(orientation), style_(style) {
std::unique_ptr<BoxLayout> layout;
if (orientation == TabbedPane::Orientation::kHorizontal) {
constexpr auto kEdgePadding = gfx::Insets(0, 9);
layout = std::make_unique<BoxLayout>(BoxLayout::Orientation::kHorizontal,
kEdgePadding);
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kEnd);
layout = std::make_unique<BoxLayout>(BoxLayout::Orientation::kHorizontal);
layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kCenter);
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStretch);
layout->SetDefaultFlex(1);
} else {
constexpr auto kEdgePadding = gfx::Insets(8, 0, 0, 0);
constexpr int kTabSpacing = 8;
layout = std::make_unique<BoxLayout>(BoxLayout::Orientation::kVertical,
kEdgePadding, kTabSpacing);
layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kStart);
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStart);
layout->SetDefaultFlex(0);
}
layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kStart);
layout->SetDefaultFlex(0);
SetLayoutManager(std::move(layout));
GetViewAccessibility().OverrideRole(ax::mojom::Role::kIgnored);
}
TabStrip::~TabStrip() = default;
void TabStrip::OnSelectedTabChanged(Tab* from_tab, Tab* to_tab) {}
void TabStrip::OnPaintBorder(gfx::Canvas* canvas) {
// Do not draw border line in kHighlight mode.
if (style_ == TabbedPane::TabStripStyle::kHighlight)
return;
cc::PaintFlags fill_flags;
fill_flags.setColor(kTabBorderColor);
fill_flags.setStrokeWidth(kTabBorderThickness);
SkScalar line_center_cross_axis;
SkScalar line_end_main_axis;
const bool is_horizontal =
orientation_ == TabbedPane::Orientation::kHorizontal;
if (is_horizontal) {
line_center_cross_axis =
SkIntToScalar(height()) - (kTabBorderThickness / 2);
line_end_main_axis = SkIntToScalar(width());
} else {
line_center_cross_axis = SkIntToScalar(width()) - (kTabBorderThickness / 2);
line_end_main_axis = SkIntToScalar(height());
}
size_t selected_tab_index = GetSelectedTabIndex();
if (selected_tab_index == kNoSelectedTab) {
if (is_horizontal) {
canvas->sk_canvas()->drawLine(0, line_center_cross_axis,
line_end_main_axis, line_center_cross_axis,
fill_flags);
} else {
canvas->sk_canvas()->drawLine(line_center_cross_axis, 0,
line_center_cross_axis, line_end_main_axis,
fill_flags);
}
} else {
Tab* selected_tab = GetTabAtIndex(selected_tab_index);
SkPath path;
SkScalar tab_height =
SkIntToScalar(selected_tab->height()) - kTabBorderThickness;
SkScalar tab_width;
SkScalar tab_start_x = SkIntToScalar(selected_tab->GetMirroredX());
SkScalar tab_start_y = SkIntToScalar(selected_tab->bounds().y());
if (is_horizontal) {
tab_width = SkIntToScalar(selected_tab->width()) - kTabBorderThickness;
path.moveTo(0, line_center_cross_axis);
path.rLineTo(tab_start_x, 0);
path.rLineTo(0, -tab_height);
path.rLineTo(tab_width, 0);
path.rLineTo(0, tab_height);
path.lineTo(line_end_main_axis, line_center_cross_axis);
} else {
tab_width =
SkIntToScalar(width() - selected_tab->GetInsets().left() / 2) -
kTabBorderThickness;
path.moveTo(line_center_cross_axis, 0);
path.rLineTo(0, tab_start_y);
path.rLineTo(-tab_width, 0);
path.rLineTo(0, tab_height);
path.rLineTo(tab_width, 0);
path.lineTo(line_center_cross_axis, line_end_main_axis);
}
fill_flags.setStyle(cc::PaintFlags::kStroke_Style);
canvas->DrawPath(path, fill_flags);
}
}
Tab* TabStrip::GetTabAtIndex(size_t index) const {
DCHECK_LT(index, children().size());
return static_cast<Tab*>(children()[index]);
}
size_t TabStrip::GetSelectedTabIndex() const {
for (size_t i = 0; i < children().size(); ++i)
if (GetTabAtIndex(i)->selected())
return i;
return kNoSelectedTab;
}
Tab* TabStrip::GetSelectedTab() const {
size_t index = GetSelectedTabIndex();
return index == kNoSelectedTab ? nullptr : GetTabAtIndex(index);
}
Tab* TabStrip::GetTabAtDeltaFromSelected(int delta) const {
const size_t selected_tab_index = GetSelectedTabIndex();
DCHECK_NE(kNoSelectedTab, selected_tab_index);
const size_t num_children = children().size();
// Clamping |delta| here ensures that even a large negative |delta| will not
// cause the addition in the next statement to wrap below 0.
delta %= static_cast<int>(num_children);
return GetTabAtIndex((selected_tab_index + num_children + delta) %
num_children);
}
TabbedPane::Orientation TabStrip::GetOrientation() const {
return orientation_;
}
TabbedPane::TabStripStyle TabStrip::GetStyle() const {
return style_;
}
DEFINE_ENUM_CONVERTERS(TabbedPane::Orientation,
{TabbedPane::Orientation::kHorizontal,
base::ASCIIToUTF16("HORIZONTAL")},
{TabbedPane::Orientation::kVertical,
base::ASCIIToUTF16("VERTICAL")})
DEFINE_ENUM_CONVERTERS(TabbedPane::TabStripStyle,
{TabbedPane::TabStripStyle::kBorder,
base::ASCIIToUTF16("BORDER")},
{TabbedPane::TabStripStyle::kHighlight,
base::ASCIIToUTF16("HIGHLIGHT")})
BEGIN_METADATA(TabStrip)
METADATA_PARENT_CLASS(View)
ADD_READONLY_PROPERTY_METADATA(TabStrip, int, SelectedTabIndex)
ADD_READONLY_PROPERTY_METADATA(TabStrip, TabbedPane::Orientation, Orientation)
ADD_READONLY_PROPERTY_METADATA(TabStrip, TabbedPane::TabStripStyle, Style)
END_METADATA()
MdTabStrip::MdTabStrip(TabbedPane::Orientation orientation,
TabbedPane::TabStripStyle style)
: TabStrip(orientation, style) {
if (orientation == TabbedPane::Orientation::kHorizontal) {
auto layout =
std::make_unique<BoxLayout>(BoxLayout::Orientation::kHorizontal);
layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kCenter);
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStretch);
layout->SetDefaultFlex(1);
SetLayoutManager(std::move(layout));
}
// These durations are taken from the Paper Tabs source:
// https://github.com/PolymerElements/paper-tabs/blob/master/paper-tabs.html
// See |selectionBar.expand| and |selectionBar.contract|.
expand_animation_ = std::make_unique<gfx::LinearAnimation>(this);
expand_animation_->SetDuration(base::TimeDelta::FromMilliseconds(150));
contract_animation_ = std::make_unique<gfx::LinearAnimation>(this);
contract_animation_->SetDuration(base::TimeDelta::FromMilliseconds(180));
}
MdTabStrip::~MdTabStrip() = default;
void MdTabStrip::OnSelectedTabChanged(Tab* from_tab, Tab* to_tab) {
DCHECK(!from_tab->selected());
DCHECK(to_tab->selected());
if (GetOrientation() == TabbedPane::Orientation::kHorizontal) {
animating_from_ = gfx::Range(from_tab->GetMirroredX(),
from_tab->GetMirroredX() + from_tab->width());
animating_to_ = gfx::Range(to_tab->GetMirroredX(),
to_tab->GetMirroredX() + to_tab->width());
} else {
animating_from_ = gfx::Range(from_tab->bounds().y(),
from_tab->bounds().y() + from_tab->height());
animating_to_ = gfx::Range(to_tab->bounds().y(),
to_tab->bounds().y() + to_tab->height());
}
contract_animation_->Stop();
expand_animation_->Start();
}
TabStrip::~TabStrip() = default;
void MdTabStrip::OnPaintBorder(gfx::Canvas* canvas) {
void TabStrip::OnPaintBorder(gfx::Canvas* canvas) {
// Do not draw border line in kHighlight mode.
if (GetStyle() == TabbedPane::TabStripStyle::kHighlight)
return;
constexpr int kUnselectedBorderThickness = 1;
constexpr int kSelectedBorderThickness = 2;
const bool is_horizontal =
GetOrientation() == TabbedPane::Orientation::kHorizontal;
int max_cross_axis;
// First, draw the unselected border across the TabStrip's entire width or
// height, depending on the orientation of the tab alignment. The area
// underneath or on the right of the selected tab will be overdrawn later.
const bool is_horizontal =
GetOrientation() == TabbedPane::Orientation::kHorizontal;
int max_cross_axis;
gfx::Rect rect;
constexpr int kUnselectedBorderThickness = 1;
if (is_horizontal) {
max_cross_axis = children().front()->bounds().bottom();
rect = gfx::Rect(0, max_cross_axis - kUnselectedBorderThickness, width(),
......@@ -640,8 +332,9 @@ void MdTabStrip::OnPaintBorder(gfx::Canvas* canvas) {
canvas->FillRect(rect, GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_TabBottomBorder));
int min_main_axis = 0;
int max_main_axis = 0;
Tab* tab = GetSelectedTab();
if (!tab)
return;
// Now, figure out the range to draw the selection marker underneath. There
// are three states here:
......@@ -652,14 +345,12 @@ void MdTabStrip::OnPaintBorder(gfx::Canvas* canvas) {
// selection marker until it encompasses only the currently selected tab;
// 3) No animations running: the selection marker is only under the currently
// selected tab.
Tab* tab = GetSelectedTab();
if (!tab)
return;
int min_main_axis = 0;
int max_main_axis = 0;
if (expand_animation_->is_animating()) {
bool animating_leading = animating_to_.start() < animating_from_.start();
double anim_value = gfx::Tween::CalculateValue(
gfx::Tween::FAST_OUT_LINEAR_IN, expand_animation_->GetCurrentValue());
if (animating_leading) {
min_main_axis = gfx::Tween::IntValueBetween(
anim_value, animating_from_.start(), animating_to_.start());
......@@ -682,45 +373,116 @@ void MdTabStrip::OnPaintBorder(gfx::Canvas* canvas) {
anim_value, animating_from_.start(), animating_to_.start());
max_main_axis = animating_to_.end();
}
} else if (tab) {
if (is_horizontal) {
min_main_axis = tab->GetMirroredX();
max_main_axis = tab->GetMirroredX() + tab->width();
} else {
min_main_axis = tab->bounds().y();
max_main_axis = tab->bounds().y() + tab->height();
}
} else if (is_horizontal) {
min_main_axis = tab->GetMirroredX();
max_main_axis = min_main_axis + tab->width();
} else {
min_main_axis = tab->bounds().y();
max_main_axis = min_main_axis + tab->height();
}
DCHECK(min_main_axis != max_main_axis);
DCHECK_NE(min_main_axis, max_main_axis);
// Draw over the unselected border from above.
if (is_horizontal) {
rect = gfx::Rect(min_main_axis, max_cross_axis - kSelectedBorderThickness,
max_main_axis - min_main_axis, kSelectedBorderThickness);
} else {
rect = gfx::Rect(max_cross_axis - kSelectedBorderThickness, min_main_axis,
kSelectedBorderThickness, max_main_axis - min_main_axis);
}
constexpr int kSelectedBorderThickness = 2;
rect = gfx::Rect(min_main_axis, max_cross_axis - kSelectedBorderThickness,
max_main_axis - min_main_axis, kSelectedBorderThickness);
if (!is_horizontal)
rect.Transpose();
canvas->FillRect(
rect, SkColorSetA(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FocusedBorderColor),
SK_AlphaOPAQUE));
}
void MdTabStrip::AnimationProgressed(const gfx::Animation* animation) {
void TabStrip::AnimationProgressed(const gfx::Animation* animation) {
SchedulePaint();
}
void MdTabStrip::AnimationEnded(const gfx::Animation* animation) {
void TabStrip::AnimationEnded(const gfx::Animation* animation) {
if (animation == expand_animation_.get())
contract_animation_->Start();
}
void TabStrip::OnSelectedTabChanged(Tab* from_tab, Tab* to_tab) {
DCHECK(!from_tab->selected());
DCHECK(to_tab->selected());
if (GetOrientation() == TabbedPane::Orientation::kHorizontal) {
animating_from_ = gfx::Range(from_tab->GetMirroredX(),
from_tab->GetMirroredX() + from_tab->width());
animating_to_ = gfx::Range(to_tab->GetMirroredX(),
to_tab->GetMirroredX() + to_tab->width());
} else {
animating_from_ = gfx::Range(from_tab->bounds().y(),
from_tab->bounds().y() + from_tab->height());
animating_to_ = gfx::Range(to_tab->bounds().y(),
to_tab->bounds().y() + to_tab->height());
}
contract_animation_->Stop();
expand_animation_->Start();
}
Tab* TabStrip::GetSelectedTab() const {
size_t index = GetSelectedTabIndex();
return index == kNoSelectedTab ? nullptr : GetTabAtIndex(index);
}
Tab* TabStrip::GetTabAtDeltaFromSelected(int delta) const {
const size_t selected_tab_index = GetSelectedTabIndex();
DCHECK_NE(kNoSelectedTab, selected_tab_index);
const size_t num_children = children().size();
// Clamping |delta| here ensures that even a large negative |delta| will not
// cause the addition in the next statement to wrap below 0.
delta %= static_cast<int>(num_children);
return GetTabAtIndex((selected_tab_index + num_children + delta) %
num_children);
}
Tab* TabStrip::GetTabAtIndex(size_t index) const {
DCHECK_LT(index, children().size());
return static_cast<Tab*>(children()[index]);
}
size_t TabStrip::GetSelectedTabIndex() const {
for (size_t i = 0; i < children().size(); ++i)
if (GetTabAtIndex(i)->selected())
return i;
return kNoSelectedTab;
}
TabbedPane::Orientation TabStrip::GetOrientation() const {
return orientation_;
}
TabbedPane::TabStripStyle TabStrip::GetStyle() const {
return style_;
}
DEFINE_ENUM_CONVERTERS(TabbedPane::Orientation,
{TabbedPane::Orientation::kHorizontal,
base::ASCIIToUTF16("HORIZONTAL")},
{TabbedPane::Orientation::kVertical,
base::ASCIIToUTF16("VERTICAL")})
DEFINE_ENUM_CONVERTERS(TabbedPane::TabStripStyle,
{TabbedPane::TabStripStyle::kBorder,
base::ASCIIToUTF16("BORDER")},
{TabbedPane::TabStripStyle::kHighlight,
base::ASCIIToUTF16("HIGHLIGHT")})
BEGIN_METADATA(TabStrip)
METADATA_PARENT_CLASS(View)
ADD_READONLY_PROPERTY_METADATA(TabStrip, int, SelectedTabIndex)
ADD_READONLY_PROPERTY_METADATA(TabStrip, TabbedPane::Orientation, Orientation)
ADD_READONLY_PROPERTY_METADATA(TabStrip, TabbedPane::TabStripStyle, Style)
END_METADATA()
TabbedPane::TabbedPane(TabbedPane::Orientation orientation,
TabbedPane::TabStripStyle style) {
DCHECK(orientation != TabbedPane::Orientation::kHorizontal ||
style != TabbedPane::TabStripStyle::kHighlight);
tab_strip_ = AddChildView(std::make_unique<MdTabStrip>(orientation, style));
tab_strip_ = AddChildView(std::make_unique<TabStrip>(orientation, style));
contents_ = AddChildView(std::make_unique<View>());
}
......@@ -743,9 +505,8 @@ void TabbedPane::AddTabInternal(size_t index,
contents->GetViewAccessibility().OverrideName(title);
contents->GetViewAccessibility().OverrideRole(ax::mojom::Role::kTab);
tab_strip_->AddChildViewAt(
std::make_unique<MdTab>(this, title, contents.get()),
static_cast<int>(index));
tab_strip_->AddChildViewAt(std::make_unique<Tab>(this, title, contents.get()),
static_cast<int>(index));
contents_->AddChildViewAt(std::move(contents), static_cast<int>(index));
if (!GetSelectedTab())
SelectTabAt(index);
......
......@@ -10,6 +10,8 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/strings/string16.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/linear_animation.h"
#include "ui/views/view.h"
namespace views {
......@@ -168,14 +170,6 @@ class VIEWS_EXPORT Tab : public View {
void OnBlur() override;
bool OnKeyPressed(const ui::KeyEvent& event) override;
protected:
Label* title() { return title_; }
TabbedPane* tabbed_pane() { return tabbed_pane_; }
// Called whenever |state_| changes.
virtual void OnStateChanged();
private:
enum class State {
kInactive,
......@@ -185,13 +179,16 @@ class VIEWS_EXPORT Tab : public View {
void SetState(State state);
// Called whenever |state_| changes.
void OnStateChanged();
// views::View:
void OnPaint(gfx::Canvas* canvas) override;
TabbedPane* tabbed_pane_;
Label* title_ = nullptr;
gfx::Size preferred_title_size_;
State state_;
int preferred_title_width_;
State state_ = State::kActive;
// The content view associated with this tab.
View* contents_;
......@@ -199,7 +196,7 @@ class VIEWS_EXPORT Tab : public View {
};
// The tab strip shown above/left of the tab contents.
class TabStrip : public View {
class TabStrip : public View, public gfx::AnimationDelegate {
public:
METADATA_HEADER(TabStrip);
......@@ -210,13 +207,17 @@ class TabStrip : public View {
TabbedPane::TabStripStyle style);
~TabStrip() override;
// Overridden from View:
void OnPaintBorder(gfx::Canvas* canvas) override;
// Overridden from AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override;
void AnimationEnded(const gfx::Animation* animation) override;
// Called by TabStrip when the selected tab changes. This function is only
// called if |from_tab| is not null, i.e., there was a previously selected
// tab.
virtual void OnSelectedTabChanged(Tab* from_tab, Tab* to_tab);
// Overridden from View:
void OnPaintBorder(gfx::Canvas* canvas) override;
void OnSelectedTabChanged(Tab* from_tab, Tab* to_tab);
Tab* GetSelectedTab() const;
Tab* GetTabAtDeltaFromSelected(int delta) const;
......@@ -234,6 +235,20 @@ class TabStrip : public View {
// The style of the tab strip.
const TabbedPane::TabStripStyle style_;
// Animations for expanding and contracting the selection bar. When changing
// selections, the selection bar first grows to encompass both the old and new
// selections, then shrinks to encompass only the new selection. The rates of
// expansion and contraction each follow the cubic bezier curves used in
// gfx::Tween; see TabStrip::OnPaintBorder for details.
std::unique_ptr<gfx::LinearAnimation> expand_animation_ =
std::make_unique<gfx::LinearAnimation>(this);
std::unique_ptr<gfx::LinearAnimation> contract_animation_ =
std::make_unique<gfx::LinearAnimation>(this);
// The x-coordinate ranges of the old selection and the new selection.
gfx::Range animating_from_;
gfx::Range animating_to_;
DISALLOW_COPY_AND_ASSIGN(TabStrip);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment