Skip to content

Commit

Permalink
Ash HUD: Add system UI dynamic animation duration control.
Browse files Browse the repository at this point in the history
This CL changes ui::ScopedAnimationDurationScaleMode to accept arbitrary
duration multipliers and implements dynamic animation duration control in Ash
HUD.

This CL also implements SliderMovementController and splits
views::Slider into ContinuousSlider and DiscreteSlider.

HUD animation speed control UI is using DiscreteSlider.

UI screenshot:
https://screenshot.googleplex.com/4xco9FhnDAsH5sJ.png

Bug: 1112706
Change-Id: Ic5bc32d3a900f35ead51e5acedf787e1fc050452
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2393623
Commit-Queue: Alexander Alekseev <alemate@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807675}
  • Loading branch information
Alexander Alekseev authored and Commit Bot committed Sep 16, 2020
1 parent c5678d4 commit 36cd5c2
Show file tree
Hide file tree
Showing 19 changed files with 535 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ constexpr float kScaleFactor = kDotLargeSizeDip / kDotSmallSizeDip;
// Helpers ---------------------------------------------------------------------

bool AreAnimationsEnabled() {
return ui::ScopedAnimationDurationScaleMode::duration_scale_mode() !=
return ui::ScopedAnimationDurationScaleMode::duration_multiplier() !=
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION;
}

Expand Down
256 changes: 250 additions & 6 deletions ash/hud_display/hud_settings_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
#include "base/strings/string16.h"
#include "components/viz/common/display/renderer_settings.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/aura/env.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/slider.h"
#include "ui/views/layout/box_layout.h"

namespace ash {
Expand Down Expand Up @@ -73,6 +77,228 @@ base::RepeatingCallback<void(views::Checkbox*)> GetHandleClickCallback(
field);
}

// views::Checkbox that ignores theme colors.
class SettingsCheckbox : public views::Checkbox {
public:
METADATA_HEADER(SettingsCheckbox);

SettingsCheckbox(const base::string16& label, views::ButtonListener* listener)
: views::Checkbox(label, listener) {}
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, 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, 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::ButtonListener:
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>;

std::unique_ptr<ui::ScopedAnimationDurationScaleMode>
scoped_animation_duration_scale_mode_;

views::View* hints_container_ = nullptr; // not owned.
AnimationSpeedSlider* slider_ = nullptr; // not owned.

SliderValuesMap slider_values_;
};

BEGIN_METADATA(AnimationSpeedControl, 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>(
base::ASCIIToUTF16("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 base::string16& text) {
const 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(/*vertical=*/0, /*horizontal=*/kLabelBorderWidth)));
label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
multipliers.push_back(multiplier);
};

add_speed_point(this, hints_container_, multipliers, 0,
base::ASCIIToUTF16("0"));
add_speed_point(this, hints_container_, multipliers, 0.5,
base::ASCIIToUTF16("0.5"));
add_speed_point(this, hints_container_, multipliers, 1,
base::ASCIIToUTF16("1"));
add_speed_point(this, hints_container_, multipliers, 2,
base::ASCIIToUTF16("2"));
add_speed_point(this, hints_container_, multipliers, 4,
base::ASCIIToUTF16("4"));
add_speed_point(this, hints_container_, multipliers, 10,
base::ASCIIToUTF16("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);
}

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.
scoped_animation_duration_scale_mode_.reset();
if (multiplier != 1) {
scoped_animation_duration_scale_mode_ =
std::make_unique<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 negin 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(/*vertical=*/0, /*horizontal=*/max_size.width() / 2)));
views::View::Layout();
}

} // anonymous namespace

BEGIN_METADATA(HUDSettingsView, View)
Expand All @@ -81,40 +307,58 @@ END_METADATA
HUDSettingsView::HUDSettingsView() {
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::kStart);
views::BoxLayout::CrossAxisAlignment::kStretch);
SetBorder(views::CreateSolidBorder(1, kHUDDefaultColor));

auto add_checkbox = [](HUDSettingsView* self,
// 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_contaner =
AddChildView(std::make_unique<views::View>());
checkbox_contaner
->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 base::string16& text) -> views::Checkbox* {
views::Checkbox* checkbox =
self->AddChildView(std::make_unique<views::Checkbox>(text, self));
container->AddChildView(std::make_unique<SettingsCheckbox>(text, self));
checkbox->SetEnabledTextColors(kHUDDefaultColor);
checkbox->SetProperty(kHUDClickHandler, HTCLIENT);
return checkbox;
};

checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(this, base::ASCIIToUTF16("Tint composited content")),
add_checkbox(this, checkbox_contaner,
base::ASCIIToUTF16("Tint composited content")),
GetUpdateStateCallback(
&viz::DebugRendererSettings::tint_composited_content),
GetHandleClickCallback(
&viz::DebugRendererSettings::tint_composited_content)));
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(this, base::ASCIIToUTF16("Show overdraw feedback")),
add_checkbox(this, checkbox_contaner,
base::ASCIIToUTF16("Show overdraw feedback")),
GetUpdateStateCallback(
&viz::DebugRendererSettings::show_overdraw_feedback),
GetHandleClickCallback(
&viz::DebugRendererSettings::show_overdraw_feedback)));
checkbox_handlers_.push_back(std::make_unique<HUDCheckboxHandler>(
add_checkbox(this, base::ASCIIToUTF16("Show aggregated damage")),
add_checkbox(this, checkbox_contaner,
base::ASCIIToUTF16("Show aggregated damage")),
GetUpdateStateCallback(
&viz::DebugRendererSettings::show_aggregated_damage),
GetHandleClickCallback(
&viz::DebugRendererSettings::show_aggregated_damage)));

AddChildView(std::make_unique<AnimationSpeedControl>());
}

HUDSettingsView::~HUDSettingsView() = default;
Expand Down
2 changes: 1 addition & 1 deletion ash/keyboard/ui/keyboard_ui_controller_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ namespace {
// Steps a layer animation until it is completed. Animations must be enabled.
void RunAnimationForLayer(ui::Layer* layer) {
// Animations must be enabled for stepping to work.
ASSERT_NE(ui::ScopedAnimationDurationScaleMode::duration_scale_mode(),
ASSERT_NE(ui::ScopedAnimationDurationScaleMode::duration_multiplier(),
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);

ui::LayerAnimatorTestController controller(layer->GetAnimator());
Expand Down
2 changes: 1 addition & 1 deletion ash/system/network/tray_network_state_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class TrayNetworkStateModel::Impl

TrayNetworkStateModel::TrayNetworkStateModel()
: update_frequency_(kUpdateFrequencyMs) {
if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() !=
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() !=
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION) {
update_frequency_ = 0; // Send updates immediately for tests.
}
Expand Down
2 changes: 1 addition & 1 deletion ash/system/night_light/night_light_controller_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class ColorTemperatureAnimation : public gfx::LinearAnimation,
target_temperature_ =
base::ClampToRange(new_target_temperature, 0.0f, 1.0f);

if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() ==
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
// Animations are disabled. Apply the target temperature directly to the
// compositors.
Expand Down
2 changes: 1 addition & 1 deletion ash/system/tray/tray_item_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void TrayItemView::CreateImageView() {

void TrayItemView::SetVisible(bool set_visible) {
if (!GetWidget() ||
ui::ScopedAnimationDurationScaleMode::duration_scale_mode() ==
ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
views::View::SetVisible(set_visible);
return;
Expand Down
2 changes: 1 addition & 1 deletion ash/wallpaper/wallpaper_controller_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ int ChildCountForContainer(int container_id) {
// Steps a layer animation until it is completed. Animations must be enabled.
void RunAnimationForLayer(ui::Layer* layer) {
// Animations must be enabled for stepping to work.
ASSERT_NE(ui::ScopedAnimationDurationScaleMode::duration_scale_mode(),
ASSERT_NE(ui::ScopedAnimationDurationScaleMode::duration_multiplier(),
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);

ui::LayerAnimatorTestController controller(layer->GetAnimator());
Expand Down
2 changes: 1 addition & 1 deletion ash/wm/overview/overview_test_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace {

void WaitForOverviewAnimationState(OverviewAnimationState state) {
// Early out if animations are disabled.
if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() ==
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void ColorTemperatureAnimation::AnimateToNewValue(float new_target_temperature,
target_temperature_ =
base::ClampToRange(new_target_temperature, 1000.0f, 20000.0f);

if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() ==
if (ui::ScopedAnimationDurationScaleMode::duration_multiplier() ==
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION) {
// Animations are disabled. Apply the target temperature directly to the
// compositor.
Expand Down
Loading

0 comments on commit 36cd5c2

Please sign in to comment.