blob: bc0293d149406e3d46374967bde15cad9a94e2b2 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/widget/sublevel_manager.h"
#include <algorithm>
#include "build/build_config.h"
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/widget.h"
namespace {
bool ShouldStackAboveParent(views::Widget* widget) {
#if !BUILDFLAG(IS_MAC)
return false;
#else
// macOS bug: a child widget might be rendered behind its parent in fullscreen
// if the child is not explicitly StackAbove()'ed its parent.
int level = 0;
views::Widget* root = widget;
while (root->parent()) {
root = root->parent();
// StackAbove() will make `widget` visible. We don't want this when its
// ancestor is invisible.
if (!root->IsVisible()) {
return false;
}
level++;
}
// Only StackAbove() when `widget` is a grandchild (or deeper) of the root
// fullscreen window.
return level > 1 && root->IsFullscreen();
#endif
}
} // namespace
namespace views {
SublevelManager::SublevelManager(Widget* owner, int sublevel)
: owner_(owner), sublevel_(sublevel) {
owner_observation_.Observe(owner);
}
SublevelManager::~SublevelManager() = default;
void SublevelManager::SetSublevel(int sublevel) {
sublevel_ = sublevel;
EnsureOwnerSublevel();
}
int SublevelManager::GetSublevel() const {
return sublevel_;
}
void SublevelManager::EnsureOwnerSublevel() {
// Walk through the path to the root and ensure sublevel on every widget
// on the path. This is to work around the behavior on some platforms
// where showing an activatable widget brings its ancestors to the front.
Widget* parent = owner_->parent();
Widget* child = owner_;
while (parent && parent->GetSublevelManager()->IsTrackingChildWidget(child)) {
parent->GetSublevelManager()->OrderChildWidget(child);
child = parent;
parent = parent->parent();
}
}
void SublevelManager::EnsureOwnerTreeSublevel() {
for (Widget* child : children_) {
child->GetSublevelManager()->EnsureOwnerTreeSublevel();
}
if (Widget* parent = owner_->parent()) {
parent->GetSublevelManager()->OrderChildWidget(owner_);
}
}
void SublevelManager::OnWidgetChildAdded(Widget* owner, Widget* child) {
CHECK_EQ(owner, owner_);
CHECK(!base::Contains(children_, child));
CHECK_EQ(child->parent(), owner_);
children_.push_back(child);
}
void SublevelManager::OnWidgetChildRemoved(Widget* owner, Widget* child) {
CHECK_EQ(owner, owner_);
// During shutdown a child might get untracked more than once by the same
// parent. We don't want to DCHECK on that.
std::erase(children_, child);
}
void SublevelManager::OrderChildWidget(Widget* child) {
auto removed = std::ranges::remove(children_, child);
DCHECK_EQ(1u, removed.size());
children_.erase(removed.begin(), removed.end());
if (ShouldStackAboveParent(child)) {
child->StackAboveWidget(owner_);
}
ui::ZOrderLevel child_level = child->GetZOrderLevel();
auto insert_it = FindInsertPosition(child);
// Stacking above an invisible widget is a no-op on Mac. Therefore, find only
// visible ones.
auto find_visible_widget_of_same_level = [child_level](Widget* widget) {
return widget->IsVisible() && widget->GetZOrderLevel() == child_level;
};
auto prev_it = std::ranges::find_if(std::make_reverse_iterator(insert_it),
std::crend(children_),
find_visible_widget_of_same_level);
if (prev_it == children_.rend()) {
// x11 bug: stacking above the base `owner_` will cause `child` to become
// unresponsive after the base widget is minimized. As a workaround, we
// position `child` relative to the next child widget.
// Find the closest next widget at the same level.
auto next_it = std::ranges::find_if(insert_it, std::cend(children_),
find_visible_widget_of_same_level);
// Put `child` below `next_it`.
if (next_it != std::end(children_)) {
child->StackAboveWidget(*next_it);
(*next_it)->StackAboveWidget(child);
}
} else {
child->StackAboveWidget(*prev_it);
}
children_.insert(insert_it, child);
}
bool SublevelManager::IsTrackingChildWidget(Widget* child) {
return std::ranges::find(children_, child) != children_.end();
}
SublevelManager::ChildIterator SublevelManager::FindInsertPosition(
Widget* child) const {
ui::ZOrderLevel child_level = child->GetZOrderLevel();
int child_sublevel = child->GetZOrderSublevel();
return std::ranges::find_if(children_, [&](Widget* widget) {
return widget->GetZOrderLevel() == child_level &&
widget->GetZOrderSublevel() > child_sublevel;
});
}
} // namespace views