blob: edcbd9b39e2815215de6a18cbad05bfac41cdf7e [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/hud_display/tab_strip.h"
#include <cmath>
#include "ash/hud_display/hud_display.h"
#include "ash/hud_display/hud_properties.h"
#include "base/bind.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/metadata/metadata_impl_macros.h"
namespace ash {
namespace hud_display {
namespace {
// The width in pixels of overlaying adjacent tabs. Must be even number.
constexpr int kTabOverlayWidth = 2 * kTabOverlayCornerRadius / 3;
// Border around tab text (tab overlay width will be added to this).
constexpr int kTabTitleBorder = 3;
class HUDTabStripLayout : public views::LayoutManager {
public:
HUDTabStripLayout() = default;
HUDTabStripLayout(const HUDTabStripLayout&) = delete;
HUDTabStripLayout& operator=(const HUDTabStripLayout&) = delete;
~HUDTabStripLayout() override = default;
// views::LayoutManager:
void Layout(views::View* host) override;
gfx::Size GetPreferredSize(const views::View* host) const override;
};
gfx::Size HUDTabStripLayout::GetPreferredSize(const views::View* host) const {
gfx::Size result;
for (const auto* child : host->children()) {
const gfx::Size child_preferred = child->GetPreferredSize();
// Tab strip is always horizontal.
result.set_width(result.width() + child_preferred.width() -
kTabOverlayWidth);
result.set_height(std::max(result.height(), child_preferred.height()));
}
// Assume all children have equal left and right border, which is used to
// overlay the tabs. Add one overlay width to compensate one edge.
if (host->children().size())
result.set_width(result.width() + kTabOverlayWidth);
// Add right padding equal to the padding of the settings icon.
result.set_width(result.width() + kSettingsIconBorder);
return result;
}
void HUDTabStripLayout::Layout(views::View* host) {
// Assume all children have equal left and right border, which is used to
// overlay the tabs.
int left_offset = 0;
for (auto* child : host->children()) {
const gfx::Size preferred = child->GetPreferredSize();
const gfx::Size child_size({preferred.width(), host->height()});
child->SetSize(child_size);
child->SetPosition({left_offset, 0});
left_offset += child_size.width() - kTabOverlayWidth;
}
}
} // namespace
BEGIN_METADATA(HUDTabButton, views::LabelButton)
END_METADATA
HUDTabButton::HUDTabButton(Style style,
const DisplayMode display_mode,
const base::string16& text)
: views::LabelButton(views::Button::PressedCallback(), text),
style_(style),
display_mode_(display_mode) {
SetHorizontalAlignment(gfx::ALIGN_CENTER);
SetEnabledTextColors(kHUDDefaultColor);
SetProperty(kHUDClickHandler, HTCLIENT);
SetBorder(views::CreateEmptyBorder(
kSettingsIconBorder, kTabOverlayWidth + kTabTitleBorder,
kSettingsIconBorder, kTabOverlayWidth + kTabTitleBorder));
SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
}
void HUDTabButton::SetStyle(Style style) {
if (style_ == style)
return;
style_ = style;
SchedulePaint();
}
void HUDTabButton::PaintButtonContents(gfx::Canvas* canvas) {
// Horizontal offset from tab {0,0} where two tab arcs cross.
constexpr int kTabArcCrossedX = kTabOverlayWidth / 2;
// Reduce kTabArcCrossedX by one pixel when calculating partial arc so that
// the pixels along kTabArcCrossedX vertical line are drawn by full arc only.
static const float kTabPartialArcAngle =
90 - 180 *
asinf((kTabOverlayCornerRadius - kTabArcCrossedX + 1) /
(float)kTabOverlayCornerRadius) /
M_PI;
const int kCircleSize = kTabOverlayCornerRadius * 2;
const int right_edge = width();
const int bottom_edge = height();
SkPath path;
// Draw left vertical line and arc
if (style_ == Style::RIGHT) {
/* |true| - move to the start of the arc */
path.arcTo({0, 0, kCircleSize, kCircleSize}, -90 - kTabPartialArcAngle,
kTabPartialArcAngle, true);
} else {
if (style_ == Style::LEFT) {
// Draw bottom line from the right edge. Adjust for 2 pixels crossing the
// right vertical line.
path.moveTo(right_edge - kTabOverlayWidth / 2 - 2, bottom_edge);
path.lineTo(0, bottom_edge);
} else {
// No bottom line. Just move to the start of the vertical line.
path.moveTo(0, bottom_edge);
}
/* |false| will draw straight line to the start of the arc */
path.arcTo({0, 0, kCircleSize - 1, kCircleSize}, -180, 90, false);
}
// Draw top line, right arc and right vertical line
if (style_ == Style::LEFT) {
/* |false| will draw straight line to the start of the arc */
path.arcTo({right_edge - kCircleSize, 0, right_edge, kCircleSize}, -90,
kTabPartialArcAngle, false);
} else {
/* |false| will draw straight line to the start of the arc */
path.arcTo({right_edge - kCircleSize, 0, right_edge, kCircleSize}, -90, 90,
false);
path.lineTo(right_edge, bottom_edge);
if (style_ == Style::RIGHT) {
// Draw bottom line to the left edge. Adjust for 2 pixels crossing the
// left vertical line.
path.lineTo(kTabOverlayWidth / 2 + 2, bottom_edge);
}
}
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setBlendMode(SkBlendMode::kSrc);
flags.setStyle(cc::PaintFlags::kStroke_Style);
flags.setStrokeWidth(1);
flags.setColor(kHUDDefaultColor);
canvas->DrawPath(path, flags);
}
BEGIN_METADATA(HUDTabStrip, views::View)
END_METADATA
HUDTabStrip::HUDTabStrip(HUDDisplayView* hud) : hud_(hud) {
SetLayoutManager(std::make_unique<HUDTabStripLayout>());
}
HUDTabStrip::~HUDTabStrip() = default;
HUDTabButton* HUDTabStrip::AddTabButton(const DisplayMode display_mode,
const base::string16& label) {
CHECK_NE(static_cast<int>(display_mode), 0);
// Make first tab active by default.
HUDTabButton* tab_button = AddChildView(std::make_unique<HUDTabButton>(
tabs_.size() ? HUDTabButton::Style::RIGHT : HUDTabButton::Style::ACTIVE,
display_mode, label));
tab_button->SetCallback(base::BindRepeating(
[](HUDTabButton* sender, HUDTabStrip* tab_strip) {
for (const auto* tab : tab_strip->tabs_) {
if (tab == sender) {
tab_strip->hud_->SetDisplayMode(tab->display_mode());
return;
}
}
NOTREACHED();
},
base::Unretained(tab_button), base::Unretained(this)));
tabs_.push_back(tab_button);
return tab_button;
}
void HUDTabStrip::ActivateTab(const DisplayMode mode) {
// True if we find given active tab.
bool found = false;
for (HUDTabButton* tab : tabs_) {
if (found) {
tab->SetStyle(HUDTabButton::Style::RIGHT);
continue;
}
if (tab->display_mode() == mode) {
found = true;
tab->SetStyle(HUDTabButton::Style::ACTIVE);
continue;
}
tab->SetStyle(HUDTabButton::Style::LEFT);
}
DCHECK(found);
}
} // namespace hud_display
} // namespace ash