1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
|
// Copyright 2018 The Chromium Authors
// 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/omnibox/rounded_omnibox_results_frame.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/layout_constants.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "components/omnibox/common/omnibox_features.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/layout/layout_provider.h"
#if defined(USE_AURA)
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "ui/views/widget/native_widget_aura.h"
#endif
namespace {
// Value from the spec controlling appearance of the shadow.
constexpr int kElevation = 16;
#if !defined(USE_AURA)
struct WidgetEventPair {
raw_ptr<views::Widget> widget;
std::unique_ptr<ui::MouseEvent> event;
};
#if BUILDFLAG(IS_MAC)
views::Widget* GetImmersiveFullscreenWidgetForEvent(
views::View* this_view,
const ui::MouseEvent* this_event) {
const views::Widget* parent_widget = this_view->GetWidget()->parent();
BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(
parent_widget->GetNativeWindow());
// If the results window is not a child of the overlay widget we are not in
// immersive fullscreen.
if (browser_view->overlay_widget() != parent_widget) {
return nullptr;
}
{
// If the event is located in the location bar send the event to the overlay
// widget to handle text selection.
gfx::Point event_location = this_event->location();
views::View::ConvertPointToScreen(this_view, &event_location);
views::View::ConvertPointFromScreen(browser_view->GetLocationBarView(),
&event_location);
if (browser_view->GetLocationBarView()->HitTestPoint(event_location)) {
return browser_view->overlay_widget();
}
}
{
// If the event is located in the content view send the event to the browser
// widget.
gfx::Point event_location = this_event->location();
views::View::ConvertPointToScreen(this_view, &event_location);
views::View::ConvertPointFromScreen(browser_view->contents_container(),
&event_location);
if (browser_view->contents_container()->HitTestPoint(event_location)) {
return browser_view->GetWidget();
}
}
// In immersive fullscreen with tabs enabled the floating results shadow
// spreads into the tab strip area which is hosted in yet another separate
// widget, the tab widget. Send the rest of the events to the tab widget. This
// will allow for tab strip interaction in the area covered by the shadow and
// accurate tab hover card dismissal.
if (browser_view->tab_overlay_widget()) {
return browser_view->tab_overlay_widget();
}
// If immersive fullscreen with tabs is not enabled, send events to the
// overlay widget for tab strip interaction in the area covered by the shadow.
return browser_view->overlay_widget();
}
#endif
WidgetEventPair GetParentWidgetAndEvent(views::View* this_view,
const ui::MouseEvent* this_event) {
// Note that the floating results view is a top-level widget, so hop up a
// level before looking for the parent's top-level widget for event
// forwarding.
views::Widget* this_widget = this_view->GetWidget();
views::Widget* parent_widget = this_widget->parent();
std::unique_ptr<ui::MouseEvent> event(
static_cast<ui::MouseEvent*>(this_event->Clone().release()));
if (!parent_widget) {
return {nullptr, std::move(event)};
}
// On macOS if the parent widget is the overlay widget we are in immersive
// fullscreen. Don't walk any higher up the tree. The overlay or tab widget will
// handle the event.
// TODO(http://crbug.com/1462791): Remove custom event handling.
#if BUILDFLAG(IS_MAC)
views::Widget* top_level =
GetImmersiveFullscreenWidgetForEvent(this_view, this_event)
?: parent_widget->GetTopLevelWidgetForNativeView(
parent_widget->GetNativeView());
#else
views::Widget* top_level = parent_widget->GetTopLevelWidgetForNativeView(
parent_widget->GetNativeView());
#endif
DCHECK_NE(this_widget, top_level);
if (!top_level) {
return {nullptr, std::move(event)};
}
gfx::Point event_location = this_event->location();
views::View::ConvertPointToScreen(this_view, &event_location);
views::View::ConvertPointFromScreen(top_level->GetRootView(),
&event_location);
// Convert location to top level widget coordinate.
event->set_location(event_location);
return {top_level, std::move(event)};
}
#endif // !USE_AURA
// View at the top of the frame which paints transparent pixels to make a hole
// so that the location bar shows through.
class TopBackgroundView : public views::View {
METADATA_HEADER(TopBackgroundView, views::View)
public:
explicit TopBackgroundView(const LocationBarView* location_bar)
: location_bar_(location_bar) {}
void OnThemeChanged() override {
views::View::OnThemeChanged();
const SkColor background_color =
GetColorProvider()->GetColor(kColorOmniboxResultsBackground);
// Paint a stroke of the background color as a 1 px border to hide the
// underlying antialiased location bar/toolbar edge. The round rect here is
// not antialiased, since the goal is to completely cover the underlying
// pixels, and AA would let those on the edge partly bleed through.
SetBackground(location_bar_->CreateRoundRectBackground(
SK_ColorTRANSPARENT, background_color, SkBlendMode::kSrc, false));
}
#if !defined(USE_AURA)
// For non-Aura platforms, forward mouse events and cursor requests intended
// for the omnibox to the proper Widgets/Views. For Aura platforms, this is
// done with an event targeter set up in
// RoundedOmniboxResultsFrame::AddedToWidget(), below.
private:
// Note that mouse moved events can be dispatched through OnMouseEvent, but
// RootView directly calls OnMouseMoved as well, so override OnMouseMoved as
// well to catch 'em all.
void OnMouseMoved(const ui::MouseEvent& event) override {
auto pair = GetParentWidgetAndEvent(this, &event);
if (pair.widget) {
pair.widget->OnMouseEvent(pair.event.get());
}
}
void OnMouseEvent(ui::MouseEvent* event) override {
auto pair = GetParentWidgetAndEvent(this, event);
if (pair.widget) {
pair.widget->OnMouseEvent(pair.event.get());
}
// If the original event isn't marked as "handled" then it will propagate up
// the view hierarchy and might be double-handled. https://crbug.com/870341
event->SetHandled();
}
ui::Cursor GetCursor(const ui::MouseEvent& event) override {
const auto pair = GetParentWidgetAndEvent(this, &event);
if (pair.widget) {
views::View* omnibox_view =
pair.widget->GetRootView()->GetEventHandlerForPoint(
pair.event->location());
return omnibox_view->GetCursor(*pair.event);
}
return ui::Cursor();
}
#endif // !USE_AURA
private:
raw_ptr<const LocationBarView> location_bar_;
};
BEGIN_METADATA(TopBackgroundView)
END_METADATA
// Insets used to position |contents_| within |contents_host_|.
gfx::Insets GetContentInsets() {
return gfx::Insets::TLBR(
RoundedOmniboxResultsFrame::GetNonResultSectionHeight(), 0, 0, 0);
}
} // namespace
RoundedOmniboxResultsFrame::RoundedOmniboxResultsFrame(
views::View* contents,
LocationBarView* location_bar)
: contents_(contents) {
// Host the contents in its own View to simplify layout and customization.
contents_host_ = new views::View();
contents_host_->SetBackground(
views::CreateSolidBackground(kColorOmniboxResultsBackground));
contents_host_->SetPaintToLayer();
contents_host_->layer()->SetFillsBoundsOpaquely(false);
// Use rounded corners.
const int corner_radius = views::LayoutProvider::Get()->GetCornerRadiusMetric(
views::ShapeContextTokens::kOmniboxExpandedRadius);
contents_host_->layer()->SetRoundedCornerRadius(
gfx::RoundedCornersF(corner_radius));
contents_host_->layer()->SetIsFastRoundedCorner(true);
top_background_ = new TopBackgroundView(location_bar);
contents_host_->AddChildViewRaw(top_background_.get());
contents_host_->AddChildViewRaw(contents_.get());
// Initialize the shadow.
auto border = std::make_unique<views::BubbleBorder>(
views::BubbleBorder::Arrow::NONE,
views::BubbleBorder::Shadow::STANDARD_SHADOW);
border->set_rounded_corners(gfx::RoundedCornersF(corner_radius));
border->set_md_shadow_elevation(kElevation);
SetBorder(std::move(border));
AddChildViewRaw(contents_host_.get());
}
RoundedOmniboxResultsFrame::~RoundedOmniboxResultsFrame() = default;
// static
void RoundedOmniboxResultsFrame::OnBeforeWidgetInit(
views::Widget::InitParams* params,
views::Widget* widget) {
#if BUILDFLAG(IS_WIN)
// On Windows, use an Aura window instead of a native window, because the
// native window does not support clicking through translucent shadows to the
// underyling content. Linux and ChromeOS do not need this because they
// already use Aura for the suggestions dropdown.
//
// TODO(sdy): Mac does not support Aura at the moment, and needs a different
// platform-specific solution.
params->native_widget = new views::NativeWidgetAura(widget);
#endif
params->name = "RoundedOmniboxResultsFrameWindow";
// Since we are drawing the shadow in Views via the BubbleBorder, we never
// want our widget to have its own window-manager drawn shadow.
params->shadow_type = views::Widget::InitParams::ShadowType::kNone;
}
// static
int RoundedOmniboxResultsFrame::GetNonResultSectionHeight() {
return GetLayoutConstant(LOCATION_BAR_HEIGHT) +
GetLocationBarAlignmentInsets().height();
}
// static
gfx::Insets RoundedOmniboxResultsFrame::GetLocationBarAlignmentInsets() {
if (ui::TouchUiController::Get()->touch_ui()) {
return gfx::Insets::TLBR(6, 1, 5, 1);
}
return gfx::Insets::VH(5, 6);
}
// static
gfx::Insets RoundedOmniboxResultsFrame::GetShadowInsets() {
return views::BubbleBorder::GetBorderAndShadowInsets(kElevation);
}
void RoundedOmniboxResultsFrame::Layout(PassKey) {
// This is called when the Widget resizes due to results changing. Resizing
// the Widget is fast on ChromeOS, but slow on other platforms, and can't be
// animated smoothly.
// TODO(tapted): Investigate using a static Widget size.
const gfx::Rect bounds = GetContentsBounds();
contents_host_->SetBoundsRect(bounds);
gfx::Rect top_bounds(contents_host_->GetContentsBounds());
top_bounds.set_height(GetNonResultSectionHeight());
top_bounds.Inset(GetLocationBarAlignmentInsets());
top_background_->SetBoundsRect(top_bounds);
gfx::Rect results_bounds(contents_host_->GetContentsBounds());
results_bounds.Inset(GetContentInsets());
contents_->SetBoundsRect(results_bounds);
}
void RoundedOmniboxResultsFrame::AddedToWidget() {
#if defined(USE_AURA)
// Use a ui::EventTargeter that allows mouse and touch events in the top
// portion of the Widget to pass through to the omnibox beneath it.
auto results_targeter = std::make_unique<aura::WindowTargeter>();
results_targeter->SetInsets(GetInsets() + GetContentInsets());
GetWidget()->GetNativeWindow()->SetEventTargeter(std::move(results_targeter));
#endif // USE_AURA
}
// Note: The OnMouseMoved function is only called for the shadow area, as mouse-
// moved events are not dispatched through the view hierarchy but are direct-
// dispatched by RootView. This OnMouseEvent function is on the dispatch path
// for all mouse events of the window, so be careful to correctly mark events as
// "handled" above in subviews.
#if !defined(USE_AURA)
// Note that mouse moved events can be dispatched through OnMouseEvent, but
// RootView directly calls OnMouseMoved as well, so override OnMouseMoved as
// well to catch 'em all.
void RoundedOmniboxResultsFrame::OnMouseMoved(const ui::MouseEvent& event) {
auto pair = GetParentWidgetAndEvent(this, &event);
if (pair.widget) {
pair.widget->OnMouseEvent(pair.event.get());
}
}
void RoundedOmniboxResultsFrame::OnMouseEvent(ui::MouseEvent* event) {
auto pair = GetParentWidgetAndEvent(this, event);
if (pair.widget) {
pair.widget->OnMouseEvent(pair.event.get());
}
}
#endif // !USE_AURA
BEGIN_METADATA(RoundedOmniboxResultsFrame)
END_METADATA
|