blob: d6ade4dfb97b9500a5f3afe90c31286932090c3b [file]
// 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.
#ifndef COMPONENTS_LIVE_CAPTION_VIEWS_CAPTION_BUBBLE_H_
#define COMPONENTS_LIVE_CAPTION_VIEWS_CAPTION_BUBBLE_H_
#include <memory>
#include <string>
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "build/buildflag.h"
#include "components/live_caption/caption_bubble_settings.h"
#include "components/live_caption/views/caption_bubble_model.h"
#include "components/live_caption/views/translation_view_wrapper_base.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/native_theme/caption_style.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/metadata/view_factory.h"
namespace views {
class Checkbox;
class ImageButton;
class ImageView;
class Label;
} // namespace views
namespace captions {
class CaptionBubbleEventObserver;
class CaptionBubbleFrameView;
class CaptionBubbleLabel;
class CaptionBubbleScrollView;
class ScrollLockButton;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. These should be the same as
// LiveCaptionSessionEvent in enums.xml.
// LINT.IfChange(SessionEvent)
enum class SessionEvent {
// We began showing captions for an audio stream.
kStreamStarted = 0,
// The audio stream ended and the caption bubble closes.
kStreamEnded = 1,
// The close button was clicked, so we stopped listening to an audio stream.
kCloseButtonClicked = 2,
kMaxValue = kCloseButtonClicked,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/accessibility/enums.xml:LiveCaptionSessionEvent)
#if BUILDFLAG(IS_CHROMEOS)
// Used by ash window manager to place the caption bubble in the correct
// container.
extern const ui::ClassProperty<bool>* const kIsCaptionBubbleKey;
#endif
///////////////////////////////////////////////////////////////////////////////
// Caption Bubble
//
// A caption bubble that floats above all other windows and shows
// automatically- generated text captions for audio and media streams. The
// captions bubble's widget is a top-level window that has top z order and is
// visible on all workspaces. It is draggable in and out of the tab.
//
class CaptionBubble : public views::BubbleDialogDelegateView,
public gfx::AnimationDelegate,
public TranslationViewWrapperBase::Delegate {
METADATA_HEADER(CaptionBubble, views::BubbleDialogDelegateView)
public:
using NewFontListGetter = base::RepeatingCallback<gfx::FontList(
const std::vector<std::string>& font_names,
int font_style,
int font_size,
gfx::Font::Weight font_weight)>;
CaptionBubble(
CaptionBubbleSettings* caption_bubble_settings,
std::unique_ptr<TranslationViewWrapperBase> translation_view_wrapper,
const std::string& application_locale,
base::OnceClosure destroyed_callback);
CaptionBubble(const CaptionBubble&) = delete;
CaptionBubble& operator=(const CaptionBubble&) = delete;
~CaptionBubble() override;
// gfx::AnimationDelegate:
void AnimationProgressed(const gfx::Animation* animation) override;
// Sets the caption bubble model currently being used for this caption bubble.
// There exists one CaptionBubble per profile, but one CaptionBubbleModel per
// media stream. A new CaptionBubbleModel is set when transcriptions from a
// different media stream are received. A CaptionBubbleModel is owned by the
// CaptionBubbleControllerViews. It is created when transcriptions from a new
// media stream are received and exists until the audio stream ends for that
// stream.
void SetModel(CaptionBubbleModel* model);
// Changes the caption style of the caption bubble.
void UpdateCaptionStyle(std::optional<ui::CaptionStyle> caption_style);
views::Label* GetLabelForTesting();
views::ScrollView* GetScrollViewForTesting();
views::Label* GetDownloadProgressLabelForTesting();
views::Label* GetScrollLockLabelForTesting();
bool IsGenericErrorMessageVisibleForTesting() const;
views::Button* GetCloseButtonForTesting();
views::Button* GetBackToTabButtonForTesting();
views::MdTextButton* GetScrollLockButtonForTesting();
views::View* GetHeaderForTesting();
TranslationViewWrapperBase* GetTranslationViewWrapperForTesting();
void SetNewFontListGetterForTesting(NewFontListGetter callback);
void SetCaptionBubbleStyle();
#if BUILDFLAG(IS_WIN)
void OnContentSettingsLinkClicked();
#endif
void UpdateControlsVisibility(bool show_controls);
void OnMouseEnteredOrExitedWindow(bool entered);
void SetTitleTextForTesting(const std::u16string title_text) {
title_->SetText(title_text);
}
protected:
// views::BubbleDialogDelegateView:
void Init() override;
void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
views::Widget* widget) const override;
bool ShouldShowCloseButton() const override;
std::unique_ptr<views::FrameView> CreateFrameView(
views::Widget* widget) override;
gfx::Rect GetBubbleBounds() override;
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
std::u16string GetAccessibleWindowTitle() const override;
void OnThemeChanged() override;
private:
friend class CaptionBubbleControllerViewsTest;
friend class CaptionBubbleModel;
FRIEND_TEST_ALL_PREFIXES(CaptionBubbleControllerViewsTest,
AccessibleProperties);
void BackToTabButtonPressed();
void CloseButtonPressed();
void ExpandOrCollapseButtonPressed();
void SwapButtons(views::Button* first_button,
views::Button* second_button,
bool show_first_button);
// TranslationViewWrapperBase::Delegate:
void CaptionSettingsButtonPressed() override;
void ScrollLockButtonPressed();
// Called by CaptionBubbleModel to notify this object that the model's text
// has changed. Sets the text of the caption bubble to the model's text.
void OnTextChanged();
// Called by CaptionBubbleModel to notify this object that the model's
// download progress text has changed. Sets the text of the caption bubble to
// the model's download progress text.
void OnDownloadProgressTextChanged();
void OnLanguagePackInstalled();
// Called by CaptionBubbleModel to notify this object that the model's
// auto-detected language has changed.
void OnAutoDetectedLanguageChanged();
// Used to prevent propagating theme changes when no theme colors have
// changed. Returns whether the caption theme colors have changed since the
// last time this function was called.
bool ThemeColorsChanged();
// Called by CaptionBubbleModel to notify this object that the model's error
// state has changed. Makes the caption bubble display an error message if
// the model has an error, otherwise displays the latest text.
void OnErrorChanged(CaptionBubbleErrorType error_type,
OnErrorClickedCallback callback,
OnDoNotShowAgainClickedCallback error_silenced_callback);
// The caption bubble manages its own visibility based on whether there's
// space for it to be shown, and if it has an error or text to display.
void UpdateBubbleVisibility();
void UpdateBubbleAndTitleVisibility();
// For the provided line index, gets the corresponding rendered line in the
// label and returns the text position of the first character of that line.
// Returns the same value regardless of whether the label is visible or not.
size_t GetTextIndexOfLineInLabel(size_t line) const;
// Returns the number of lines in the caption bubble label that are rendered.
size_t GetNumLinesInLabel() const;
int GetNumLinesVisible();
// Internal service methods.
void MaybeScrollToBottom();
void UpdateContentSize();
void Redraw();
void ShowInactive();
void Hide();
// The following methods set the caption bubble style based on the user's
// preferences, which are stored in `caption_style_`.
double GetTextScaleFactor();
const gfx::FontList GetFontList(int font_size);
void SetTextSizeAndFontFamily();
void SetTextColor();
void SetBackgroundColor();
// TranslationViewWrapperBase::Delegate:
void OnLanguageChanged(const std::string& display_language) override;
void UpdateLanguageDirection(const std::string& display_language) override;
// Places the bubble at the bottom center of the context widget for the active
// model, ensuring that it's positioned where the user will spot it. If there
// are multiple browser windows open, and the user plays media on the second
// window, the caption bubble will show up in the bottom center of the second
// window, which is where the user is already looking. It also ensures that
// the caption bubble will appear in the right workspace if a user has Chrome
// windows open on multiple workspaces. This method has no effect if the
// active model has changed between when it was posted and executed, which
// is ensured by passing the active model's id as |model_id|.
void RepositionInContextRect(CaptionBubbleModel::Id model_id,
const gfx::Rect& context_rect);
void AdjustPosition(CaptionBubbleModel::Id model_id,
const gfx::Rect& context_rect);
void MediaFoundationErrorCheckboxPressed();
bool HasMediaFoundationError();
void LogSessionEvent(SessionEvent event);
std::vector<raw_ptr<views::View, VectorExperimental>> GetButtons();
void OnTitleTextChanged();
void UpdateAccessibleName();
bool IsScrollabilityEnabled() const;
void ResetScrollIfLocked(gfx::PointF current_offset,
views::ScrollView* scrollable);
// Unowned. Owned by views hierarchy.
raw_ptr<CaptionBubbleLabel> label_;
raw_ptr<CaptionBubbleScrollView> scrollable_;
raw_ptr<views::Label> title_;
raw_ptr<views::Label> generic_error_text_;
raw_ptr<views::Label> download_progress_label_;
raw_ptr<ScrollLockButton> scroll_lock_button_;
raw_ptr<views::View> header_container_;
raw_ptr<views::View> left_header_container_;
raw_ptr<views::View> translate_header_container_;
raw_ptr<views::ImageView> generic_error_icon_;
raw_ptr<views::View> generic_error_message_;
raw_ptr<views::ImageButton> back_to_tab_button_;
raw_ptr<views::ImageButton> close_button_;
raw_ptr<views::ImageButton> expand_button_;
raw_ptr<views::ImageButton> collapse_button_;
raw_ptr<CaptionBubbleFrameView> frame_;
#if BUILDFLAG(IS_WIN)
raw_ptr<views::StyledLabel> media_foundation_renderer_error_text_;
raw_ptr<views::ImageView> media_foundation_renderer_error_icon_;
raw_ptr<views::View> media_foundation_renderer_error_message_;
// Checkbox the user can use to indicate whether to silence the error message
// for the origin.
raw_ptr<views::Checkbox> media_foundation_renderer_error_checkbox_ = nullptr;
#endif
std::optional<ui::CaptionStyle> caption_style_;
raw_ptr<CaptionBubbleModel> model_ = nullptr;
const raw_ptr<CaptionBubbleSettings> caption_bubble_settings_;
std::unique_ptr<TranslationViewWrapperBase> translation_view_wrapper_;
OnErrorClickedCallback error_clicked_callback_;
OnDoNotShowAgainClickedCallback error_silenced_callback_;
base::ScopedClosureRunner destroyed_callback_;
const std::string application_locale_;
// Whether the caption bubble is expanded to show more lines of text.
bool is_expanded_;
bool has_been_shown_ = false;
// Used to determine whether to propagate theme changes to the widget.
SkColor text_color_ = gfx::kPlaceholderColor;
SkColor icon_color_ = gfx::kPlaceholderColor;
SkColor icon_disabled_color_ = gfx::kPlaceholderColor;
SkColor link_color_ = gfx::kPlaceholderColor;
SkColor checkbox_color_ = gfx::kPlaceholderColor;
SkColor background_color_ = gfx::kPlaceholderColor;
gfx::SlideAnimation controls_animation_;
bool render_active_ = false;
bool mouse_inside_window_ = false;
std::unique_ptr<CaptionBubbleEventObserver> caption_bubble_event_observer_;
base::CallbackListSubscription title_text_changed_callback_;
NewFontListGetter new_font_list_getter_;
base::WeakPtrFactory<CaptionBubble> weak_ptr_factory_{this};
};
BEGIN_VIEW_BUILDER(/* no export */,
CaptionBubble,
views::BubbleDialogDelegateView)
END_VIEW_BUILDER
} // namespace captions
DEFINE_VIEW_BUILDER(/* no export */, captions::CaptionBubble)
#endif // COMPONENTS_LIVE_CAPTION_VIEWS_CAPTION_BUBBLE_H_