| // Copyright 2024 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/audio/labeled_slider_view.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/typography.h" |
| #include "ash/system/audio/audio_detailed_view_utils.h" |
| #include "ash/system/tray/hover_highlight_view.h" |
| #include "ash/system/tray/tray_detailed_view.h" |
| #include "ash/system/tray/tray_popup_utils.h" |
| #include "ash/system/unified/unified_slider_view.h" |
| #include "chromeos/ash/components/audio/audio_device.h" |
| #include "chromeos/ash/components/audio/cras_audio_handler.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/chromeos/styles/cros_tokens_color_mappings.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/geometry/rounded_corners_f.h" |
| #include "ui/gfx/geometry/rrect_f.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/views/controls/focus_ring.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/view_utils.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr auto kDevicesNameViewPreferredSize = gfx::Size(0, 44); |
| constexpr auto kDevicesTriViewInsets = gfx::Insets::TLBR(0, 24, 0, 32); |
| constexpr auto kDevicesTriViewBorder = gfx::Insets::VH(0, 4); |
| constexpr auto kWideDevicesSliderBorder = gfx::Insets::VH(4, 8); |
| constexpr auto kWideDevicesTriViewInsets = gfx::Insets::TLBR(0, 8, 0, 16); |
| |
| // Returns the corresponding device name based on the passed in `device`. |
| std::u16string GetAudioDeviceName(const AudioDevice& device) { |
| switch (device.type) { |
| case AudioDeviceType::kBluetooth: |
| case AudioDeviceType::kBluetoothNbMic: |
| return l10n_util::GetStringFUTF16( |
| IDS_ASH_STATUS_TRAY_AUDIO_BLUETOOTH_DEVICE, |
| base::UTF8ToUTF16(device.display_name)); |
| case AudioDeviceType::kFrontMic: |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_FRONT_MIC); |
| case AudioDeviceType::kHeadphone: |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_HEADPHONE); |
| case AudioDeviceType::kHdmi: |
| return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_AUDIO_HDMI_DEVICE, |
| base::UTF8ToUTF16(device.display_name)); |
| case AudioDeviceType::kInternalMic: |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_INTERNAL_MIC); |
| case AudioDeviceType::kInternalSpeaker: |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_AUDIO_INTERNAL_SPEAKER); |
| case AudioDeviceType::kMic: |
| return l10n_util::GetStringUTF16( |
| IDS_ASH_STATUS_TRAY_AUDIO_MIC_JACK_DEVICE); |
| case AudioDeviceType::kRearMic: |
| return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO_REAR_MIC); |
| case AudioDeviceType::kUsb: |
| return l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_AUDIO_USB_DEVICE, |
| base::UTF8ToUTF16(device.display_name)); |
| case AudioDeviceType::kAlsaLoopback: |
| case AudioDeviceType::kHotword: |
| case AudioDeviceType::kKeyboardMic: |
| case AudioDeviceType::kLineout: |
| case AudioDeviceType::kPostDspLoopback: |
| case AudioDeviceType::kPostMixLoopback: |
| case AudioDeviceType::kOther: |
| return base::UTF8ToUTF16(device.display_name); |
| } |
| } |
| |
| // Customizes the highlight path so that the device container view's focus ring |
| // follows the slider's size instead of its own size. |
| // NOTE: This class requires a valid slider during its life cycle. |
| class DeviceNameContainerHighlightPathGenerator |
| : public views::HighlightPathGenerator { |
| public: |
| DeviceNameContainerHighlightPathGenerator(QuickSettingsSlider* slider, |
| bool is_wide_slider) |
| : slider_(slider), is_wide_slider_(is_wide_slider) { |
| CHECK(slider); |
| } |
| DeviceNameContainerHighlightPathGenerator( |
| const DeviceNameContainerHighlightPathGenerator&) = delete; |
| DeviceNameContainerHighlightPathGenerator& operator=( |
| const DeviceNameContainerHighlightPathGenerator&) = delete; |
| ~DeviceNameContainerHighlightPathGenerator() override = default; |
| |
| private: |
| // HighlightPathGenerator: |
| std::optional<gfx::RRectF> GetRoundRect(const gfx::RectF& rect) override { |
| const gfx::Rect slider_bounds = slider_->GetInactiveRadioSliderRect(); |
| const gfx::RectF bounds( |
| slider_bounds.x() + |
| (is_wide_slider_ ? 0 : kRadioSliderViewPadding.left()), |
| slider_bounds.y(), slider_bounds.width(), slider_bounds.height()); |
| const gfx::RoundedCornersF rounded( |
| slider_->GetInactiveRadioSliderRoundedCornerRadius()); |
| return gfx::RRectF(bounds, rounded); |
| } |
| |
| const raw_ptr<QuickSettingsSlider> slider_; |
| const bool is_wide_slider_; |
| }; |
| |
| } // namespace |
| |
| LabeledSliderView::LabeledSliderView(TrayDetailedView* detailed_view, |
| std::unique_ptr<views::View> slider_view, |
| const AudioDevice& device, |
| bool is_wide_slider) |
| : is_wide_slider_(is_wide_slider) { |
| SetUseDefaultFillLayout(true); |
| |
| // Creates and formats the slider view. |
| unified_slider_view_ = views::AsViewClass<UnifiedSliderView>( |
| AddChildView(std::move(slider_view))); |
| auto* slider = |
| views::AsViewClass<QuickSettingsSlider>(unified_slider_view_->slider()); |
| if (is_wide_slider_) { |
| slider->SetBorder(views::CreateEmptyBorder(kWideDevicesSliderBorder)); |
| } |
| |
| // Creates and formats the device name view. |
| device_name_view_ = detailed_view->AddScrollListCheckableItem( |
| this, gfx::VectorIcon::EmptyIcon(), GetAudioDeviceName(device), |
| device.active); |
| ConfigureDeviceNameView(device); |
| |
| // Puts `unified_slider_view_` beneath `device_name_view_`. |
| device_name_view_->AddLayerToRegion(unified_slider_view_->layer(), |
| views::LayerRegion::kBelow); |
| |
| ConfigureFocusBehavior(device.active, slider); |
| } |
| |
| LabeledSliderView::~LabeledSliderView() { |
| // Remove the focus ring before the slider view that highlight path generator |
| // of `device_name_view_` depends on is deleted. |
| views::FocusRing::Remove(device_name_view_); |
| } |
| |
| void LabeledSliderView::ConfigureDeviceNameView(const AudioDevice& device) { |
| // Set this flag to false to make the assigned color id effective. |
| // Otherwise it will use `color_utils::BlendForMinContrast()` to improve |
| // label readability over the background. |
| device_name_view_->text_label()->SetAutoColorReadabilityEnabled( |
| /*enabled=*/false); |
| |
| device_name_view_->SetPreferredSize(kDevicesNameViewPreferredSize); |
| device_name_view_->tri_view()->SetInsets( |
| is_wide_slider_ ? kWideDevicesTriViewInsets : kDevicesTriViewInsets); |
| device_name_view_->tri_view()->SetContainerBorder( |
| TriView::Container::CENTER, |
| views::CreateEmptyBorder(kDevicesTriViewBorder)); |
| const bool is_muted = |
| device.is_input |
| ? CrasAudioHandler::Get()->IsInputMutedForDevice(device.id) |
| : CrasAudioHandler::Get()->IsOutputMutedForDevice(device.id); |
| UpdateDeviceContainerColor(device_name_view_, is_muted, device.active); |
| TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2, |
| *device_name_view_->text_label()); |
| } |
| |
| void LabeledSliderView::ConfigureFocusBehavior(const bool is_active, |
| QuickSettingsSlider* slider) { |
| // If this device is the active one, disables event handling on |
| // `device_name_view_` so that `slider` can handle the events. |
| if (is_active) { |
| device_name_view_->SetFocusBehavior( |
| HoverHighlightView::FocusBehavior::NEVER); |
| device_name_view_->SetCanProcessEventsWithinSubtree(false); |
| } else { |
| // Installs the customized focus ring path generator for |
| // `device_name_view_`. |
| device_name_view_->SetInstallFocusRingOnFocus(true); |
| views::FocusRing::Get(device_name_view_) |
| ->SetPathGenerator( |
| std::make_unique<DeviceNameContainerHighlightPathGenerator>( |
| slider, is_wide_slider_)); |
| device_name_view_->SetFocusPainter(nullptr); |
| views::FocusRing::Get(device_name_view_) |
| ->SetColorId(cros_tokens::kCrosSysPrimary); |
| } |
| } |
| |
| BEGIN_METADATA(LabeledSliderView) |
| END_METADATA |
| |
| } // namespace ash |