| // Copyright 2017 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/keyboard/ui/container_floating_behavior.h" |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "ash/keyboard/ui/display_util.h" |
| #include "ash/keyboard/ui/drag_descriptor.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/display/display.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/wm/core/window_animations.h" |
| |
| namespace keyboard { |
| |
| // The virtual keyboard show/hide animation durations. |
| constexpr auto kShowAnimationDuration = base::Milliseconds(200); |
| constexpr auto kHideAnimationDuration = base::Milliseconds(100); |
| |
| // Distance the keyboard moves during the animation |
| constexpr int kAnimationDistance = 30; |
| |
| ContainerFloatingBehavior::ContainerFloatingBehavior(Delegate* delegate) |
| : ContainerBehavior(delegate) {} |
| |
| ContainerFloatingBehavior::~ContainerFloatingBehavior() = default; |
| |
| ContainerType ContainerFloatingBehavior::GetType() const { |
| return ContainerType::kFloating; |
| } |
| |
| void ContainerFloatingBehavior::DoHidingAnimation( |
| aura::Window* container, |
| ::wm::ScopedHidingAnimationSettings* animation_settings) { |
| animation_settings->layer_animation_settings()->SetTransitionDuration( |
| kHideAnimationDuration); |
| gfx::Transform transform; |
| transform.Translate(0, kAnimationDistance); |
| container->SetTransform(transform); |
| container->layer()->SetOpacity(0.f); |
| } |
| |
| void ContainerFloatingBehavior::DoShowingAnimation( |
| aura::Window* container, |
| ui::ScopedLayerAnimationSettings* animation_settings) { |
| animation_settings->SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN); |
| animation_settings->SetTransitionDuration(kShowAnimationDuration); |
| |
| container->SetTransform(gfx::Transform()); |
| container->layer()->SetOpacity(1.0); |
| } |
| |
| void ContainerFloatingBehavior::InitializeShowAnimationStartingState( |
| aura::Window* container) { |
| aura::Window* root_window = container->GetRootWindow(); |
| |
| SetCanonicalBounds(container, root_window->bounds()); |
| |
| gfx::Transform transform; |
| transform.Translate(0, kAnimationDistance); |
| container->SetTransform(transform); |
| container->layer()->SetOpacity(kAnimationStartOrAfterHideOpacity); |
| } |
| |
| gfx::Rect ContainerFloatingBehavior::AdjustSetBoundsRequest( |
| const gfx::Rect& display_bounds, |
| const gfx::Rect& requested_bounds_in_screen) { |
| gfx::Rect keyboard_bounds_in_screen = ContainKeyboardToDisplayBounds( |
| requested_bounds_in_screen, display_bounds); |
| SavePosition(keyboard_bounds_in_screen, display_bounds.size()); |
| return keyboard_bounds_in_screen; |
| } |
| |
| void ContainerFloatingBehavior::SavePosition( |
| const gfx::Rect& keyboard_bounds_in_screen, |
| const gfx::Size& screen_size) { |
| int left_distance = keyboard_bounds_in_screen.x(); |
| int right_distance = screen_size.width() - keyboard_bounds_in_screen.right(); |
| int top_distance = keyboard_bounds_in_screen.y(); |
| int bottom_distance = |
| screen_size.height() - keyboard_bounds_in_screen.bottom(); |
| |
| double available_width = left_distance + right_distance; |
| double available_height = top_distance + bottom_distance; |
| |
| if (!default_position_in_screen_) { |
| default_position_in_screen_ = std::make_unique<KeyboardPosition>(); |
| } |
| |
| default_position_in_screen_->left_padding_allotment_ratio = |
| left_distance / available_width; |
| default_position_in_screen_->top_padding_allotment_ratio = |
| top_distance / available_height; |
| } |
| |
| gfx::Rect ContainerFloatingBehavior::ConvertAreaInKeyboardToScreenBounds( |
| const gfx::Rect& area_in_keyboard_window, |
| const gfx::Rect& keyboard_window_bounds_in_display) const { |
| gfx::Point origin_in_screen( |
| keyboard_window_bounds_in_display.x() + area_in_keyboard_window.x(), |
| keyboard_window_bounds_in_display.y() + area_in_keyboard_window.y()); |
| return gfx::Rect(origin_in_screen, area_in_keyboard_window.size()); |
| } |
| |
| gfx::Rect ContainerFloatingBehavior::GetBoundsWithinDisplay( |
| const gfx::Rect& bounds, |
| const gfx::Rect& display_bounds) const { |
| gfx::Rect new_bounds = bounds; |
| |
| if (bounds.x() < display_bounds.x()) { |
| new_bounds.set_origin(gfx::Point(display_bounds.x(), new_bounds.y())); |
| } |
| if (bounds.right() >= display_bounds.right()) { |
| new_bounds.set_origin( |
| gfx::Point(display_bounds.right() - bounds.width(), new_bounds.y())); |
| } |
| if (bounds.y() < display_bounds.y()) { |
| new_bounds.set_origin(gfx::Point(new_bounds.x(), display_bounds.y())); |
| } |
| if (bounds.bottom() >= display_bounds.bottom()) { |
| new_bounds.set_origin(gfx::Point( |
| new_bounds.x(), display_bounds.bottom() - new_bounds.height())); |
| } |
| |
| return new_bounds; |
| } |
| |
| gfx::Rect ContainerFloatingBehavior::ContainKeyboardToDisplayBounds( |
| const gfx::Rect& keyboard_window_bounds_in_screen, |
| const gfx::Rect& display_bounds) const { |
| if (!area_in_window_to_remain_on_screen_) { |
| return GetBoundsWithinDisplay(keyboard_window_bounds_in_screen, |
| display_bounds); |
| } |
| |
| // This area is relative to the origin of the keyboard window not the |
| // screen. |
| gfx::Rect inner_area_of_keyboard_window = |
| *area_in_window_to_remain_on_screen_; |
| |
| gfx::Rect area_to_remain_on_display = ConvertAreaInKeyboardToScreenBounds( |
| inner_area_of_keyboard_window, keyboard_window_bounds_in_screen); |
| |
| gfx::Rect area_constrained_to_display = |
| GetBoundsWithinDisplay(area_to_remain_on_display, display_bounds); |
| |
| // We need to calculate the new keyboard window bounds in this method, |
| // and at the moment we have constrained only an area inside the window |
| // to the display, not the entire keyboard window. So now we must |
| // derive the containing keyboard window bounds from this constrained |
| // inner area. |
| gfx::Point containing_keyboard_window_origin( |
| area_constrained_to_display.x() - inner_area_of_keyboard_window.x(), |
| area_constrained_to_display.y() - inner_area_of_keyboard_window.y()); |
| |
| return gfx::Rect(containing_keyboard_window_origin, |
| keyboard_window_bounds_in_screen.size()); |
| } |
| |
| bool ContainerFloatingBehavior::IsOverscrollAllowed() const { |
| return false; |
| } |
| |
| gfx::Point ContainerFloatingBehavior::GetPositionForShowingKeyboard( |
| const gfx::Size& keyboard_size, |
| const gfx::Rect& display_bounds) const { |
| // Start with the last saved position |
| gfx::Point top_left_offset; |
| KeyboardPosition* position = default_position_in_screen_.get(); |
| if (position == nullptr) { |
| // If there is none, center the keyboard along the bottom of the screen. |
| top_left_offset.set_x(display_bounds.width() - keyboard_size.width() - |
| kDefaultDistanceFromScreenRight); |
| top_left_offset.set_y(display_bounds.height() - keyboard_size.height() - |
| kDefaultDistanceFromScreenBottom); |
| } else { |
| double left = (display_bounds.width() - keyboard_size.width()) * |
| position->left_padding_allotment_ratio; |
| double top = (display_bounds.height() - keyboard_size.height()) * |
| position->top_padding_allotment_ratio; |
| top_left_offset.set_x(base::ClampFloor(left)); |
| top_left_offset.set_y(base::ClampFloor(top)); |
| } |
| |
| // Make sure that this location is valid according to the current size of the |
| // screen. |
| gfx::Rect keyboard_bounds = |
| gfx::Rect(top_left_offset.x() + display_bounds.x(), |
| top_left_offset.y() + display_bounds.y(), keyboard_size.width(), |
| keyboard_size.height()); |
| |
| gfx::Rect valid_keyboard_bounds = |
| ContainKeyboardToDisplayBounds(keyboard_bounds, display_bounds); |
| |
| return valid_keyboard_bounds.origin(); |
| } |
| |
| bool ContainerFloatingBehavior::HandlePointerEvent( |
| const ui::LocatedEvent& event, |
| const display::Display& current_display) { |
| const gfx::Vector2d kb_offset(base::ClampFloor(event.x()), |
| base::ClampFloor(event.y())); |
| |
| const gfx::Rect& keyboard_bounds_in_screen = delegate_->GetBoundsInScreen(); |
| |
| // Don't handle events if this runs in a partially initialized state. |
| if (keyboard_bounds_in_screen.height() <= 0) |
| return false; |
| |
| ui::PointerId pointer_id = ui::kPointerIdMouse; |
| if (event.IsTouchEvent()) { |
| const ui::TouchEvent* te = event.AsTouchEvent(); |
| pointer_id = te->pointer_details().id; |
| } |
| |
| const ui::EventType type = event.type(); |
| switch (type) { |
| case ui::EventType::kTouchPressed: |
| case ui::EventType::kMousePressed: |
| if (!draggable_area_.Contains(kb_offset.x(), kb_offset.y())) { |
| drag_descriptor_.reset(); |
| } else if (type == ui::EventType::kMousePressed && |
| !static_cast<const ui::MouseEvent&>(event) |
| .IsOnlyLeftMouseButton()) { |
| // Mouse events are limited to just the left mouse button. |
| drag_descriptor_.reset(); |
| } else if (!drag_descriptor_) { |
| drag_descriptor_ = std::make_unique<DragDescriptor>(DragDescriptor{ |
| keyboard_bounds_in_screen.origin(), kb_offset, pointer_id}); |
| } |
| break; |
| |
| case ui::EventType::kMouseDragged: |
| case ui::EventType::kTouchMoved: |
| if (drag_descriptor_ && drag_descriptor_->pointer_id == pointer_id) { |
| // Drag continues. |
| // If there is an active drag, use it to determine the new location |
| // of the keyboard. |
| const gfx::Point original_click_location = |
| drag_descriptor_->original_keyboard_location + |
| drag_descriptor_->original_click_offset; |
| const gfx::Point current_drag_location = |
| keyboard_bounds_in_screen.origin() + kb_offset; |
| const gfx::Vector2d cumulative_drag_offset = |
| current_drag_location - original_click_location; |
| const gfx::Point new_keyboard_location = |
| drag_descriptor_->original_keyboard_location + |
| cumulative_drag_offset; |
| gfx::Rect new_bounds_in_local = |
| gfx::Rect(new_keyboard_location, keyboard_bounds_in_screen.size()); |
| |
| DisplayUtil display_util; |
| const display::Display& new_display = |
| display_util.FindAdjacentDisplayIfPointIsNearMargin( |
| current_display, current_drag_location); |
| |
| if (current_display.id() == new_display.id()) { |
| delegate_->MoveKeyboardWindow(new_bounds_in_local); |
| } else { |
| // Since the keyboard has jumped across screens, cancel the current |
| // drag descriptor as though the user has lifted their finger. |
| drag_descriptor_.reset(); |
| |
| gfx::Rect new_bounds_in_screen = |
| new_bounds_in_local + |
| current_display.bounds().origin().OffsetFromOrigin(); |
| gfx::Rect contained_new_bounds_in_screen = |
| ContainKeyboardToDisplayBounds(new_bounds_in_screen, |
| new_display.bounds()); |
| |
| // Enqueue a transition to the adjacent display. |
| new_bounds_in_local = |
| contained_new_bounds_in_screen - |
| new_display.bounds().origin().OffsetFromOrigin(); |
| delegate_->MoveKeyboardWindowToDisplay(new_display, |
| new_bounds_in_local); |
| } |
| SavePosition(delegate_->GetBoundsInScreen(), new_display.size()); |
| return true; |
| } |
| break; |
| |
| default: |
| drag_descriptor_.reset(); |
| break; |
| } |
| return false; |
| } |
| |
| bool ContainerFloatingBehavior::HandleGestureEvent( |
| const ui::GestureEvent& event, |
| const gfx::Rect& bounds_in_screen) { |
| return false; |
| } |
| |
| void ContainerFloatingBehavior::SetCanonicalBounds( |
| aura::Window* container, |
| const gfx::Rect& display_bounds) { |
| gfx::Point keyboard_location = |
| GetPositionForShowingKeyboard(container->bounds().size(), display_bounds); |
| gfx::Rect keyboard_bounds_in_screen = |
| gfx::Rect(keyboard_location, container->bounds().size()); |
| SavePosition(keyboard_bounds_in_screen, display_bounds.size()); |
| container->SetBounds(keyboard_bounds_in_screen); |
| } |
| |
| bool ContainerFloatingBehavior::TextBlurHidesKeyboard() const { |
| return true; |
| } |
| |
| gfx::Rect ContainerFloatingBehavior::GetOccludedBounds( |
| const gfx::Rect& visual_bounds_in_screen) const { |
| return {}; |
| } |
| |
| bool ContainerFloatingBehavior::OccludedBoundsAffectWorkspaceLayout() const { |
| return false; |
| } |
| |
| void ContainerFloatingBehavior::SetDraggableArea(const gfx::Rect& rect) { |
| draggable_area_ = rect; |
| } |
| |
| void ContainerFloatingBehavior::SetAreaToRemainOnScreen(const gfx::Rect& rect) { |
| area_in_window_to_remain_on_screen_ = rect; |
| } |
| |
| } // namespace keyboard |