blob: 8381e91a72d0951afb188422f539a8c423275d03 [file] [log] [blame]
// Copyright 2021 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/projector/projector_annotation_tray.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/tray_background_view_catalog.h"
#include "ash/projector/projector_controller_impl.h"
#include "ash/projector/ui/projector_color_button.h"
#include "ash/public/cpp/projector/annotator_tool.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/tray/hover_highlight_view.h"
#include "ash/system/tray/tray_bubble_wrapper.h"
#include "ash/system/tray/tray_container.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_service.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/event.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
// Margins between the title view and the edges around it (dp).
constexpr int kPaddingBetweenBottomAndLastTrayItem = 4;
// Width of the bubble itself (dp).
constexpr int kBubbleWidth = 196;
// Insets for the views (dp).
constexpr auto kPenViewPadding = gfx::Insets::TLBR(4, 16, 0, 16);
// Spacing between buttons (dp).
constexpr int kButtonsPadding = 12;
// Size of menu rows.
constexpr int kMenuRowHeight = 48;
// Color selection buttons.
constexpr int kColorButtonColorViewSize = 16;
constexpr int kColorButtonViewRadius = 28;
constexpr SkColor kPenColors[] = {
kProjectorMagentaPenColor, kProjectorBluePenColor, kProjectorYellowPenColor,
kProjectorRedPenColor};
// TODO(b/201664243): Use AnnotatorToolType.
enum ProjectorTool { kToolNone, kToolPen };
bool IsAnnotatorEnabled() {
auto* controller = Shell::Get()->projector_controller();
// `controller` may not be available yet as the `ProjectorAnnotationTray`
// is created before it.
return controller ? controller->IsAnnotatorEnabled() : false;
}
ProjectorTool GetCurrentTool() {
return IsAnnotatorEnabled() ? kToolPen : kToolNone;
}
const gfx::VectorIcon& GetIconForTool(ProjectorTool tool, SkColor color) {
switch (tool) {
case kToolNone:
return kPaletteTrayIconProjectorIcon;
case kToolPen:
switch (color) {
case kProjectorMagentaPenColor:
return kPaletteTrayIconProjectorMagentaIcon;
case kProjectorBluePenColor:
return kPaletteTrayIconProjectorBlueIcon;
case kProjectorRedPenColor:
return kPaletteTrayIconProjectorRedIcon;
case kProjectorYellowPenColor:
return kPaletteTrayIconProjectorYellowIcon;
}
}
NOTREACHED();
return kPaletteTrayIconProjectorIcon;
}
} // namespace
ProjectorAnnotationTray::ProjectorAnnotationTray(Shelf* shelf)
: TrayBackgroundView(shelf,
TrayBackgroundViewCatalogName::kProjectorAnnotation),
image_view_(
tray_container()->AddChildView(std::make_unique<views::ImageView>())),
pen_view_(nullptr) {
SetCallback(base::BindRepeating(&ProjectorAnnotationTray::OnTrayButtonPressed,
base::Unretained(this)));
// Right click should show the bubble. In tablet mode, long press is
// synonymous with right click, gesture long press must be intercepted via
// `OnGestureEvent()` override, as `views::Button` forces long press to show a
// contextual menu.
SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON |
ui::EF_RIGHT_MOUSE_BUTTON);
image_view_->SetTooltipText(GetTooltip());
image_view_->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
image_view_->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
image_view_->SetPreferredSize(gfx::Size(kTrayItemSize, kTrayItemSize));
ResetTray();
session_observer_.Observe(Shell::Get()->session_controller());
}
ProjectorAnnotationTray::~ProjectorAnnotationTray() = default;
void ProjectorAnnotationTray::OnGestureEvent(ui::GestureEvent* event) {
// Long Press typically is used to show a contextual menu, but because in
// tablet mode tapping the pod is used to toggle a feature, long press is the
// only available way to show the bubble.
// TODO(crbug/1374368): Put this where we handle other button activations,
// once the `views::Button` code allows it.
if (event->details().type() != ui::ET_GESTURE_LONG_PRESS) {
TrayBackgroundView::OnGestureEvent(event);
return;
}
ShowBubble();
}
void ProjectorAnnotationTray::ClickedOutsideBubble() {
CloseBubble();
}
void ProjectorAnnotationTray::UpdateTrayItemColor(bool is_active) {
SetIconImage(is_active);
}
std::u16string ProjectorAnnotationTray::GetAccessibleNameForTray() {
std::u16string enabled_state = l10n_util::GetStringUTF16(
GetCurrentTool() == kToolNone
? IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_OFF_STATE
: IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ON_STATE);
return l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ACCESSIBLE_TITLE,
enabled_state);
}
void ProjectorAnnotationTray::HandleLocaleChange() {}
void ProjectorAnnotationTray::HideBubbleWithView(
const TrayBubbleView* bubble_view) {
if (bubble_->bubble_view() == bubble_view)
CloseBubble();
}
void ProjectorAnnotationTray::CloseBubble() {
pen_view_ = nullptr;
bubble_.reset();
// Annotator can be enabled after closing the bubble so set the activity state
// according to it.
SetIsActive(IsAnnotatorEnabled());
shelf()->UpdateAutoHideState();
}
void ProjectorAnnotationTray::ShowBubble() {
if (bubble_)
return;
DCHECK(tray_container());
TrayBubbleView::InitParams init_params = CreateInitParamsForTrayBubble(this);
init_params.preferred_width = kBubbleWidth;
// Create and customize bubble view.
auto bubble_view = std::make_unique<TrayBubbleView>(init_params);
bubble_view->SetBorder(views::CreateEmptyBorder(
gfx::Insets::TLBR(0, 0, kPaddingBetweenBottomAndLastTrayItem, 0)));
// Add drawing tools
{
auto* marker_view_container =
bubble_view->AddChildView(std::make_unique<views::View>());
auto box_layout = std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal, kPenViewPadding,
kButtonsPadding);
box_layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
box_layout->set_minimum_cross_axis_size(kMenuRowHeight);
marker_view_container->SetLayoutManager(std::move(box_layout));
for (SkColor color : kPenColors) {
auto* color_button = marker_view_container->AddChildView(
std::make_unique<ProjectorColorButton>(
base::BindRepeating(&ProjectorAnnotationTray::OnPenColorPressed,
base::Unretained(this), color),
color, kColorButtonColorViewSize, kColorButtonViewRadius,
l10n_util::GetStringUTF16(GetAccessibleNameForColor(color))));
color_button->SetToggled(current_pen_color_ == color);
}
}
// Show the bubble.
bubble_ = std::make_unique<TrayBubbleWrapper>(this);
bubble_->ShowBubble(std::move(bubble_view));
SetIsActive(true);
}
TrayBubbleView* ProjectorAnnotationTray::GetBubbleView() {
return bubble_ ? bubble_->bubble_view() : nullptr;
}
views::Widget* ProjectorAnnotationTray::GetBubbleWidget() const {
return bubble_ ? bubble_->GetBubbleWidget() : nullptr;
}
void ProjectorAnnotationTray::OnThemeChanged() {
TrayBackgroundView::OnThemeChanged();
UpdateIcon();
}
void ProjectorAnnotationTray::HideBubble(const TrayBubbleView* bubble_view) {
CloseBubble();
}
void ProjectorAnnotationTray::OnActiveUserPrefServiceChanged(
PrefService* pref_service) {
const uint64_t color =
pref_service->GetUint64(prefs::kProjectorAnnotatorLastUsedMarkerColor);
current_pen_color_ = !color ? kProjectorDefaultPenColor : color;
}
void ProjectorAnnotationTray::HideAnnotationTray() {
SetVisiblePreferred(false);
UpdateIcon();
PrefService* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
pref_service->SetUint64(prefs::kProjectorAnnotatorLastUsedMarkerColor,
current_pen_color_);
ResetTray();
}
void ProjectorAnnotationTray::OnTrayButtonPressed(const ui::Event& event) {
// NOTE: Long press not supported via the `views::Button` callback, it
// is handled via OnGestureEvent override.
if (event.IsMouseEvent() && event.AsMouseEvent()->IsRightMouseButton()) {
ShowBubble();
return;
}
ToggleAnnotator();
}
void ProjectorAnnotationTray::SetTrayEnabled(bool enabled) {
SetEnabled(enabled);
if (enabled)
return;
// For disabled state, set icon color to kIconColorPrimary with 30% opacity.
SkColor disabled_icon_color =
SkColorSetA(AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary),
0x4D);
image_view_->SetImage(gfx::CreateVectorIcon(kPaletteTrayIconProjectorIcon,
disabled_icon_color));
image_view_->SetTooltipText(l10n_util::GetStringUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_UNAVAILABLE));
}
void ProjectorAnnotationTray::ToggleAnnotator() {
if (GetCurrentTool() == kToolNone) {
EnableAnnotatorWithPenColor();
} else {
DeactivateActiveTool();
}
if (bubble_) {
CloseBubble();
}
UpdateIcon();
}
void ProjectorAnnotationTray::EnableAnnotatorWithPenColor() {
auto* controller = Shell::Get()->projector_controller();
DCHECK(controller);
AnnotatorTool tool;
tool.color = current_pen_color_;
controller->SetAnnotatorTool(tool);
controller->EnableAnnotatorTool();
}
void ProjectorAnnotationTray::DeactivateActiveTool() {
auto* controller = Shell::Get()->projector_controller();
DCHECK(controller);
controller->ResetTools();
}
void ProjectorAnnotationTray::UpdateIcon() {
bool annotator_toggled = false;
if (is_active() != IsAnnotatorEnabled()) {
SetIsActive(IsAnnotatorEnabled());
annotator_toggled = true;
}
// Only sets the image if Jelly is not enabled or if the annotator was not
// toggled, since `UpdateTrayItemColor()` will be called in `SetIsActive()` to
// set the image for Jelly only when active state changes.
if (!chromeos::features::IsJellyEnabled()) {
image_view_->SetImage(gfx::CreateVectorIcon(
GetIconForTool(GetCurrentTool(), current_pen_color_),
AshColorProvider::Get()->GetContentLayerColor(
AshColorProvider::ContentLayerType::kIconColorPrimary)));
} else if (!annotator_toggled) {
SetIconImage(is_active());
}
image_view_->SetTooltipText(GetTooltip());
}
void ProjectorAnnotationTray::OnPenColorPressed(SkColor color) {
current_pen_color_ = color;
EnableAnnotatorWithPenColor();
CloseBubble();
UpdateIcon();
}
int ProjectorAnnotationTray::GetAccessibleNameForColor(SkColor color) {
switch (color) {
case kProjectorRedPenColor:
return IDS_RED_COLOR_BUTTON;
case kProjectorBluePenColor:
return IDS_BLUE_COLOR_BUTTON;
case kProjectorYellowPenColor:
return IDS_YELLOW_COLOR_BUTTON;
case kProjectorMagentaPenColor:
return IDS_MAGENTA_COLOR_BUTTON;
}
NOTREACHED();
return IDS_RED_COLOR_BUTTON;
}
void ProjectorAnnotationTray::ResetTray() {
// Disable the tray icon. It is enabled once the ink canvas is initialized.
SetEnabled(false);
}
std::u16string ProjectorAnnotationTray::GetTooltip() {
std::u16string enabled_state = l10n_util::GetStringUTF16(
GetCurrentTool() == kToolNone
? IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_OFF_STATE
: IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_ON_STATE);
return l10n_util::GetStringFUTF16(
IDS_ASH_STATUS_AREA_PROJECTOR_ANNOTATION_TRAY_TOOLTIP, enabled_state);
}
void ProjectorAnnotationTray::SetIconImage(bool is_active) {
DCHECK(chromeos::features::IsJellyEnabled());
image_view_->SetImage(ui::ImageModel::FromVectorIcon(
GetIconForTool(GetCurrentTool(), current_pen_color_),
is_active ? cros_tokens::kCrosSysSystemOnPrimaryContainer
: cros_tokens::kCrosSysOnSurface));
}
BEGIN_METADATA(ProjectorAnnotationTray)
END_METADATA
} // namespace ash