blob: 6f964ee1bc9eb3b450766654e63f731ca251975a [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/hud_display/hud_settings_view.h"
#include <string>
#include "ash/hud_display/ash_tracing_handler.h"
#include "ash/hud_display/hud_display.h"
#include "ash/hud_display/hud_properties.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "cc/debug/layer_tree_debug_state.h"
#include "components/viz/common/display/renderer_settings.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/paint_throbber.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/slider.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace hud_display {
namespace {
constexpr SkColor kHUDDisabledButtonColor =
SkColorSetA(kHUDDefaultColor, 0xFF * 0.5);
// Thickness of border around settings.
constexpr int kHUDSettingsBorderWidth = 1;
ui::ScopedAnimationDurationScaleMode* scoped_animation_duration_scale_mode =
nullptr;
} // anonymous namespace
class HUDCheckboxHandler {
public:
HUDCheckboxHandler(
views::Checkbox* checkbox,
base::RepeatingCallback<void(views::Checkbox*)> update_state)
: checkbox_(checkbox), update_state_(update_state) {}
HUDCheckboxHandler(const HUDCheckboxHandler&) = delete;
HUDCheckboxHandler& operator=(const HUDCheckboxHandler&) = delete;
void UpdateState() const { update_state_.Run(checkbox_); }
private:
views::Checkbox* const checkbox_; // not owned.
base::RepeatingCallback<void(views::Checkbox*)> update_state_;
};
namespace {
base::RepeatingCallback<void(views::Checkbox*)> GetVisDebugUpdateStateCallback(
const bool viz::DebugRendererSettings::*field) {
return base::BindRepeating(
[](const bool viz::DebugRendererSettings::*field,
views::Checkbox* checkbox) {
checkbox->SetChecked(aura::Env::GetInstance()
->context_factory()
->GetHostFrameSinkManager()
->debug_renderer_settings().*
field);
},
field);
}
base::RepeatingCallback<void(views::Checkbox*)> GetVisDebugHandleClickCallback(
bool viz::DebugRendererSettings::*field) {
return base::BindRepeating(
[](bool viz::DebugRendererSettings::*field, views::Checkbox* checkbox) {
viz::HostFrameSinkManager* manager = aura::Env::GetInstance()
->context_factory()
->GetHostFrameSinkManager();
viz::DebugRendererSettings debug_settings =
manager->debug_renderer_settings();
debug_settings.*field = checkbox->GetChecked();
manager->UpdateDebugRendererSettings(debug_settings);
},
field);
}
base::RepeatingCallback<void(views::Checkbox*)> GetCCDebugUpdateStateCallback(
const bool cc::LayerTreeDebugState::*field) {
return base::BindRepeating(
[](const bool cc::LayerTreeDebugState::*field,
views::Checkbox* checkbox) {
bool is_enabled = false;
aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows();
for (auto* window : root_windows) {
ui::Compositor* compositor = window->GetHost()->compositor();
is_enabled |= compositor->GetLayerTreeDebugState().*field;
}
checkbox->SetChecked(is_enabled);
},
field);
}
base::RepeatingCallback<void(views::Checkbox*)> GetCCDebugHandleClickCallback(
bool cc::LayerTreeDebugState::*field) {
return base::BindRepeating(
[](bool cc::LayerTreeDebugState::*field, views::Checkbox* checkbox) {
aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows();
for (auto* window : root_windows) {
ui::Compositor* compositor = window->GetHost()->compositor();
cc::LayerTreeDebugState state = compositor->GetLayerTreeDebugState();
state.*field = checkbox->GetChecked();
compositor->SetLayerTreeDebugState(state);
}
},
field);
}
// views::Checkbox that ignores theme colors.
class SettingsCheckbox : public views::Checkbox {
public:
METADATA_HEADER(SettingsCheckbox);
SettingsCheckbox(const std::u16string& label, const std::u16string& tooltip)
: views::Checkbox(label, views::Button::PressedCallback()) {
SetTooltipText(tooltip);
}
SettingsCheckbox(const SettingsCheckbox& other) = delete;
SettingsCheckbox operator=(const SettingsCheckbox& other) = delete;
~SettingsCheckbox() override = default;
// views::Checkbox:
SkColor GetIconImageColor(int icon_state) const override {
return kHUDDefaultColor;
}
};
BEGIN_METADATA(SettingsCheckbox, views::Checkbox);
END_METADATA
class AnimationSpeedSlider : public views::Slider {
public:
METADATA_HEADER(AnimationSpeedSlider);
AnimationSpeedSlider(const base::flat_set<float>& values,
views::SliderListener* listener = nullptr)
: views::Slider(listener) {
SetAllowedValues(&values);
}
AnimationSpeedSlider(const AnimationSpeedSlider&) = delete;
AnimationSpeedSlider operator=(const AnimationSpeedSlider&) = delete;
~AnimationSpeedSlider() override = default;
// views::Slider:
SkColor GetThumbColor() const override { return kHUDDefaultColor; }
SkColor GetTroughColor() const override { return kHUDDefaultColor; }
void OnPaint(gfx::Canvas* canvas) override;
};
BEGIN_METADATA(AnimationSpeedSlider, views::Slider)
END_METADATA
void AnimationSpeedSlider::OnPaint(gfx::Canvas* canvas) {
views::Slider::OnPaint(canvas);
// Paint ticks.
const int kTickHeight = 8;
const gfx::Rect content = GetContentsBounds();
const gfx::Insets insets = GetInsets();
const int y = insets.top() + content.height() / 2 - kTickHeight / 2;
SkPath path;
for (const float v : allowed_values()) {
const float x = insets.left() + content.width() * v;
path.moveTo(x, y);
path.lineTo(x, y + kTickHeight);
}
cc::PaintFlags flags;
flags.setAntiAlias(true);
flags.setBlendMode(SkBlendMode::kSrc);
flags.setColor(GetThumbColor());
flags.setStrokeWidth(1);
flags.setStyle(cc::PaintFlags::kStroke_Style);
canvas->DrawPath(path, flags);
}
// Checkbox group for setting UI animation speed.
class AnimationSpeedControl : public views::SliderListener, public views::View {
public:
METADATA_HEADER(AnimationSpeedControl);
AnimationSpeedControl();
AnimationSpeedControl(const AnimationSpeedControl&) = delete;
AnimationSpeedControl& operator=(const AnimationSpeedControl&) = delete;
~AnimationSpeedControl() override;
// views::SliderListener:
void SliderValueChanged(views::Slider* sender,
float value,
float old_value,
views::SliderChangeReason reason) override;
// views::View:
void Layout() override;
private:
// Map slider values to animation scale.
using SliderValuesMap = base::flat_map<float, float>;
views::View* hints_container_ = nullptr; // not owned.
AnimationSpeedSlider* slider_ = nullptr; // not owned.
SliderValuesMap slider_values_;
};
BEGIN_METADATA(AnimationSpeedControl, views::View)
END_METADATA
AnimationSpeedControl::AnimationSpeedControl() {
// This view consists of the title, slider values hints and a slider.
// Values hints live in a separate container.
// Slider is under that container and is resized to match the hints.
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical))
->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kCenter);
views::Label* title = AddChildView(std::make_unique<views::Label>(
u"Animation speed:", views::style::CONTEXT_LABEL));
title->SetAutoColorReadabilityEnabled(false);
title->SetEnabledColor(kHUDDefaultColor);
hints_container_ = AddChildView(std::make_unique<views::View>());
hints_container_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
std::vector<float> multipliers;
auto add_speed_point = [](AnimationSpeedControl* self, views::View* container,
std::vector<float>& multipliers, float multiplier,
const std::u16string& text) {
constexpr int kLabelBorderWidth = 3;
views::Label* label = container->AddChildView(
std::make_unique<views::Label>(text, views::style::CONTEXT_LABEL));
label->SetAutoColorReadabilityEnabled(false);
label->SetEnabledColor(kHUDDefaultColor);
label->SetBorder(
views::CreateEmptyBorder(gfx::Insets::VH(0, kLabelBorderWidth)));
label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
multipliers.push_back(multiplier);
};
add_speed_point(this, hints_container_, multipliers, 0, u"0");
add_speed_point(this, hints_container_, multipliers, 0.5, u"0.5");
add_speed_point(this, hints_container_, multipliers, 1, u"1");
add_speed_point(this, hints_container_, multipliers, 2, u"2");
add_speed_point(this, hints_container_, multipliers, 4, u"4");
add_speed_point(this, hints_container_, multipliers, 10, u"10");
// Now we need to calculate discrete values for the slider and active slider
// value.
std::vector<float> slider_values_list;
const float steps = multipliers.size() - 1;
const float active_multiplier =
ui::ScopedAnimationDurationScaleMode::duration_multiplier();
float slider_value = -1;
for (size_t i = 0; i < multipliers.size(); ++i) {
const float slider_step = i / steps;
slider_values_list.push_back(slider_step);
slider_values_[slider_step] = multipliers[i];
if (multipliers[i] == active_multiplier)
slider_value = slider_step;
// If we did not find exact value for the slider, set it to upper bound
// or to the maximum.
if (slider_value == -1 &&
(i == multipliers.size() - 1 || multipliers[i] > active_multiplier))
slider_value = slider_step;
}
slider_ = AddChildView(std::make_unique<AnimationSpeedSlider>(
base::flat_set<float>(slider_values_list), this));
slider_->SetProperty(kHUDClickHandler, HTCLIENT);
if (slider_value != -1)
slider_->SetValue(slider_value);
// Because the slider is focusable, it needs to have an accessible name so
// that the screen reader knows what to announce. Indicating the slider is
// labelled by the title will cause ViewAccessibility to set the name.
slider_->GetViewAccessibility().OverrideLabelledBy(title);
}
AnimationSpeedControl::~AnimationSpeedControl() = default;
void AnimationSpeedControl::SliderValueChanged(
views::Slider* sender,
float value,
float old_value,
views::SliderChangeReason reason) {
SliderValuesMap::const_iterator it = slider_values_.find(value);
DCHECK(it != slider_values_.end());
float multiplier = it->second;
// There could be only one instance of the scoped modifier at a time.
// So we need to destroy the existing one before we can create a
// new one.
delete scoped_animation_duration_scale_mode;
scoped_animation_duration_scale_mode = nullptr;
if (multiplier != 1) {
scoped_animation_duration_scale_mode =
new ui::ScopedAnimationDurationScaleMode(multiplier);
}
}
void AnimationSpeedControl::Layout() {
gfx::Size max_size;
// Make all labels equal size.
for (const auto* label : hints_container_->children())
max_size.SetToMax(label->GetPreferredSize());
for (auto* label : hints_container_->children())
label->SetPreferredSize(max_size);
gfx::Size hints_total_size = hints_container_->GetPreferredSize();
// Slider should begin in the middle of the first label, and end in the
// middle of the last label. But ripple overlays border, so we set total
// width to match the total hints width and adjust border to make slider
// correct size.
gfx::Size slider_size(hints_total_size.width(), 30);
slider_->SetPreferredSize(slider_size);
slider_->SetBorder(
views::CreateEmptyBorder(gfx::Insets::VH(0, max_size.width() / 2)));
views::View::Layout();
}
class HUDActionButton : public views::LabelButton {
public:
HUDActionButton(views::Button::PressedCallback::Callback callback,
const std::u16string& text)
: LabelButton(callback, text) {
SetHorizontalAlignment(gfx::ALIGN_CENTER);
SetEnabledTextColors(kHUDBackground);
SetProperty(kHUDClickHandler, HTCLIENT);
constexpr float kActionButtonCournerRadius = 2;
SetBackground(views::CreateRoundedRectBackground(
kHUDDefaultColor, kActionButtonCournerRadius));
SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
on_enabled_changed_subscription_ =
AddEnabledChangedCallback(base::BindRepeating(
&HUDActionButton::OnEnabedChanged, base::Unretained(this)));
}
HUDActionButton(const HUDActionButton&) = delete;
HUDActionButton& operator=(const HUDActionButton&) = delete;
~HUDActionButton() override = default;
void PaintButtonContents(gfx::Canvas* canvas) override {
views::LabelButton::PaintButtonContents(canvas);
if (spinner_refresh_timer_.IsRunning()) {
base::Time now = base::Time::Now();
gfx::Rect spinner = GetContentsBounds();
int spinner_width = std::min(spinner.width(), spinner.height());
spinner.ClampToCenteredSize(gfx::Size(spinner_width, spinner_width));
gfx::PaintThrobberSpinning(canvas, spinner,
SkColorSetA(SK_ColorWHITE, 0xFF * (.5)),
(now - spinner_created_) / 8);
}
}
void DisableWithSpinner() {
DCHECK(!spinner_refresh_timer_.IsRunning());
SetEnabled(false);
constexpr base::TimeDelta interval = base::Seconds(0.5);
spinner_created_ = base::Time::Now();
spinner_refresh_timer_.Start(
FROM_HERE, interval,
base::BindRepeating(
[](views::View* button) { button->SchedulePaint(); },
base::Unretained(this)));
SchedulePaint();
}
void UpdateBackgroundColor() override {
if (GetVisualState() == STATE_DISABLED) {
GetBackground()->SetNativeControlColor(kHUDDisabledButtonColor);
} else {
GetBackground()->SetNativeControlColor(kHUDDefaultColor);
}
}
private:
void OnEnabedChanged() {
if (GetEnabled())
spinner_refresh_timer_.Stop();
}
base::CallbackListSubscription on_enabled_changed_subscription_;
base::Time spinner_created_;
base::RepeatingTimer spinner_refresh_timer_;
};
} // anonymous namespace
BEGIN_METADATA(HUDSettingsView, views::View)
END_METADATA
HUDSettingsView::HUDSettingsView(HUDDisplayView* hud_display) {
SetVisible(false);
// We want AnimationSpeedControl to be stretched horizontally so we turn
// stretch on by default.
views::BoxLayout* layout_manager =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
layout_manager->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
SetBorder(
views::CreateSolidBorder(kHUDSettingsBorderWidth, kHUDDefaultColor));
// We want the HUD to be draggable when clicked on the whitespace, so we do
// not want the buttons to extend past the minimum size. To overcome the
// default horizontal stretch we put them into a separate container with
// default left alignment.
views::View* checkbox_container =
AddChildView(std::make_unique<views::View>());
checkbox_container
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical))
->set_cross_axis_alignment(views::BoxLayout::CrossAxisAlignment::kStart);
auto add_checkbox =
[](HUDSettingsView* self, views::View* container,
const std::u16string& text, const std::u16string& tooltip,
base::RepeatingCallback<void(views::Checkbox*)> callback) {
views::Checkbox* checkbox = container->AddChildView(
std::make_unique<SettingsCheckbox>(text, tooltip));
checkbox->SetCallback(
base::BindRepeating(std::move(callback), checkbox));
checkbox->SetEnabledTextColors(kHUDDefaultColor);
checkbox->SetProperty(kHUDClickHandler, HTCLIENT);
return checkbox;
};
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(
this, checkbox_container, u"Tint composited content",
u"Equivalent to --tint-composited-content command-line option.",
GetVisDebugHandleClickCallback(
&viz::DebugRendererSettings::tint_composited_content)),
GetVisDebugUpdateStateCallback(
&viz::DebugRendererSettings::tint_composited_content)));
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(
this, checkbox_container, u"Show overdraw feedback",
u"Equivalent to --show-overdraw-feedback command-line option.",
GetVisDebugHandleClickCallback(
&viz::DebugRendererSettings::show_overdraw_feedback)),
GetVisDebugUpdateStateCallback(
&viz::DebugRendererSettings::show_overdraw_feedback)));
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(
this, checkbox_container, u"Show aggregated damage",
u"Equivalent to --show-aggregated-damage command-line option.",
GetVisDebugHandleClickCallback(
&viz::DebugRendererSettings::show_aggregated_damage)),
GetVisDebugUpdateStateCallback(
&viz::DebugRendererSettings::show_aggregated_damage)));
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(this, checkbox_container, u"Show paint rect.",
u"Equivalent to --ui-show-paint-rects command-line option.",
GetCCDebugHandleClickCallback(
&cc::LayerTreeDebugState::show_paint_rects)),
GetCCDebugUpdateStateCallback(
&cc::LayerTreeDebugState::show_paint_rects)));
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(this, checkbox_container, u"HUD is overlay.",
u"Flips HUD overlay mode flag.",
base::BindRepeating(
[](HUDDisplayView* hud_display, views::Checkbox*) {
hud_display->ToggleOverlay();
},
base::Unretained(hud_display))),
base::BindRepeating(
[](HUDDisplayView* hud_display, views::Checkbox* checkbox) {
checkbox->SetChecked(hud_display->IsOverlay());
},
base::Unretained(hud_display))));
AddChildView(std::make_unique<AnimationSpeedControl>());
// Ui Dev Tools controls.
constexpr int kUiDevToolsControlButtonMargin = 6;
views::View* ui_devtools_controls =
AddChildView(std::make_unique<views::View>());
ui_devtools_controls
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical))
->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
// Tracing controls.
constexpr int kTracingControlButtonMargin = 6;
views::View* tracing_controls = AddChildView(std::make_unique<views::View>());
tracing_controls
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical))
->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
ui_devtools_controls->SetBorder(
views::CreateEmptyBorder(kUiDevToolsControlButtonMargin));
ui_dev_tools_control_button_ =
ui_devtools_controls->AddChildView(std::make_unique<HUDActionButton>(
base::BindRepeating(&HUDSettingsView::OnEnableUiDevToolsButtonPressed,
base::Unretained(this)),
std::u16string()));
UpdateDevToolsControlButtonLabel();
tracing_controls->SetBorder(
views::CreateEmptyBorder(kTracingControlButtonMargin));
tracing_control_button_ =
tracing_controls->AddChildView(std::make_unique<HUDActionButton>(
base::BindRepeating(&HUDSettingsView::OnEnableTracingButtonPressed,
base::Unretained(this)),
std::u16string()));
const int kLabelBorderWidth = 3;
tracing_status_message_ =
tracing_controls->AddChildView(std::make_unique<views::Label>(
std::u16string(), views::style::CONTEXT_LABEL));
tracing_status_message_->SetAutoColorReadabilityEnabled(false);
tracing_status_message_->SetEnabledColor(kHUDDefaultColor);
tracing_status_message_->SetBorder(
views::CreateEmptyBorder(gfx::Insets::VH(0, kLabelBorderWidth)));
tracing_status_message_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
views::Label* pii_label = tracing_controls->AddChildView(std::make_unique<
views::Label>(
u"WARNING: Trace files may contain Personally Identifiable Information. "
u"You should use discretion when sharing your trace files.",
views::style::CONTEXT_LABEL));
pii_label->SetMultiLine(true);
pii_label->SetAutoColorReadabilityEnabled(false);
pii_label->SetEnabledColor(kHUDDefaultColor);
pii_label->SetBorder(
views::CreateEmptyBorder(gfx::Insets::VH(0, kLabelBorderWidth)));
pii_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
UpdateTracingControlButton();
AshTracingManager::Get().AddObserver(this);
}
HUDSettingsView::~HUDSettingsView() {
AshTracingManager::Get().RemoveObserver(this);
}
void HUDSettingsView::OnTracingStatusChange() {
UpdateTracingControlButton();
}
void HUDSettingsView::OnEnableUiDevToolsButtonPressed(const ui::Event& event) {
if (Shell::Get()->shell_delegate()->IsUiDevToolsStarted()) {
Shell::Get()->shell_delegate()->StopUiDevTools();
} else {
Shell::Get()->shell_delegate()->StartUiDevTools();
}
UpdateDevToolsControlButtonLabel();
}
void HUDSettingsView::UpdateDevToolsControlButtonLabel() {
if (!Shell::Get()->shell_delegate()->IsUiDevToolsStarted()) {
ui_dev_tools_control_button_->SetText(u"Create Ui Dev Tools");
} else {
const int port = Shell::Get()->shell_delegate()->GetUiDevToolsPort();
ui_dev_tools_control_button_->SetText(base::ASCIIToUTF16(
base::StringPrintf("Ui Dev Tools: ON, port %d", port).c_str()));
}
}
void HUDSettingsView::ToggleVisibility() {
const bool is_shown = !GetVisible();
if (is_shown) {
for (const auto& handler : checkbox_handlers_) {
handler->UpdateState();
}
}
SetVisible(is_shown);
}
void HUDSettingsView::OnEnableTracingButtonPressed(const ui::Event& event) {
ToggleTracing();
}
void HUDSettingsView::ToggleTracingForTesting() {
ToggleTracing();
}
void HUDSettingsView::ToggleTracing() {
AshTracingManager& manager = AshTracingManager::Get();
tracing_control_button_->DisableWithSpinner();
if (manager.IsTracingStarted()) {
manager.Stop();
} else {
manager.Start();
}
}
void HUDSettingsView::UpdateTracingControlButton() {
AshTracingManager& manager = AshTracingManager::Get();
if (!manager.IsBusy())
tracing_control_button_->SetEnabled(true);
tracing_status_message_->SetText(
base::ASCIIToUTF16(manager.GetStatusMessage()));
if (manager.IsTracingStarted()) {
tracing_control_button_->SetText(u"Stop tracing.");
} else {
tracing_control_button_->SetText(u"Start tracing.");
}
}
} // namespace hud_display
} // namespace ash