blob: 47ea4b45ebaf9455363c456b8b0cb219926d4567 [file] [log] [blame]
// Copyright 2025 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/utility/client_controlled_state_util.h"
#include "ash/wm/client_controlled_state.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_state_delegate.h"
#include "chromeos/ui/base/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/display/screen.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// A WindowStateDelegate that implements ToggleFullscreen behavior for
// client controlled window.
class ClientControlledWindowStateDelegate : public WindowStateDelegate {
public:
explicit ClientControlledWindowStateDelegate(
ClientControlledState::Delegate* delegate)
: delegate_(delegate) {}
ClientControlledWindowStateDelegate(
const ClientControlledWindowStateDelegate&) = delete;
ClientControlledWindowStateDelegate& operator=(
const ClientControlledWindowStateDelegate&) = delete;
~ClientControlledWindowStateDelegate() override = default;
// WindowStateDelegate:
bool ToggleFullscreen(WindowState* window_state) override {
chromeos::WindowStateType next_state;
aura::Window* window = window_state->window();
switch (window_state->GetStateType()) {
case chromeos::WindowStateType::kDefault:
case chromeos::WindowStateType::kNormal:
next_state = chromeos::WindowStateType::kFullscreen;
break;
case chromeos::WindowStateType::kMaximized:
next_state = chromeos::WindowStateType::kFullscreen;
break;
case chromeos::WindowStateType::kFullscreen:
switch (window->GetProperty(aura::client::kRestoreShowStateKey)) {
case ui::mojom::WindowShowState::kDefault:
case ui::mojom::WindowShowState::kNormal:
next_state = chromeos::WindowStateType::kNormal;
break;
case ui::mojom::WindowShowState::kMaximized:
next_state = chromeos::WindowStateType::kMaximized;
break;
case ui::mojom::WindowShowState::kMinimized:
next_state = chromeos::WindowStateType::kMinimized;
break;
case ui::mojom::WindowShowState::kFullscreen:
case ui::mojom::WindowShowState::kInactive:
case ui::mojom::WindowShowState::kEnd:
DUMP_WILL_BE_NOTREACHED()
<< " unknown state :"
<< window->GetProperty(aura::client::kRestoreShowStateKey);
return false;
}
break;
case chromeos::WindowStateType::kMinimized: {
next_state = chromeos::WindowStateType::kFullscreen;
break;
}
default:
return false;
}
delegate_->HandleWindowStateRequest(window_state, next_state);
return true;
}
void OnWindowDestroying() override { delegate_ = nullptr; }
private:
raw_ptr<ClientControlledState::Delegate> delegate_;
};
// A ClientControlledStateDelegate that applies the state/bounds asynchronously.
class ClientControlledStateDelegate : public ClientControlledState::Delegate {
public:
ClientControlledStateDelegate(
ClientControlledStateUtil::StateChangeRequestCallback
state_change_callback,
ClientControlledStateUtil::BoundsChangeRequestCallback
bounds_change_callback)
: state_change_callback_(state_change_callback),
bounds_change_callback_(bounds_change_callback) {
if (state_change_callback_.is_null()) {
state_change_callback_ = base::BindRepeating(
&ClientControlledStateUtil::ApplyWindowStateRequest);
}
if (bounds_change_callback.is_null()) {
bounds_change_callback_ =
base::BindRepeating(&ClientControlledStateUtil::ApplyBoundsRequest);
}
}
ClientControlledStateDelegate(const ClientControlledStateDelegate&) = delete;
ClientControlledStateDelegate& operator=(
const ClientControlledStateDelegate&) = delete;
~ClientControlledStateDelegate() override = default;
// Overridden from ClientControlledState::Delegate:
void HandleWindowStateRequest(WindowState* window_state,
chromeos::WindowStateType next_state) override {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(state_change_callback_, base::Unretained(window_state),
base::Unretained(client_controlled_state_), next_state));
}
void HandleBoundsRequest(WindowState* window_state,
chromeos::WindowStateType requested_state,
const gfx::Rect& bounds_in_display,
int64_t display_id) override {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(bounds_change_callback_, base::Unretained(window_state),
base::Unretained(client_controlled_state_),
requested_state, bounds_in_display, display_id));
}
void set_client_controlled_state(ClientControlledState* state) {
client_controlled_state_ = state;
}
raw_ptr<ClientControlledState> client_controlled_state_;
ClientControlledStateUtil::StateChangeRequestCallback state_change_callback_;
ClientControlledStateUtil::BoundsChangeRequestCallback
bounds_change_callback_;
base::WeakPtrFactory<ClientControlledStateDelegate> weak_ptr_factory_{this};
};
} // namespace
// static
void ClientControlledStateUtil::ApplyWindowStateRequest(
WindowState* window_state,
ClientControlledState* client_controlled_state,
chromeos::WindowStateType next_state) {
std::optional<gfx::Rect> bounds;
auto display =
display::Screen::Get()->GetDisplayNearestWindow(window_state->window());
switch (next_state) {
case chromeos::WindowStateType::kDefault:
case chromeos::WindowStateType::kNormal:
bounds = window_state->GetRestoreBoundsInScreen();
break;
case chromeos::WindowStateType::kMaximized:
window_state->SaveCurrentBoundsForRestore();
bounds = display.work_area();
break;
case chromeos::WindowStateType::kFullscreen:
window_state->SaveCurrentBoundsForRestore();
bounds = display.bounds();
break;
case chromeos::WindowStateType::kMinimized:
break;
case chromeos::WindowStateType::kPrimarySnapped:
window_state->SaveCurrentBoundsForRestore();
bounds = display.bounds();
bounds->set_width(bounds->width() / 2);
break;
case chromeos::WindowStateType::kSecondarySnapped:
window_state->SaveCurrentBoundsForRestore();
bounds = display.bounds();
bounds->set_x(bounds->width() / 2);
break;
case chromeos::WindowStateType::kFloated:
// Additional ash states.
return;
case chromeos::WindowStateType::kInactive:
case chromeos::WindowStateType::kPinned:
case chromeos::WindowStateType::kTrustedPinned:
case chromeos::WindowStateType::kPip:
// Not supported;
return;
}
client_controlled_state->EnterNextState(window_state, next_state);
if (bounds) {
gfx::Rect bounds_in_parent = *bounds;
wm::ConvertRectFromScreen(window_state->window()->parent(),
&bounds_in_parent);
window_state->SetBoundsDirectCrossFade(bounds_in_parent, true);
}
}
// static
void ClientControlledStateUtil::ApplyBoundsRequest(
WindowState* window_state,
ClientControlledState* client_controlled_state,
chromeos::WindowStateType requested_state,
const gfx::Rect& bounds_in_display,
int64_t display_id) {
display::Display target_display;
CHECK(display::Screen::Get()->GetDisplayWithDisplayId(display_id,
&target_display));
auto target_bounds = bounds_in_display;
target_bounds.Offset(target_display.bounds().origin().x(),
target_display.bounds().origin().y());
if (client_controlled_state->EnterNextState(window_state, requested_state)) {
client_controlled_state->set_next_bounds_change_animation_type(
WindowState::BoundsChangeAnimationType::kCrossFadeFloat);
}
client_controlled_state->set_bounds_locally(true);
window_state->window()->SetBoundsInScreen(target_bounds, target_display);
client_controlled_state->set_bounds_locally(false);
}
// static
void ClientControlledStateUtil::BuildAndSet(
aura::Window* window,
StateChangeRequestCallback state_change_callback,
BoundsChangeRequestCallback bounds_change_callback) {
auto delegate = std::make_unique<ClientControlledStateDelegate>(
state_change_callback, bounds_change_callback);
auto* delegate_ptr = delegate.get();
auto state = std::make_unique<ClientControlledState>(std::move(delegate));
delegate_ptr->set_client_controlled_state(state.get());
auto window_state_delegate =
std::make_unique<ClientControlledWindowStateDelegate>(delegate.get());
auto* window_state = WindowState::Get(window);
window_state->SetStateObject(std::move(state));
window_state->SetDelegate(std::move(window_state_delegate));
window_state->set_allow_set_bounds_direct(true);
}
} // namespace ash