| // Copyright 2023 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/gfx/x/geometry_cache.h" |
| |
| #include <memory> |
| |
| #include "ui/gfx/geometry/vector2d.h" |
| #include "ui/gfx/x/connection.h" |
| #include "ui/gfx/x/event.h" |
| #include "ui/gfx/x/xproto.h" |
| |
| namespace x11 { |
| |
| GeometryCache::GeometryCache(Connection* connection, |
| Window window, |
| BoundsChangedCallback bounds_changed_callback) |
| : connection_(connection), |
| window_(window), |
| bounds_changed_callback_(bounds_changed_callback) { |
| scoped_observation_.Observe(connection_); |
| window_events_ = |
| connection_->ScopedSelectEvent(window_, EventMask::StructureNotify); |
| |
| parent_future_ = connection_->QueryTree(window_); |
| parent_future_.OnResponse(base::BindOnce(&GeometryCache::OnQueryTreeResponse, |
| weak_ptr_factory_.GetWeakPtr())); |
| geometry_future_ = connection_->GetGeometry(window_); |
| geometry_future_.OnResponse(base::BindOnce( |
| &GeometryCache::OnGetGeometryResponse, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| GeometryCache::~GeometryCache() = default; |
| |
| gfx::Rect GeometryCache::GetBoundsPx() { |
| if (!have_parent_) { |
| parent_future_.DispatchNow(); |
| } |
| CHECK(have_parent_); |
| if (!have_geometry_) { |
| geometry_future_.DispatchNow(); |
| } |
| CHECK(have_geometry_); |
| |
| if (!parent_) { |
| return geometry_; |
| } |
| auto parent_bounds = parent_->GetBoundsPx(); |
| gfx::Vector2d offset(parent_bounds.x(), parent_bounds.y()); |
| return geometry_ + offset; |
| } |
| |
| void GeometryCache::OnQueryTreeResponse(QueryTreeResponse response) { |
| if (have_parent_) { |
| return; |
| } |
| OnParentChanged(response ? response->parent : Window::None, |
| geometry_.origin()); |
| } |
| |
| void GeometryCache::OnGetGeometryResponse(GetGeometryResponse response) { |
| if (have_geometry_) { |
| return; |
| } |
| OnGeometryChanged(response ? gfx::Rect(response->x, response->y, |
| response->width, response->height) |
| : gfx::Rect()); |
| } |
| |
| void GeometryCache::OnParentChanged(Window parent, const gfx::Point& position) { |
| have_parent_ = true; |
| if (parent == Window::None) { |
| parent_.reset(); |
| } else if (!parent_ || parent_->window_ != parent) { |
| parent_ = std::make_unique<GeometryCache>( |
| connection_, parent, |
| base::BindRepeating(&GeometryCache::OnParentGeometryChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| geometry_.set_origin(position); |
| |
| NotifyGeometryChanged(); |
| } |
| |
| void GeometryCache::OnGeometryChanged(const gfx::Rect& geometry) { |
| have_geometry_ = true; |
| geometry_ = geometry; |
| |
| NotifyGeometryChanged(); |
| } |
| |
| void GeometryCache::OnPositionChanged(const gfx::Point& origin) { |
| geometry_.set_origin(origin); |
| NotifyGeometryChanged(); |
| } |
| |
| bool GeometryCache::Ready() const { |
| return have_geometry_ && have_parent_ && (!parent_ || parent_->Ready()); |
| } |
| |
| void GeometryCache::OnParentGeometryChanged( |
| const std::optional<gfx::Rect>& old_parent_bounds, |
| const gfx::Rect& new_parent_bounds) { |
| NotifyGeometryChanged(); |
| } |
| |
| void GeometryCache::NotifyGeometryChanged() { |
| if (!Ready()) { |
| return; |
| } |
| |
| auto geometry = GetBoundsPx(); |
| if (last_notified_geometry_ == geometry) { |
| return; |
| } |
| |
| auto old_geometry = last_notified_geometry_; |
| last_notified_geometry_ = geometry; |
| bounds_changed_callback_.Run(old_geometry, geometry); |
| } |
| |
| void GeometryCache::OnEvent(const Event& xevent) { |
| // Ignore client events. |
| if (xevent.send_event()) { |
| return; |
| } |
| |
| if (auto* configure = xevent.As<ConfigureNotifyEvent>()) { |
| if (configure->window == window_) { |
| OnGeometryChanged(gfx::Rect(configure->x, configure->y, configure->width, |
| configure->height)); |
| } |
| } else if (auto* reparent = xevent.As<ReparentNotifyEvent>()) { |
| if (reparent->window == window_) { |
| OnParentChanged(reparent->parent, gfx::Point(reparent->x, reparent->y)); |
| } |
| } else if (auto* gravity = xevent.As<GravityNotifyEvent>()) { |
| if (gravity->window == window_) { |
| OnPositionChanged(gfx::Point(gravity->x, gravity->y)); |
| } |
| } |
| } |
| |
| } // namespace x11 |