Commit ae662d34 authored by Ionel Popescu's avatar Ionel Popescu Committed by Commit Bot

Add pre-macOS 10.15 support for the eye dropper.

This CL adds eye dropper support for pre-macOS 10.15 due to
NSColorSampler not being supported.

The pre-macOS 10.15 support is added by splitting the current aura
specific implementation.

Since the eye dropper can currently be opened from the color picker
which is a popup[1], there is a need to add Mac specific support for:
- moving the view to the front of the screen list within the popup level
- event monitor since the popup had already installed one to be able to
close when the user clicked outside of its bounds

[1]: https://source.chromium.org/chromium/chromium/src/+/master:content/app_shim_remote_cocoa/popup_window_mac.mm

Bug: 992297
Change-Id: I5de7b21845ef8a4e45243d257fa7a8a9af4a193f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2405302
Commit-Queue: Ionel Popescu <iopopesc@microsoft.com>
Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806368}
parent 6e4034d6
...@@ -210,7 +210,6 @@ ...@@ -210,7 +210,6 @@
#endif // OS_CHROMEOS #endif // OS_CHROMEOS
#if defined(OS_MAC) #if defined(OS_MAC)
#include "base/mac/mac_util.h"
#include "chrome/browser/ui/browser_dialogs.h" #include "chrome/browser/ui/browser_dialogs.h"
#endif // OS_MAC #endif // OS_MAC
...@@ -6530,15 +6529,6 @@ bool SkipConditionalFeatureEntry(const flags_ui::FlagsStorage* storage, ...@@ -6530,15 +6529,6 @@ bool SkipConditionalFeatureEntry(const flags_ui::FlagsStorage* storage,
} }
#endif // OS_ANDROID #endif // OS_ANDROID
#if defined(OS_MAC)
// The Eye Dropper relies on the NSColorSampler API, which is available
// starting with macOS 10.15.
if (!strcmp("color-picker-eye-dropper", entry.internal_name) &&
!base::mac::IsAtLeastOS10_15()) {
return true;
}
#endif // OS_MAC
if (flags::IsFlagExpired(storage, entry.internal_name)) if (flags::IsFlagExpired(storage, entry.internal_name))
return true; return true;
......
...@@ -1288,6 +1288,8 @@ static_library("ui") { ...@@ -1288,6 +1288,8 @@ static_library("ui") {
"unload_controller.h", "unload_controller.h",
"views/eye_dropper/eye_dropper.cc", "views/eye_dropper/eye_dropper.cc",
"views/eye_dropper/eye_dropper.h", "views/eye_dropper/eye_dropper.h",
"views/eye_dropper/eye_dropper_view.cc",
"views/eye_dropper/eye_dropper_view.h",
"views/eye_dropper/eye_dropper_view_mac.h", "views/eye_dropper/eye_dropper_view_mac.h",
"views/eye_dropper/eye_dropper_view_mac.mm", "views/eye_dropper/eye_dropper_view_mac.mm",
"webui/app_launcher_login_handler.cc", "webui/app_launcher_login_handler.cc",
...@@ -4186,7 +4188,6 @@ static_library("ui") { ...@@ -4186,7 +4188,6 @@ static_library("ui") {
"views/apps/shaped_app_window_targeter.h", "views/apps/shaped_app_window_targeter.h",
"views/dropdown_bar_host_aura.cc", "views/dropdown_bar_host_aura.cc",
"views/eye_dropper/eye_dropper_view_aura.cc", "views/eye_dropper/eye_dropper_view_aura.cc",
"views/eye_dropper/eye_dropper_view_aura.h",
"views/renderer_context_menu/render_view_context_menu_views.cc", "views/renderer_context_menu/render_view_context_menu_views.cc",
"views/renderer_context_menu/render_view_context_menu_views.h", "views/renderer_context_menu/render_view_context_menu_views.h",
"views/tab_contents/chrome_web_contents_view_delegate_views.cc", "views/tab_contents/chrome_web_contents_view_delegate_views.cc",
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
#include "ui/display/display_switches.h" #include "ui/display/display_switches.h"
#if defined(OS_WIN) #if defined(OS_WIN)
#include "chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.h" #include "chrome/browser/ui/views/eye_dropper/eye_dropper_view.h"
#endif #endif
class EyeDropperBrowserTest : public UiBrowserTest, class EyeDropperBrowserTest : public UiBrowserTest,
...@@ -46,7 +46,7 @@ class EyeDropperBrowserTest : public UiBrowserTest, ...@@ -46,7 +46,7 @@ class EyeDropperBrowserTest : public UiBrowserTest,
return false; return false;
views::Widget* widget = views::Widget* widget =
static_cast<EyeDropperViewAura*>(eye_dropper_.get())->GetWidget(); static_cast<EyeDropperView*>(eye_dropper_.get())->GetWidget();
auto* test_info = testing::UnitTest::GetInstance()->current_test_info(); auto* test_info = testing::UnitTest::GetInstance()->current_test_info();
const std::string screenshot_name = const std::string screenshot_name =
base::StrCat({test_info->test_case_name(), "_", test_info->name()}); base::StrCat({test_info->test_case_name(), "_", test_info->name()});
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/eye_dropper/eye_dropper_view.h"
#include "base/time/time.h"
#include "chrome/browser/ui/views/eye_dropper/eye_dropper.h"
#include "content/public/browser/desktop_capture.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/display/screen.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/widget/widget.h"
class EyeDropperView::ViewPositionHandler {
public:
explicit ViewPositionHandler(EyeDropperView* owner);
ViewPositionHandler(const ViewPositionHandler&) = delete;
ViewPositionHandler& operator=(const ViewPositionHandler&) = delete;
~ViewPositionHandler();
private:
void UpdateViewPosition();
// Timer used for updating the window location.
base::RepeatingTimer timer_;
EyeDropperView* owner_;
};
EyeDropperView::ViewPositionHandler::ViewPositionHandler(EyeDropperView* owner)
: owner_(owner) {
// Use a value close to the refresh rate @60hz.
// TODO(iopopesc): Use SetCapture instead of a timer when support for
// activating the eye dropper without closing the color popup is added.
timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(16), this,
&EyeDropperView::ViewPositionHandler::UpdateViewPosition);
}
EyeDropperView::ViewPositionHandler::~ViewPositionHandler() {
timer_.AbandonAndStop();
}
void EyeDropperView::ViewPositionHandler::UpdateViewPosition() {
owner_->UpdatePosition();
}
class EyeDropperView::ScreenCapturer
: public webrtc::DesktopCapturer::Callback {
public:
ScreenCapturer();
ScreenCapturer(const ScreenCapturer&) = delete;
ScreenCapturer& operator=(const ScreenCapturer&) = delete;
~ScreenCapturer() override = default;
// webrtc::DesktopCapturer::Callback:
void OnCaptureResult(webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) override;
SkBitmap GetBitmap() const;
private:
std::unique_ptr<webrtc::DesktopCapturer> capturer_;
SkBitmap frame_;
};
EyeDropperView::ScreenCapturer::ScreenCapturer() {
// TODO(iopopesc): Update the captured frame after a period of time to match
// latest content on screen.
capturer_ = content::desktop_capture::CreateScreenCapturer();
capturer_->Start(this);
capturer_->CaptureFrame();
}
void EyeDropperView::ScreenCapturer::OnCaptureResult(
webrtc::DesktopCapturer::Result result,
std::unique_ptr<webrtc::DesktopFrame> frame) {
if (result != webrtc::DesktopCapturer::Result::SUCCESS)
return;
frame_.allocN32Pixels(frame->size().width(), frame->size().height(), true);
memcpy(frame_.getAddr32(0, 0), frame->data(),
frame->size().height() * frame->stride());
frame_.setImmutable();
}
SkBitmap EyeDropperView::ScreenCapturer::GetBitmap() const {
return frame_;
}
EyeDropperView::EyeDropperView(content::RenderFrameHost* frame,
content::EyeDropperListener* listener)
: render_frame_host_(frame),
listener_(listener),
view_position_handler_(std::make_unique<ViewPositionHandler>(this)),
screen_capturer_(std::make_unique<ScreenCapturer>()) {
SetModalType(ui::MODAL_TYPE_WINDOW);
SetOwnedByWidget(false);
SetPreferredSize(GetSize());
views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
// Use software compositing to prevent situations when the widget is not
// translucent when moved fast.
// TODO(iopopesc): Investigate if this is a compositor bug or this is indeed
// an intentional limitation.
params.force_software_compositing = true;
params.z_order = ui::ZOrderLevel::kFloatingWindow;
params.name = "MagnifierHost";
params.parent = content::WebContents::FromRenderFrameHost(render_frame_host_)
->GetNativeView();
params.delegate = this;
views::Widget* widget = new views::Widget();
widget->Init(std::move(params));
widget->SetContentsView(this);
MoveViewToFront();
HideCursor();
pre_dispatch_handler_ = std::make_unique<PreEventDispatchHandler>(this);
widget->Show();
}
EyeDropperView::~EyeDropperView() {
if (GetWidget())
GetWidget()->CloseNow();
}
void EyeDropperView::OnPaint(gfx::Canvas* view_canvas) {
if (screen_capturer_->GetBitmap().drawsNothing())
return;
const float diameter = GetDiameter();
constexpr float kPixelSize = 10;
const gfx::Size padding((size().width() - diameter) / 2,
(size().height() - diameter) / 2);
if (GetWidget()->IsTranslucentWindowOpacitySupported()) {
// Clip circle for magnified projection only when the widget
// supports translucency.
SkPath clip_path;
clip_path.addOval(SkRect::MakeXYWH(padding.width(), padding.height(),
diameter, diameter));
clip_path.close();
view_canvas->ClipPath(clip_path, true);
}
// Project pixels.
const int pixel_count = diameter / kPixelSize;
const SkBitmap frame = screen_capturer_->GetBitmap();
// The captured frame is not scaled so we need to use widget's bounds in
// pixels to have the magnified region match cursor position.
const gfx::Point center_position =
display::Screen::GetScreen()
->DIPToScreenRectInWindow(GetWidget()->GetNativeWindow(),
GetWidget()->GetWindowBoundsInScreen())
.CenterPoint();
view_canvas->DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(frame),
center_position.x() - pixel_count / 2,
center_position.y() - pixel_count / 2, pixel_count,
pixel_count, padding.width(), padding.height(),
diameter, diameter, false);
// Store the pixel color under the cursor as it is the last color seen
// by the user before selection.
selected_color_ = frame.getColor(center_position.x(), center_position.y());
// Paint grid.
cc::PaintFlags flags;
flags.setStrokeWidth(1);
flags.setStyle(cc::PaintFlags::kStroke_Style);
// TODO(iopopesc): Get all colors from theming object.
flags.setColor(SK_ColorGRAY);
for (int i = 0; i < pixel_count; ++i) {
view_canvas->DrawLine(
gfx::PointF(padding.width() + i * kPixelSize, padding.height()),
gfx::PointF(padding.width() + i * kPixelSize,
size().height() - padding.height()),
flags);
view_canvas->DrawLine(
gfx::PointF(padding.width(), padding.height() + i * kPixelSize),
gfx::PointF(size().width() - padding.width(),
padding.height() + i * kPixelSize),
flags);
}
// Paint central pixel in red.
gfx::RectF pixel((size().width() - kPixelSize) / 2,
(size().height() - kPixelSize) / 2, kPixelSize, kPixelSize);
flags.setColor(SK_ColorRED);
view_canvas->DrawRect(pixel, flags);
// Paint outline.
flags.setStrokeWidth(2);
flags.setColor(SK_ColorDKGRAY);
flags.setAntiAlias(true);
if (GetWidget()->IsTranslucentWindowOpacitySupported()) {
view_canvas->DrawCircle(
gfx::PointF(size().width() / 2, size().height() / 2), diameter / 2,
flags);
} else {
view_canvas->DrawRect(bounds(), flags);
}
OnPaintBorder(view_canvas);
}
void EyeDropperView::WindowClosing() {
ShowCursor();
pre_dispatch_handler_.reset();
}
void EyeDropperView::OnWidgetMove() {
// Trigger a repaint since because the widget was moved, the content of the
// view needs to be updated.
SchedulePaint();
}
void EyeDropperView::UpdatePosition() {
if (screen_capturer_->GetBitmap().drawsNothing() || !GetWidget())
return;
gfx::Point cursor_position =
display::Screen::GetScreen()->GetCursorScreenPoint();
if (cursor_position == GetWidget()->GetWindowBoundsInScreen().CenterPoint())
return;
GetWidget()->SetBounds(
gfx::Rect(gfx::Point(cursor_position.x() - size().width() / 2,
cursor_position.y() - size().height() / 2),
size()));
}
void EyeDropperView::OnColorSelected() {
if (!selected_color_.has_value()) {
listener_->ColorSelectionCanceled();
return;
}
// Use the last selected color and notify listener.
listener_->ColorSelected(selected_color_.value());
}
...@@ -2,27 +2,31 @@ ...@@ -2,27 +2,31 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_AURA_H_ #ifndef CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_AURA_H_ #define CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_H_
#include <memory> #include <memory>
#include "base/optional.h" #include "base/optional.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "build/build_config.h"
#include "content/public/browser/eye_dropper.h" #include "content/public/browser/eye_dropper.h"
#include "content/public/browser/eye_dropper_listener.h" #include "content/public/browser/eye_dropper_listener.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point.h"
#include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/widget_delegate.h"
class EyeDropperViewAura : public content::EyeDropper, // EyeDropperView is used on Aura platforms and on the Mac before 10.15.
public views::WidgetDelegateView { // Starting with macOS 10.15, EyeDropperViewMac is used as it relies on the new
// NSColorSampler API.
class EyeDropperView : public content::EyeDropper,
public views::WidgetDelegateView {
public: public:
EyeDropperViewAura(content::RenderFrameHost* frame, EyeDropperView(content::RenderFrameHost* frame,
content::EyeDropperListener* listener); content::EyeDropperListener* listener);
EyeDropperViewAura(const EyeDropperViewAura&) = delete; EyeDropperView(const EyeDropperView&) = delete;
EyeDropperViewAura& operator=(const EyeDropperViewAura&) = delete; EyeDropperView& operator=(const EyeDropperView&) = delete;
~EyeDropperViewAura() override; ~EyeDropperView() override;
protected: protected:
// views::WidgetDelegateView: // views::WidgetDelegateView:
...@@ -31,18 +35,42 @@ class EyeDropperViewAura : public content::EyeDropper, ...@@ -31,18 +35,42 @@ class EyeDropperViewAura : public content::EyeDropper,
void OnWidgetMove() override; void OnWidgetMove() override;
private: private:
class PreEventDispatchHandler;
class ViewPositionHandler; class ViewPositionHandler;
class ScreenCapturer; class ScreenCapturer;
class PreEventDispatchHandler : public ui::EventHandler {
public:
explicit PreEventDispatchHandler(EyeDropperView* view);
PreEventDispatchHandler(const PreEventDispatchHandler&) = delete;
PreEventDispatchHandler& operator=(const PreEventDispatchHandler&) = delete;
~PreEventDispatchHandler() override;
private:
void OnMouseEvent(ui::MouseEvent* event) override;
EyeDropperView* view_;
#if defined(OS_MAC)
id clickEventTap_;
id notificationObserver_;
#endif
};
// Moves the view to the cursor position. // Moves the view to the cursor position.
void UpdatePosition(); void UpdatePosition();
void MoveViewToFront();
void HideCursor();
void ShowCursor();
// Handles color selection and notifies the listener. // Handles color selection and notifies the listener.
void OnColorSelected(); void OnColorSelected();
content::RenderFrameHost* render_frame_host_; content::RenderFrameHost* render_frame_host_;
gfx::Size GetSize() const;
float GetDiameter() const;
// Receives the color selection. // Receives the color selection.
content::EyeDropperListener* listener_; content::EyeDropperListener* listener_;
...@@ -52,4 +80,4 @@ class EyeDropperViewAura : public content::EyeDropper, ...@@ -52,4 +80,4 @@ class EyeDropperViewAura : public content::EyeDropper,
base::Optional<SkColor> selected_color_; base::Optional<SkColor> selected_color_;
}; };
#endif // CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_AURA_H_ #endif // CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_H_
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include "chrome/browser/ui/views/eye_dropper/eye_dropper_view.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "skia/ext/skia_utils_mac.h" #include "skia/ext/skia_utils_mac.h"
...@@ -27,11 +28,84 @@ EyeDropperViewMac::EyeDropperViewMac(content::EyeDropperListener* listener) ...@@ -27,11 +28,84 @@ EyeDropperViewMac::EyeDropperViewMac(content::EyeDropperListener* listener)
EyeDropperViewMac::~EyeDropperViewMac() {} EyeDropperViewMac::~EyeDropperViewMac() {}
EyeDropperView::PreEventDispatchHandler::PreEventDispatchHandler(
EyeDropperView* view)
: view_(view) {
// Ensure that this handler is called before color popup handler.
clickEventTap_ = [NSEvent
addLocalMonitorForEventsMatchingMask:NSAnyEventMask
handler:^NSEvent*(NSEvent* event) {
NSEventType eventType = [event type];
if (eventType == NSLeftMouseDown ||
eventType == NSRightMouseDown) {
view_->OnColorSelected();
return nil;
}
return event;
}];
// Needed because the local event monitor doesn't see the click on the
// menubar.
NSNotificationCenter* notificationCenter =
[NSNotificationCenter defaultCenter];
notificationObserver_ =
[notificationCenter addObserverForName:NSMenuDidBeginTrackingNotification
object:[NSApp mainMenu]
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification* note) {
view_->OnColorSelected();
}];
}
EyeDropperView::PreEventDispatchHandler::~PreEventDispatchHandler() {
if (clickEventTap_) {
[NSEvent removeMonitor:clickEventTap_];
clickEventTap_ = nil;
}
if (notificationObserver_) {
NSNotificationCenter* notificationCenter =
[NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:notificationObserver_];
notificationObserver_ = nil;
}
}
void EyeDropperView::PreEventDispatchHandler::OnMouseEvent(
ui::MouseEvent* event) {
// The event monitor already provides a handler.
}
void EyeDropperView::MoveViewToFront() {
// Moves the window to the front of the screen list within the popup level
// since the eye dropper can be opened from the color picker.
NSWindow* window = GetWidget()->GetNativeWindow().GetNativeNSWindow();
[window setLevel:NSPopUpMenuWindowLevel];
[window makeKeyAndOrderFront:nil];
}
void EyeDropperView::HideCursor() {
[NSCursor hide];
}
void EyeDropperView::ShowCursor() {
[NSCursor unhide];
}
gfx::Size EyeDropperView::GetSize() const {
return gfx::Size(70, 70);
}
float EyeDropperView::GetDiameter() const {
return 70;
}
std::unique_ptr<content::EyeDropper> ShowEyeDropper( std::unique_ptr<content::EyeDropper> ShowEyeDropper(
content::RenderFrameHost* frame, content::RenderFrameHost* frame,
content::EyeDropperListener* listener) { content::EyeDropperListener* listener) {
if (@available(macOS 10.15, *)) { if (@available(macOS 10.15, *)) {
return std::make_unique<EyeDropperViewMac>(listener); return std::make_unique<EyeDropperViewMac>(listener);
} }
return nullptr; return std::make_unique<EyeDropperView>(frame, listener);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment