| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/keyboard/container_floating_behavior.h" |
| |
| #include "ui/aura/window.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/keyboard/display_util.h" |
| #include "ui/keyboard/drag_descriptor.h" |
| #include "ui/wm/core/window_animations.h" |
| |
| namespace keyboard { |
| |
| // Length of the animation to show and hide the keyboard. |
| constexpr int kAnimationDurationMs = 200; |
| |
| // Distance the keyboard moves during the animation |
| constexpr int kAnimationDistance = 30; |
| |
| ContainerFloatingBehavior::ContainerFloatingBehavior(Delegate* delegate) |
| : ContainerBehavior(delegate) {} |
| |
| ContainerFloatingBehavior::~ContainerFloatingBehavior() = default; |
| |
| mojom::ContainerType ContainerFloatingBehavior::GetType() const { |
| return mojom::ContainerType::kFloating; |
| } |
| |
| void ContainerFloatingBehavior::DoHidingAnimation( |
| aura::Window* container, |
| ::wm::ScopedHidingAnimationSettings* animation_settings) { |
| animation_settings->layer_animation_settings()->SetTransitionDuration( |
| base::TimeDelta::FromMilliseconds(kAnimationDurationMs)); |
| 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( |
| base::TimeDelta::FromMilliseconds(kAnimationDurationMs)); |
| |
| 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 = requested_bounds_in_screen; |
| |
| if (!default_position_in_screen_) { |
| // If the keyboard hasn't been shown yet, ignore the request and use |
| // default. |
| gfx::Point default_location = GetPositionForShowingKeyboard( |
| keyboard_bounds_in_screen.size(), display_bounds); |
| keyboard_bounds_in_screen = |
| gfx::Rect(default_location, keyboard_bounds_in_screen.size()); |
| } else { |
| // Otherwise, simply make sure that the new bounds are not off the edge of |
| // the screen. |
| keyboard_bounds_in_screen = ContainKeyboardToScreenBounds( |
| keyboard_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::ContainKeyboardToScreenBounds( |
| const gfx::Rect& keyboard_bounds_in_screen, |
| const gfx::Rect& display_bounds) const { |
| int left = keyboard_bounds_in_screen.x(); |
| int top = keyboard_bounds_in_screen.y(); |
| int right = keyboard_bounds_in_screen.right(); |
| int bottom = keyboard_bounds_in_screen.bottom(); |
| |
| // Prevent keyboard from appearing off screen or overlapping with the edge. |
| if (left < display_bounds.x()) { |
| left = display_bounds.x(); |
| right = left + keyboard_bounds_in_screen.width(); |
| } |
| if (right >= display_bounds.right()) { |
| right = display_bounds.right(); |
| left = right - keyboard_bounds_in_screen.width(); |
| } |
| if (top < display_bounds.y()) { |
| top = display_bounds.y(); |
| bottom = top + keyboard_bounds_in_screen.height(); |
| } |
| if (bottom >= display_bounds.bottom()) { |
| bottom = display_bounds.bottom(); |
| top = bottom - keyboard_bounds_in_screen.height(); |
| } |
| |
| return gfx::Rect(left, top, right - left, bottom - top); |
| } |
| |
| 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((int)left); |
| top_left_offset.set_y((int)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 = |
| ContainKeyboardToScreenBounds(keyboard_bounds, display_bounds); |
| |
| return valid_keyboard_bounds.origin(); |
| } |
| |
| bool ContainerFloatingBehavior::IsDragHandle( |
| const gfx::Vector2d& offset, |
| const gfx::Size& keyboard_size) const { |
| return draggable_area_.Contains(offset.x(), offset.y()); |
| } |
| |
| bool ContainerFloatingBehavior::HandlePointerEvent( |
| const ui::LocatedEvent& event, |
| const display::Display& current_display) { |
| auto kb_offset = gfx::Vector2d(event.x(), 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 = -1; |
| 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::ET_TOUCH_PRESSED: |
| case ui::ET_MOUSE_PRESSED: |
| if (!IsDragHandle(kb_offset, keyboard_bounds_in_screen.size())) { |
| drag_descriptor_ = nullptr; |
| } else if (type == ui::ET_MOUSE_PRESSED && |
| !((const ui::MouseEvent*)&event)->IsOnlyLeftMouseButton()) { |
| // Mouse events are limited to just the left mouse button. |
| drag_descriptor_ = nullptr; |
| } else if (!drag_descriptor_) { |
| // If there is no active drag descriptor, start a new one. |
| bool drag_started_by_touch = (type == ui::ET_TOUCH_PRESSED); |
| drag_descriptor_.reset( |
| new DragDescriptor{keyboard_bounds_in_screen.origin(), kb_offset, |
| drag_started_by_touch, pointer_id}); |
| } |
| break; |
| |
| case ui::ET_MOUSE_DRAGGED: |
| case ui::ET_TOUCH_MOVED: |
| if (!drag_descriptor_) { |
| // do nothing |
| } else if (drag_descriptor_->is_touch_drag != |
| (type == ui::ET_TOUCH_MOVED)) { |
| // If the event isn't of the same type that started the drag, end the |
| // drag to prevent confusion. |
| drag_descriptor_ = nullptr; |
| } else if (drag_descriptor_->pointer_id != pointer_id) { |
| // do nothing. |
| } else { |
| // 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_ = nullptr; |
| |
| gfx::Rect new_bounds_in_screen = |
| new_bounds_in_local + |
| current_display.bounds().origin().OffsetFromOrigin(); |
| gfx::Rect contained_new_bounds_in_screen = |
| ContainKeyboardToScreenBounds(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_ = nullptr; |
| break; |
| } |
| 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; |
| } |
| |
| } // namespace keyboard |