chromium/ash/wm/raster_scale/raster_scale_controller.cc

// 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 "ash/wm/raster_scale/raster_scale_controller.h"

#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "base/ranges/algorithm.h"
#include "ui/aura/client/aura_constants.h"

namespace ash {

ScopedSetRasterScale::ScopedSetRasterScale(aura::Window* window,
                                           float raster_scale)
    : window_(window), raster_scale_(raster_scale) {
  window_->AddObserver(this);
  Shell::Get()->raster_scale_controller()->PushRasterScale(window_,
                                                           raster_scale_);
}

ScopedSetRasterScale::~ScopedSetRasterScale() {
  Shutdown();
}

void ScopedSetRasterScale::UpdateScale(float raster_scale) {
  if (window_) {
    auto* controller = Shell::Get()->raster_scale_controller();
    controller->PushRasterScale(window_, raster_scale);
    controller->PopRasterScale(window_, raster_scale_);
  }
  raster_scale_ = raster_scale;
}

// static
void ScopedSetRasterScale::SetOrUpdateRasterScale(
    aura::Window* window,
    float raster_scale,
    std::unique_ptr<ScopedSetRasterScale>* p) {
  if (*p) {
    DCHECK_EQ(window, (*p)->window_);
    (*p)->UpdateScale(raster_scale);
  } else {
    *p = std::make_unique<ScopedSetRasterScale>(window, raster_scale);
  }
}

void ScopedSetRasterScale::Shutdown() {
  if (window_) {
    auto* rsc = Shell::Get()->raster_scale_controller();
    if (rsc) {
      rsc->PopRasterScale(window_, raster_scale_);
    }
    window_->RemoveObserver(this);
    window_ = nullptr;
  }
}

void ScopedSetRasterScale::OnWindowDestroying(aura::Window* window) {
  DCHECK_EQ(window_, window);
  Shutdown();
}

ScopedPauseRasterScaleUpdates::ScopedPauseRasterScaleUpdates() {
  auto* rsc = Shell::Get()->raster_scale_controller();
  if (rsc) {
    rsc->Pause();
  }
}

ScopedPauseRasterScaleUpdates::~ScopedPauseRasterScaleUpdates() {
  auto* rsc = Shell::Get()->raster_scale_controller();
  if (rsc) {
    rsc->Unpause();
  }
}

RasterScaleController::RasterScaleController() = default;
RasterScaleController::~RasterScaleController() {
  // Reset raster scales to 1.0 on destruction.
  for (const auto& [window, _] : window_scales_) {
    window->SetProperty(aura::client::kRasterScale, 1.0f);
  }
}

float RasterScaleController::RasterScaleFromTransform(
    const gfx::Transform& transform) {
  const auto scale_2d = transform.To2dScale();
  return std::max(scale_2d.x(), scale_2d.y());
}

void RasterScaleController::PushRasterScale(aura::Window* window,
                                            float raster_scale) {
  if (!windows_observation_.IsObservingSource(window)) {
    windows_observation_.AddObservation(window);
  }

  window_scales_[window].push_back(raster_scale);
  MaybeSetRasterScale(window);
}

void RasterScaleController::PopRasterScale(aura::Window* window,
                                           float raster_scale) {
  auto iter = window_scales_.find(window);

  // This may happen if the window has been destroyed.
  if (iter == window_scales_.end()) {
    return;
  }

  auto& scales = iter->second;
  DCHECK(base::Contains(scales, raster_scale));
  auto scale_iter = base::ranges::find(scales, raster_scale);
  if (scale_iter != scales.end()) {
    scales.erase(scale_iter);
  }

  MaybeSetRasterScale(window);

  if (scales.empty()) {
    window_scales_.erase(window);

    // If we still hold a reference to this window, we need to keep observing
    // it.
    if (!pending_windows_.contains(window)) {
      windows_observation_.RemoveObservation(window);
    }
  }
}

float RasterScaleController::ComputeRasterScaleForWindow(aura::Window* window) {
  auto iter = window_scales_.find(window);
  if (iter == window_scales_.end() || iter->second.empty()) {
    return 1.0f;
  }

  float raster_scale = 0.0;
  for (auto scale : iter->second) {
    raster_scale = std::max(raster_scale, scale);
  }
  return std::max(raster_scale, kMinimumRasterScale);
}

void RasterScaleController::OnWindowDestroying(aura::Window* window) {
  windows_observation_.RemoveObservation(window);
  window_scales_.erase(window);
  pending_windows_.erase(window);
}

void RasterScaleController::MaybeSetRasterScale(aura::Window* window) {
  if (pause_count_ > 0) {
    pending_windows_.insert(window);
    return;
  }

  const float previous_scale = window->GetProperty(aura::client::kRasterScale);
  const float current_scale = ComputeRasterScaleForWindow(window);

  // Allow updating the raster scale if the relative change is at least the slop
  // proportion.
  const bool slop_condition =
      previous_scale != 0.0 &&
      std::abs((previous_scale - current_scale) / previous_scale) >=
          raster_scale_slop_proportion_;
  if (previous_scale != current_scale &&
      (slop_condition || current_scale == 1.0f)) {
    window->SetProperty(aura::client::kRasterScale, current_scale);
  }
}

void RasterScaleController::Pause() {
  pause_count_++;
}

void RasterScaleController::Unpause() {
  pause_count_--;
  DCHECK_GE(pause_count_, 0);
  if (pause_count_ == 0) {
    for (aura::Window* window : pending_windows_) {
      MaybeSetRasterScale(window);

      // If we kept observing a window since it had a pending change, we can
      // stop observing now if there is no scale set.
      if (!window_scales_.contains(window)) {
        windows_observation_.RemoveObservation(window);
      }
    }
    pending_windows_.clear();
  }
}

}  // namespace ash