// 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.
#include "ash/hud_display/hud_display.h"
#include "ash/fast_ink/view_tree_host_root_view.h"
#include "ash/fast_ink/view_tree_host_widget.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/hud_display/graphs_container_view.h"
#include "ash/hud_display/hud_constants.h"
#include "ash/hud_display/hud_header_view.h"
#include "ash/hud_display/hud_properties.h"
#include "ash/hud_display/hud_settings_view.h"
#include "ash/hud_display/tab_strip.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/aura/window.h"
#include "ui/base/hit_test.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/layer.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/native_widget.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace ash {
namespace hud_display {
namespace {
// Header height.
constexpr int kHUDHeaderHeight =
kHUDSettingsIconSize + 2 * kHUDSettingsIconBorder;
// Margin below header.
constexpr int kHUDHeaderMargin = 5;
// Graph height.
constexpr int kHUDGraphHeight = 300;
// Graph width/height including bordering reference lines.
constexpr int kHUDGraphWidthWithReferenceLines =
kHUDGraphWidth + 2 * kHUDGraphReferenceLineWidth;
constexpr int kHUDGraphHeightWithReferenceLines =
kHUDGraphHeight + 2 * kHUDGraphReferenceLineWidth;
// HUD window width.
constexpr int kHUDWidth = kHUDGraphWidthWithReferenceLines + 2 * kHUDInset;
// Top inset + header + header margin + bottom inset. Used to compute the HUD
// window height. Just add the graph height or settings height as appropriate.
constexpr int kHUDFrameHeight =
kHUDInset + kHUDHeaderHeight + kHUDHeaderMargin + kHUDInset;
// HUD window height with graph.
constexpr int kHUDHeightWithGraph =
kHUDFrameHeight + kHUDGraphHeightWithReferenceLines;
views::Widget* g_hud_widget = nullptr;
// True if HUD should be initialized as overlay.
bool g_hud_overlay_mode = true;
// ClientView that return HTNOWHERE by default. A child view can receive event
// by setting kHitTestComponentKey property to HTCLIENT.
class HTClientView : public views::ClientView {
METADATA_HEADER(HTClientView, views::ClientView)
public:
HTClientView(HUDDisplayView* hud_display,
views::Widget* widget,
views::View* contents_view)
: views::ClientView(widget, contents_view), hud_display_(hud_display) {}
HTClientView(const HTClientView&) = delete;
HTClientView& operator=(const HTClientView&) = delete;
~HTClientView() override = default;
// views::ClientView
int NonClientHitTest(const gfx::Point& point) override {
return hud_display_->NonClientHitTest(point);
}
HUDDisplayView* GetHUDDisplayViewForTesting() { return hud_display_; }
private:
raw_ptr<HUDDisplayView> hud_display_;
};
BEGIN_METADATA(HTClientView)
END_METADATA
std::unique_ptr<views::ClientView> MakeClientView(views::Widget* widget) {
auto view = std::make_unique<HUDDisplayView>();
auto* weak_view = view.get();
return std::make_unique<HTClientView>(weak_view, widget, view.release());
}
void InitializeFrameView(views::WidgetDelegate* delegate) {
auto* frame_view = static_cast<NonClientFrameViewAsh*>(
delegate->GetWidget()->non_client_view()->frame_view());
// TODO(oshima): support component type with TYPE_WINDOW_FLAMELESS widget.
if (frame_view)
frame_view->SetFrameEnabled(false);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// HUDDisplayView, public:
BEGIN_METADATA(HUDDisplayView)
END_METADATA
// static
void HUDDisplayView::Destroy() {
delete g_hud_widget;
g_hud_widget = nullptr;
}
// static
void HUDDisplayView::Toggle() {
if (g_hud_widget) {
Destroy();
return;
}
auto delegate = std::make_unique<views::WidgetDelegate>();
delegate->SetClientViewFactory(base::BindOnce(&MakeClientView));
delegate->RegisterWidgetInitializedCallback(
base::BindOnce(&InitializeFrameView, base::Unretained(delegate.get())));
delegate->SetOwnedByWidget(true);
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW);
params.delegate = delegate.release();
params.name = "HUDDisplay";
params.parent = Shell::GetContainer(Shell::GetPrimaryRootWindow(),
kShellWindowId_OverlayContainer);
params.bounds = gfx::Rect(kHUDWidth, kHUDHeightWithGraph);
auto* widget = CreateViewTreeHostWidget(std::move(params));
widget->GetLayer()->SetName("HUDDisplayView");
ViewTreeHostRootView* root_view =
static_cast<ViewTreeHostRootView*>(widget->GetRootView());
root_view->SetIsOverlayCandidate(g_hud_overlay_mode);
root_view->Init(widget->GetNativeView());
widget->Show();
g_hud_widget = widget;
}
// static
bool HUDDisplayView::IsShown() {
return g_hud_widget;
}
HUDDisplayView::HUDDisplayView() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
// Layout:
// ----------------------
// | Header | // Buttons, tabs, controls
// ----------------------
// | | // Data views full-size, z-stacked.
// | Data |
// | |
// ----------------------
// Create two child views for header and data. Vertically stacked.
views::BoxLayout* layout_manager =
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
layout_manager->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kStretch);
header_view_ = AddChildView(std::make_unique<HUDHeaderView>(this));
views::View* data = AddChildView(std::make_unique<views::View>());
// Data view takes the rest of the host view.
layout_manager->SetFlexForView(data, 1, /*use_min_size=*/false);
// Setup header.
header_view_->tab_strip()->AddTabButton(HUDDisplayMode::CPU, u"CPU");
header_view_->tab_strip()->AddTabButton(HUDDisplayMode::MEMORY, u"RAM");
header_view_->tab_strip()->AddTabButton(HUDDisplayMode::FPS, u"FPS");
// Setup data.
data->SetBackground(views::CreateSolidBackground(kHUDBackground));
data->SetBorder(views::CreateEmptyBorder(
gfx::Insets::TLBR(kHUDHeaderMargin, kHUDInset, kHUDInset, kHUDInset)));
// We have two child views z-stacked.
// The bottom one is GraphsContainerView with all the graph lines.
// The top one is settings UI overlay.
data->SetLayoutManager(std::make_unique<views::FillLayout>());
graphs_container_ =
data->AddChildView(std::make_unique<GraphsContainerView>());
settings_view_ = data->AddChildView(std::make_unique<HUDSettingsView>(this));
settings_view_->SetVisible(false);
// CPU display is active by default.
SetDisplayMode(HUDDisplayMode::CPU);
}
HUDDisplayView::~HUDDisplayView() {
DCHECK_CALLED_ON_VALID_SEQUENCE(ui_sequence_checker_);
}
// There is only one button.
void HUDDisplayView::OnSettingsToggle() {
gfx::Rect bounds = g_hud_widget->GetWindowBoundsInScreen();
// Here we are checking the settings visibility before we toggle it. We must
// keep in mind that it is the opposite of what it will be.
bounds.set_height(settings_view_->GetVisible()
? kHUDHeightWithGraph
: kHUDFrameHeight +
settings_view_->GetPreferredSize().height());
g_hud_widget->SetBounds(bounds);
settings_view_->ToggleVisibility();
graphs_container_->SetVisible(!settings_view_->GetVisible());
}
bool HUDDisplayView::IsOverlay() {
return static_cast<ViewTreeHostRootView*>(GetWidget()->GetRootView())
->GetIsOverlayCandidate();
}
void HUDDisplayView::ToggleOverlay() {
g_hud_overlay_mode = !g_hud_overlay_mode;
static_cast<ViewTreeHostRootView*>(GetWidget()->GetRootView())
->SetIsOverlayCandidate(g_hud_overlay_mode);
}
// static
HUDDisplayView* HUDDisplayView::GetForTesting() {
if (!g_hud_widget)
return nullptr;
HTClientView* client_view =
static_cast<HTClientView*>(g_hud_widget->client_view());
if (!client_view)
return nullptr;
return client_view->GetHUDDisplayViewForTesting(); // IN-TEST
}
HUDSettingsView* HUDDisplayView::GetSettingsViewForTesting() {
return settings_view_;
}
void HUDDisplayView::ToggleSettingsForTesting() {
OnSettingsToggle();
}
int HUDDisplayView::NonClientHitTest(const gfx::Point& point) {
const View* view = GetEventHandlerForPoint(point);
if (!view)
return HTNOWHERE;
return view->GetProperty(kHUDClickHandler);
}
void HUDDisplayView::SetDisplayMode(HUDDisplayMode display_mode) {
graphs_container_->SetMode(display_mode);
header_view_->tab_strip()->ActivateTab(display_mode);
}
} // namespace hud_display
} // namespace ash