blob: 2440c447b310252deb0671ede9110b4cb3022bc0 [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.
#include <algorithm>
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "components/eye_dropper/eye_dropper_view.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/focus_change_observer.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/screen.h"
#include "ui/native_window_tracker/native_window_tracker.h"
namespace eye_dropper {
namespace {
gfx::Point ClampToDisplay(const gfx::Point& point) {
gfx::Rect bounds =
display::Screen::Get()->GetDisplayNearestPoint(point).bounds();
return gfx::Point(std::clamp(point.x(), bounds.x(), bounds.right() - 1),
std::clamp(point.y(), bounds.y(), bounds.bottom() - 1));
}
} // namespace
class EyeDropperView::PreEventDispatchHandler::KeyboardHandler
: public ui::EventHandler {
public:
KeyboardHandler(EyeDropperView* view, aura::Window* parent);
KeyboardHandler(const KeyboardHandler&) = delete;
KeyboardHandler& operator=(const KeyboardHandler&) = delete;
~KeyboardHandler() override;
private:
void OnKeyEvent(ui::KeyEvent* event) override;
raw_ptr<EyeDropperView> view_;
raw_ptr<aura::Window> parent_;
std::unique_ptr<ui::NativeWindowTracker> parent_tracker_;
};
EyeDropperView::PreEventDispatchHandler::KeyboardHandler::KeyboardHandler(
EyeDropperView* view,
aura::Window* parent)
: view_(view),
parent_(parent),
parent_tracker_(ui::NativeWindowTracker::Create(parent)) {
// Because the eye dropper is not focused in order to not dismiss the color
// popup, we need to listen for key events on the parent window that has
// focus.
parent_->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
}
EyeDropperView::PreEventDispatchHandler::KeyboardHandler::~KeyboardHandler() {
if (!parent_tracker_->WasNativeWindowDestroyed()) {
parent_->RemovePreTargetHandler(this);
}
}
void EyeDropperView::PreEventDispatchHandler::KeyboardHandler::OnKeyEvent(
ui::KeyEvent* event) {
if (event->type() != ui::EventType::kKeyPressed) {
return;
}
// Move when arrow keys are pressed, move faster if shift is down.
const auto move_by = [&](int x, int y) {
if (event->IsShiftDown()) {
x *= 10;
y *= 10;
}
const auto center =
view_->GetWidget()->GetWindowBoundsInScreen().CenterPoint();
view_->UpdatePosition(ClampToDisplay(center + gfx::Vector2d(x, y)));
};
switch (event->key_code()) {
case ui::VKEY_ESCAPE:
view_->OnColorSelectionCanceled();
break;
case ui::VKEY_RETURN:
case ui::VKEY_SPACE:
view_->OnColorSelected();
break;
case ui::VKEY_UP:
move_by(0, -1);
break;
case ui::VKEY_DOWN:
move_by(0, 1);
break;
case ui::VKEY_LEFT:
move_by(-1, 0);
break;
case ui::VKEY_RIGHT:
move_by(1, 0);
break;
default:
return;
}
event->StopPropagation();
}
class EyeDropperView::PreEventDispatchHandler::FocusObserver
: public aura::client::FocusChangeObserver {
public:
FocusObserver(EyeDropperView* view, aura::Window* parent);
// aura::client::FocusChangeObserver
void OnWindowFocused(aura::Window* gained_focus,
aura::Window* lost_focus) override;
raw_ptr<EyeDropperView> view_;
base::ScopedObservation<aura::client::FocusClient,
aura::client::FocusChangeObserver>
focus_observation_{this};
};
EyeDropperView::PreEventDispatchHandler::FocusObserver::FocusObserver(
EyeDropperView* view,
aura::Window* parent)
: view_(view) {
focus_observation_.Observe(
aura::client::GetFocusClient(parent->GetRootWindow()));
}
void EyeDropperView::PreEventDispatchHandler::FocusObserver::OnWindowFocused(
aura::Window* gained_focus,
aura::Window* lost_focus) {
view_->OnColorSelectionCanceled();
}
EyeDropperView::PreEventDispatchHandler::PreEventDispatchHandler(
EyeDropperView* view,
gfx::NativeView parent)
: view_(view),
keyboard_handler_(std::make_unique<KeyboardHandler>(view, parent)),
focus_observer_(std::make_unique<FocusObserver>(view, parent)) {
// Ensure that this handler is called before color popup handler by using
// a higher priority.
view->GetWidget()->GetNativeWindow()->AddPreTargetHandler(
this, ui::EventTarget::Priority::kSystem);
}
EyeDropperView::PreEventDispatchHandler::~PreEventDispatchHandler() {
view_->GetWidget()->GetNativeWindow()->RemovePreTargetHandler(this);
}
void EyeDropperView::PreEventDispatchHandler::OnMouseEvent(
ui::MouseEvent* event) {
if (event->type() == ui::EventType::kMousePressed) {
// Avoid closing the color popup when the eye dropper is clicked.
event->StopPropagation();
}
if (event->type() == ui::EventType::kMouseReleased) {
view_->OnColorSelected();
event->StopPropagation();
}
}
void EyeDropperView::PreEventDispatchHandler::OnGestureEvent(
ui::GestureEvent* event) {
if (event->type() == ui::EventType::kGestureTap) {
view_->OnColorSelected();
event->StopPropagation();
}
}
void EyeDropperView::PreEventDispatchHandler::OnTouchEvent(
ui::TouchEvent* event) {
if (event->type() == ui::EventType::kTouchPressed) {
// For touch-move, we don't move the center of the EyeDropper to be at the
// touch point, but rather maintain the offset from the first press.
touch_offset_ = event->root_location() -
view_->GetWidget()->GetWindowBoundsInScreen().CenterPoint();
}
if (event->type() == ui::EventType::kTouchMoved) {
// Keep EyeDropper always inside a display, but adjust offset when position
// is clamped.
gfx::Point position = event->root_location() - touch_offset_;
gfx::Point clamped = ClampToDisplay(position);
if (clamped != position) {
touch_offset_ = event->root_location() - clamped;
}
view_->UpdatePosition(std::move(clamped));
}
}
void EyeDropperView::CaptureInput() {
// The eye dropper needs to capture input since it is not activated
// in order to avoid dismissing the color picker.
GetWidget()->GetNativeWindow()->SetCapture();
}
void EyeDropperView::HideCursor() {
auto* cursor_client = aura::client::GetCursorClient(
GetWidget()->GetNativeWindow()->GetRootWindow());
cursor_client->HideCursor();
cursor_client->LockCursor();
}
void EyeDropperView::ShowCursor() {
aura::client::GetCursorClient(GetWidget()->GetNativeWindow()->GetRootWindow())
->UnlockCursor();
}
gfx::Size EyeDropperView::GetSize() const {
return gfx::Size(100, 100);
}
float EyeDropperView::GetDiameter() const {
return 90;
}
} // namespace eye_dropper