Chromium Code Reviews| Index: ui/views/controls/tabbed_pane/tabbed_pane.cc |
| diff --git a/ui/views/controls/tabbed_pane/tabbed_pane.cc b/ui/views/controls/tabbed_pane/tabbed_pane.cc |
| index b445db30b5cef9f364aaeb3f6be7651c51800c9f..da1d41187515410d89fb0e101589f590f085ed30 100644 |
| --- a/ui/views/controls/tabbed_pane/tabbed_pane.cc |
| +++ b/ui/views/controls/tabbed_pane/tabbed_pane.cc |
| @@ -60,6 +60,11 @@ class Tab : public View { |
| bool selected() const { return contents_->visible(); } |
| void SetSelected(bool selected); |
| + // This is a bit odd: since Tabs are not actually focusable, but rather the |
| + // containing TabStrip is, Tabs need to be told when the TabStrip gains or |
| + // loses focus so they can swap to/from the focus ring border. |
| + void OnContainerFocusChanged(); |
|
Evan Stade
2016/09/28 17:32:32
this doesn't seem necessary any more? At least the
Elly Fong-Jones
2016/09/28 18:01:02
Done.
|
| + |
| // Overridden from View: |
| bool OnMousePressed(const ui::MouseEvent& event) override; |
| void OnMouseEntered(const ui::MouseEvent& event) override; |
| @@ -68,6 +73,8 @@ class Tab : public View { |
| gfx::Size GetPreferredSize() const override; |
| void Layout() override; |
| const char* GetClassName() const override; |
| + void OnFocus() override; |
| + void OnBlur() override; |
| protected: |
| Label* title() { return title_; } |
| @@ -75,6 +82,9 @@ class Tab : public View { |
| // Called whenever |tab_state_| changes. |
| virtual void OnStateChanged(); |
| + // Returns whether the containing TabStrip has focus. |
| + bool ContainerHasFocus(); |
| + |
| private: |
| enum TabState { |
| TAB_INACTIVE, |
| @@ -169,6 +179,12 @@ Tab::~Tab() {} |
| void Tab::SetSelected(bool selected) { |
| contents_->SetVisible(selected); |
| SetState(selected ? TAB_ACTIVE : TAB_INACTIVE); |
| +#if defined(OS_MACOSX) |
| + SetFocusBehavior(selected ? FocusBehavior::ACCESSIBLE_ONLY |
| + : FocusBehavior::NEVER); |
| +#else |
| + SetFocusBehavior(selected ? FocusBehavior::ALWAYS : FocusBehavior::NEVER); |
| +#endif |
| } |
| void Tab::OnStateChanged() { |
| @@ -252,6 +268,77 @@ void Tab::SetState(TabState tab_state) { |
| SchedulePaint(); |
| } |
| +bool Tab::ContainerHasFocus() { |
| + return tabbed_pane_->HasFocus(); |
| +} |
| + |
| +void Tab::OnContainerFocusChanged() { |
| + OnStateChanged(); |
| + SchedulePaint(); |
| +} |
| + |
| +void Tab::OnFocus() { |
| + OnStateChanged(); |
| + if (contents()) |
| + contents()->NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, true); |
| + SchedulePaint(); |
| +} |
| + |
| +void Tab::OnBlur() { |
| + OnStateChanged(); |
| + SchedulePaint(); |
| +} |
| + |
| +// The border used for MdTabs when they are both focused and selected: a |
| +// rectangle with the bottom side in a base color and the other sides in a |
| +// lightened version of the base color. |
| +class MdTabFocusRingBorder : public Border { |
| + public: |
| + MdTabFocusRingBorder(); |
| + ~MdTabFocusRingBorder() override; |
| + |
| + private: |
| + void Paint(const View& view, gfx::Canvas* canvas) override; |
| + gfx::Insets GetInsets() const override; |
| + gfx::Size GetMinimumSize() const override; |
| +}; |
| + |
| +MdTabFocusRingBorder::MdTabFocusRingBorder() {} |
| +MdTabFocusRingBorder::~MdTabFocusRingBorder() {} |
| + |
| +void MdTabFocusRingBorder::Paint(const View& view, gfx::Canvas* canvas) { |
| + gfx::Insets insets = GetInsets(); |
| + // TODO(ellyjones): should this 0x66 be part of NativeTheme somehow? |
| + SkColor base_color = view.GetNativeTheme()->GetSystemColor( |
| + ui::NativeTheme::kColorId_FocusedBorderColor); |
| + SkColor light_color = SkColorSetA(base_color, 0x66); |
| + const int kBorderStrokeWidth = 2; |
| + |
| + SkPaint paint; |
| + paint.setColor(light_color); |
| + paint.setStyle(SkPaint::kStroke_Style); |
| + paint.setStrokeWidth(kBorderStrokeWidth); |
| + |
| + gfx::Rect bounds = gfx::Rect(0, 0, view.width(), view.height()); |
| + bounds.Inset(kBorderStrokeWidth / 2, kBorderStrokeWidth / 2); |
| + |
| + // Draw the lighter-colored stroke first, then draw the heavier stroke over |
| + // the bottom of it. This is fine because the heavier stroke has 1.0 alpha, so |
| + // the lighter stroke won't show through. |
| + canvas->DrawRect(bounds, paint); |
| + canvas->FillRect(gfx::Rect(0, view.height() - insets.bottom(), view.width(), |
| + insets.bottom()), |
| + base_color); |
| +} |
| + |
| +gfx::Insets MdTabFocusRingBorder::GetInsets() const { |
| + return gfx::Insets(2, 2); |
| +} |
| + |
| +gfx::Size MdTabFocusRingBorder::GetMinimumSize() const { |
| + return gfx::Size(GetInsets().width(), GetInsets().height()); |
| +} |
| + |
| MdTab::MdTab(TabbedPane* tabbed_pane, |
| const base::string16& title, |
| View* contents) |
| @@ -267,8 +354,12 @@ void MdTab::OnStateChanged() { |
| selected() ? ui::NativeTheme::kColorId_FocusedBorderColor |
| : ui::NativeTheme::kColorId_UnfocusedBorderColor); |
| int border_thickness = selected() ? 2 : 1; |
| - SetBorder( |
| - Border::CreateSolidSidedBorder(0, 0, border_thickness, 0, border_color)); |
| + if (HasFocus() && selected()) { |
| + SetBorder(base::MakeUnique<MdTabFocusRingBorder>()); |
| + } else { |
| + SetBorder(Border::CreateSolidSidedBorder(0, 0, border_thickness, 0, |
| + border_color)); |
| + } |
| SkColor font_color = selected() |
| ? theme->GetSystemColor(ui::NativeTheme::kColorId_CallToActionColor) |
| @@ -386,12 +477,6 @@ TabbedPane::TabbedPane() |
| : new TabStrip(this)), |
| contents_(new View()), |
| selected_tab_index_(-1) { |
| -#if defined(OS_MACOSX) |
| - SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); |
| -#else |
| - SetFocusBehavior(FocusBehavior::ALWAYS); |
| -#endif |
| - |
| AddChildView(tab_strip_); |
| AddChildView(contents_); |
| } |
| @@ -403,11 +488,6 @@ int TabbedPane::GetTabCount() { |
| return contents_->child_count(); |
| } |
| -View* TabbedPane::GetSelectedTab() { |
| - return selected_tab_index() < 0 ? |
| - NULL : GetTabAt(selected_tab_index())->contents(); |
| -} |
| - |
| void TabbedPane::AddTab(const base::string16& title, View* contents) { |
| AddTabAtIndex(tab_strip_->child_count(), title, contents); |
| } |
| @@ -432,17 +512,23 @@ void TabbedPane::AddTabAtIndex(int index, |
| void TabbedPane::SelectTabAt(int index) { |
| DCHECK(index >= 0 && index < GetTabCount()); |
| + bool give_focus = false; |
|
Evan Stade
2016/09/28 17:32:32
nit: declare after early return
Elly Fong-Jones
2016/09/28 18:01:02
Done.
|
| if (index == selected_tab_index()) |
| return; |
| - if (selected_tab_index() >= 0) |
| - GetTabAt(selected_tab_index())->SetSelected(false); |
| + if (GetSelectedTab()) { |
|
Evan Stade
2016/09/28 17:32:32
can we move this below the tab->SetSelected call b
Elly Fong-Jones
2016/09/28 18:01:02
Ooh, yes, good thinking! Done.
|
| + give_focus = GetSelectedTab()->HasFocus(); |
| + GetSelectedTab()->SetSelected(false); |
| + } |
| selected_tab_index_ = index; |
| Tab* tab = GetTabAt(index); |
| tab->SetSelected(true); |
| tab_strip_->SchedulePaint(); |
| + if (give_focus) |
| + tab->RequestFocus(); |
| + |
| FocusManager* focus_manager = tab->contents()->GetFocusManager(); |
| if (focus_manager) { |
| const View* focused_view = focus_manager->GetFocusedView(); |
| @@ -473,6 +559,21 @@ Tab* TabbedPane::GetTabAt(int index) { |
| return static_cast<Tab*>(tab_strip_->child_at(index)); |
| } |
| +Tab* TabbedPane::GetSelectedTab() { |
| + return selected_tab_index() >= 0 ? GetTabAt(selected_tab_index()) : nullptr; |
| +} |
| + |
| +bool TabbedPane::MoveSelectionBy(int delta) { |
| + const int tab_count = GetTabCount(); |
| + if (tab_count <= 1) |
| + return false; |
| + int next_selected_index = (selected_tab_index() + delta) % tab_count; |
| + if (next_selected_index < 0) |
| + next_selected_index += tab_count; |
|
Evan Stade
2016/09/28 17:32:32
nit: I think you can just add tab_count in with de
Elly Fong-Jones
2016/09/28 18:01:01
I think the reason this code is done this way is t
Evan Stade
2016/09/28 21:25:54
good point
|
| + SelectTabAt(next_selected_index); |
| + return true; |
| +} |
| + |
| void TabbedPane::Layout() { |
| const gfx::Size size = tab_strip_->GetPreferredSize(); |
| tab_strip_->SetBounds(0, 0, width(), size.height()); |
| @@ -495,16 +596,16 @@ void TabbedPane::ViewHierarchyChanged( |
| bool TabbedPane::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| // Handle Ctrl+Tab and Ctrl+Shift+Tab navigation of pages. |
| DCHECK(accelerator.key_code() == ui::VKEY_TAB && accelerator.IsCtrlDown()); |
| - const int tab_count = GetTabCount(); |
| - if (tab_count <= 1) |
| + return MoveSelectionBy(accelerator.IsShiftDown() ? -1 : 1); |
| +} |
| + |
| +bool TabbedPane::OnKeyPressed(const ui::KeyEvent& event) { |
| + if (!GetSelectedTab() || !GetSelectedTab()->HasFocus()) |
| return false; |
| - const int increment = accelerator.IsShiftDown() ? -1 : 1; |
| - int next_tab_index = (selected_tab_index() + increment) % tab_count; |
| - // Wrap around. |
| - if (next_tab_index < 0) |
| - next_tab_index += tab_count; |
| - SelectTabAt(next_tab_index); |
| - return true; |
| + ui::KeyboardCode key = event.key_code(); |
| + if (key != ui::VKEY_LEFT && key != ui::VKEY_RIGHT) |
| + return false; |
| + return MoveSelectionBy(key == ui::VKEY_RIGHT ? 1 : -1); |
| } |
| const char* TabbedPane::GetClassName() const { |
| @@ -514,13 +615,21 @@ const char* TabbedPane::GetClassName() const { |
| void TabbedPane::OnFocus() { |
| View::OnFocus(); |
| - View* selected_tab = GetSelectedTab(); |
| - if (selected_tab) { |
| - selected_tab->NotifyAccessibilityEvent( |
| - ui::AX_EVENT_FOCUS, true); |
| + Tab* selected = GetSelectedTab(); |
| + if (selected) { |
| + selected->OnContainerFocusChanged(); |
| + if (selected->contents()) |
| + selected->contents()->NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, true); |
| } |
| } |
| +void TabbedPane::OnBlur() { |
| + View::OnBlur(); |
| + Tab* selected_tab = GetSelectedTab(); |
| + if (selected_tab) |
| + selected_tab->OnContainerFocusChanged(); |
| +} |
| + |
| void TabbedPane::GetAccessibleState(ui::AXViewState* state) { |
| state->role = ui::AX_ROLE_TAB_LIST; |
| } |