// Copyright 2018 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/system/unified/unified_slider_view.h"
#include <memory>
#include <utility>
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/unified/quick_settings_metrics_util.h"
#include "ash/system/unified/quick_settings_slider.h"
#include "base/check_op.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/layer.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/slider.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
constexpr auto kQsSliderRowPadding = gfx::Insets::TLBR(0, 12, 4, 16);
constexpr auto kQsSliderBorder = gfx::Insets::TLBR(4, 4, 4, 16);
std::unique_ptr<views::Slider> CreateSlider(
UnifiedSliderListener* listener,
bool read_only,
QuickSettingsSlider::Style slider_style) {
return read_only
? std::make_unique<ReadOnlySlider>(slider_style)
: std::make_unique<QuickSettingsSlider>(listener, slider_style);
}
} // namespace
void UnifiedSliderListener::TrackToggleUMA(bool target_toggle_state) {
CHECK_NE(GetCatalogName(), QsSliderCatalogName::kUnknown);
quick_settings_metrics_util::RecordQsSliderToggle(
GetCatalogName(), /*enable=*/target_toggle_state);
}
void UnifiedSliderListener::TrackValueChangeUMA(bool going_up) {
CHECK_NE(GetCatalogName(), QsSliderCatalogName::kUnknown);
quick_settings_metrics_util::RecordQsSliderValueChange(GetCatalogName(),
/*going_up=*/going_up);
}
UnifiedSliderView::UnifiedSliderView(views::Button::PressedCallback callback,
UnifiedSliderListener* listener,
const gfx::VectorIcon& icon,
int accessible_name_id,
bool is_togglable,
bool read_only,
QuickSettingsSlider::Style slider_style)
: icon_(&icon), is_togglable_(is_togglable) {
slider_ = AddChildView(CreateSlider(listener, read_only, slider_style));
slider_->SetBorder(views::CreateEmptyBorder(kQsSliderBorder));
// Sets `slider_` to have a `BoxLayout` to align the child view
// `slider_button_` to the left.
auto* slider_layout =
slider_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
slider_layout->set_main_axis_alignment(
views::BoxLayout::MainAxisAlignment::kStart);
const bool is_default_style =
(slider_style == QuickSettingsSlider::Style::kDefault ||
slider_style == QuickSettingsSlider::Style::kDefaultMuted);
slider_button_ = slider_->AddChildView(std::make_unique<IconButton>(
std::move(callback),
is_default_style ? IconButton::Type::kMediumFloating
: IconButton::Type::kLargeFloating,
&icon, accessible_name_id,
/*is_togglable=*/true,
/*has_border=*/true));
slider_button_->SetIconColor(cros_tokens::kCrosSysSystemOnPrimaryContainer);
// The `slider_button_` should be focusable by the ChromeVox.
slider_button_->SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
// `slider_button_` should disable event processing if it's not togglable.
if (!is_togglable_) {
slider_button_->SetCanProcessEventsWithinSubtree(/*can_process=*/false);
}
// Prevent an accessibility event while initiallizing this view.
// Typically the first update of the slider value is conducted by the
// caller function to reflect the current value.
slider_->SetEnableAccessibilityEvents(false);
slider_->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(accessible_name_id),
ax::mojom::NameFrom::kAttribute);
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kQsSliderRowPadding,
kSliderChildrenViewSpacing));
layout->SetFlexForView(slider_, /*flex=*/1);
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
// Adds a layer to set it non-opaque. Otherwise the full part of the slider
// will be drawn on top of the previous draw.
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
}
void UnifiedSliderView::SetSliderValue(float value, bool by_user) {
// SetValue() calls |listener|, so we should ignore the call when the widget
// is closed, because controllers are already deleted.
// It should allow the case GetWidget() returning null, so that initial
// position can be properly set by controllers before the view is attached to
// a widget.
if (GetWidget() && GetWidget()->IsClosed()) {
return;
}
slider_->SetValue(value);
if (by_user) {
slider_->SetEnableAccessibilityEvents(true);
}
}
UnifiedSliderView::~UnifiedSliderView() = default;
void UnifiedSliderView::OnEvent(ui::Event* event) {
// If `slider_button_` is not togglable, pressing the return key should not
// trigger the clicking callback.
if (!is_togglable_) {
views::View::OnEvent(event);
return;
}
if (!event->IsKeyEvent()) {
views::View::OnEvent(event);
return;
}
if (slider_ && !slider_->GetEnableAccessibilityEvents()) {
slider_->SetEnableAccessibilityEvents(true);
}
auto* key_event = event->AsKeyEvent();
auto key_code = key_event->key_code();
// Only handles press event to avoid handling the event again when the key is
// released.
if (key_code == ui::VKEY_RETURN &&
key_event->type() == ui::EventType::kKeyPressed) {
slider_button_->NotifyClick(*event);
return;
}
views::View::OnEvent(event);
}
BEGIN_METADATA(UnifiedSliderView)
END_METADATA
} // namespace ash