Index: remoting/client/desktop_viewport.cc |
diff --git a/remoting/client/desktop_viewport.cc b/remoting/client/desktop_viewport.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f1b3dda978bd0dc1910ea90a3914b3fca7392fad |
--- /dev/null |
+++ b/remoting/client/desktop_viewport.cc |
@@ -0,0 +1,238 @@ |
+// 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 "remoting/client/desktop_viewport.h" |
+ |
+#include <algorithm> |
+ |
+#include "base/logging.h" |
+ |
+namespace remoting { |
+ |
+namespace { |
+ |
+float MAX_ZOOM_LEVEL = 100.f; |
+ |
+} // namespace |
+ |
+DesktopViewport::DesktopViewport() : desktop_to_surface_transform_() {} |
+ |
+DesktopViewport::~DesktopViewport() {} |
+ |
+void DesktopViewport::SetDesktopSize(int desktop_width, int desktop_height) { |
+ bool need_resize_to_fit = !desktop_size_ready_ && surface_size_ready_; |
+ desktop_size_.x = desktop_width; |
+ desktop_size_.y = desktop_height; |
+ desktop_size_ready_ = true; |
+ if (need_resize_to_fit) { |
+ ResizeToFit(); |
+ } else if (IsViewportReady()) { |
+ UpdateViewport(); |
+ } |
+} |
+ |
+void DesktopViewport::SetSurfaceSize(int surface_width, int surface_height) { |
+ bool need_resize_to_fit = desktop_size_ready_ && !surface_size_ready_; |
+ surface_size_.x = surface_width; |
+ surface_size_.y = surface_height; |
+ surface_size_ready_ = true; |
+ if (need_resize_to_fit) { |
+ ResizeToFit(); |
+ } else if (IsViewportReady()) { |
+ UpdateViewport(); |
+ } |
+} |
+ |
+void DesktopViewport::MoveDesktop(float dx, float dy) { |
+ desktop_to_surface_transform_.PostTranslate({dx, dy}); |
+ UpdateViewport(); |
+} |
+ |
+void DesktopViewport::ScaleDesktop(float px, float py, float scale) { |
+ desktop_to_surface_transform_.PostScale({px, py}, scale); |
+ UpdateViewport(); |
+} |
+ |
+void DesktopViewport::SetViewportCenter(float x, float y) { |
+ ViewMatrix::Point old_center = GetViewportCenter(); |
+ MoveViewportCenterWithoutUpdate(x - old_center.x, y - old_center.y); |
+ UpdateViewport(); |
+} |
+ |
+void DesktopViewport::RegisterOnTransformationChangedCallback( |
+ const TransformationCallback& callback, |
+ bool run_immediately) { |
+ on_transformation_changed_ = callback; |
+ if (IsViewportReady() && run_immediately) { |
+ callback.Run(desktop_to_surface_transform_); |
+ } |
+} |
+ |
+void DesktopViewport::ResizeToFit() { |
+ DCHECK(IsViewportReady()); |
+ |
+ // <---Desktop Width----> |
+ // +==========+---------+ |
+ // | | | |
+ // | Viewport | Desktop | |
+ // | | | |
+ // +==========+---------+ |
+ // |
+ // +==========+ ^ |
+ // | | | |
+ // | Viewport | | |
+ // | | | |
+ // +==========+ | Desktop |
+ // | | | Height |
+ // | Desktop | | |
+ // | | | |
+ // +----------+ v |
+ // resize the desktop such that it fits the viewport in one dimension. |
+ |
+ float scale = std::max(surface_size_.x / desktop_size_.x, |
+ surface_size_.y / desktop_size_.y); |
+ desktop_to_surface_transform_.SetScale(scale); |
+ UpdateViewport(); |
+} |
+ |
+bool DesktopViewport::IsViewportReady() const { |
+ return desktop_size_ready_ && surface_size_ready_; |
+} |
+ |
+void DesktopViewport::UpdateViewport() { |
+ if (!IsViewportReady()) { |
+ // User may attempt to zoom and pan before the desktop image is received. |
+ // This should be fine since the viewport will be reset once the image |
+ // dimension is set. |
+ VLOG(1) << "Viewport is not ready yet."; |
+ return; |
+ } |
+ |
+ // Adjust zoom level. |
+ float zoom_level = desktop_to_surface_transform_.GetScale(); |
+ if (zoom_level > MAX_ZOOM_LEVEL) { |
+ // TODO(yuweih): This doesn't account for the effect of the pivot point, |
+ // which will shift the desktop closer to the origin. |
+ desktop_to_surface_transform_.SetScale(MAX_ZOOM_LEVEL); |
+ } |
+ |
+ ViewMatrix::Vector2D desktop_size_on_surface_ = |
+ desktop_to_surface_transform_.MapVector(desktop_size_); |
+ if (desktop_size_on_surface_.x < surface_size_.x && |
+ desktop_size_on_surface_.y < surface_size_.y) { |
+ // +==============+ |
+ // | VP | +==========+ |
+ // | | | VP | |
+ // | +----------+ | +----------+ |
+ // | | DP | | ==> | DP | |
+ // | +----------+ | +----------+ |
+ // | | | | |
+ // | | +==========+ |
+ // +==============+ |
+ // Displayed desktop is too small in both directions, so apply the minimum |
+ // zoom level needed to fit either the width or height. |
+ float scale = std::min(surface_size_.x / desktop_size_.x, |
+ surface_size_.y / desktop_size_.y); |
+ desktop_to_surface_transform_.SetScale(scale); |
+ } |
+ |
+ // Adjust position. |
+ // Scenarios: |
+ // 1. If the viewport can fully fit inside the desktop (smaller or equal width |
+ // and height) but it overlaps with the border, it will be moved in minimum |
+ // distance to be fitted inside the desktop. |
+ // |
+ // +========+ |
+ // | VP | |
+ // | +----|--------+ +========+-----+ |
+ // | | | | | | | |
+ // +========+ | | VP | DP | |
+ // | DP | ==> | | | |
+ // | | +========+ | |
+ // | | | | |
+ // +-------------+ +--------------+ |
+ // |
+ // 2. If the viewport is larger than the desktop, the viewport will always |
+ // share the same center as the desktop. |
+ // |
+ // +==========+------+==+ +======+------+======+ |
+ // | VP | DP | | ==> | VP | DP | | |
+ // +==========+------+==+ +======+------+======+ |
+ // |
+ // This is done on the desktop's reference frame to simplify things a bit. |
+ ViewMatrix::Point old_center = GetViewportCenter(); |
+ ViewMatrix::Point new_center = |
+ ConstrainPointToBounds(GetViewportCenterBounds(), old_center); |
+ MoveViewportCenterWithoutUpdate(new_center.x - old_center.x, |
+ new_center.y - old_center.y); |
+ |
+ if (on_transformation_changed_) { |
+ on_transformation_changed_.Run(desktop_to_surface_transform_); |
+ } |
+} |
+ |
+DesktopViewport::Bounds DesktopViewport::GetViewportCenterBounds() const { |
+ Bounds bounds; |
+ |
+ // Viewport size on the desktop space. |
+ ViewMatrix::Vector2D viewport_size = |
+ desktop_to_surface_transform_.Invert().MapVector(surface_size_); |
+ |
+ // Scenario 1: If VP can fully fit inside the desktop, then VP's center can be |
+ // anywhere inside the desktop as long as VP doesn't overlap with the border. |
+ bounds.left = viewport_size.x / 2.f; |
+ bounds.right = desktop_size_.x - viewport_size.x / 2.f; |
+ bounds.top = viewport_size.y / 2.f; |
+ bounds.bottom = desktop_size_.y - viewport_size.y / 2.f; |
+ |
+ // Scenario 2: If VP can't fully fit inside the desktop in dimension D, then |
+ // its bounds in dimension D is tightly restricted to the center of the |
+ // desktop. |
+ if (bounds.left > bounds.right) { |
+ float desktop_width_center = desktop_size_.x / 2.f; |
+ bounds.left = desktop_width_center; |
+ bounds.right = desktop_width_center; |
+ } |
+ |
+ if (bounds.top > bounds.bottom) { |
+ float desktop_height_center = desktop_size_.y / 2.f; |
+ bounds.top = desktop_height_center; |
+ bounds.bottom = desktop_height_center; |
+ } |
+ |
+ return bounds; |
+} |
+ |
+ViewMatrix::Point DesktopViewport::GetViewportCenter() const { |
+ return desktop_to_surface_transform_.Invert().MapPoint( |
+ {surface_size_.x / 2.f, surface_size_.y / 2.f}); |
+} |
+ |
+void DesktopViewport::MoveViewportCenterWithoutUpdate(float dx, float dy) { |
+ // <dx, dy> is defined on desktop's reference frame. Translation must be |
+ // flipped and scaled. |
+ desktop_to_surface_transform_.PostTranslate( |
+ desktop_to_surface_transform_.MapVector({-dx, -dy})); |
+} |
+ |
+// static |
+ViewMatrix::Point DesktopViewport::ConstrainPointToBounds( |
+ const Bounds& bounds, |
+ const ViewMatrix::Point& point) { |
+ ViewMatrix::Point new_point = point; |
+ if (new_point.x < bounds.left) { |
+ new_point.x = bounds.left; |
+ } else if (new_point.x > bounds.right) { |
+ new_point.x = bounds.right; |
+ } |
+ |
+ if (new_point.y < bounds.top) { |
+ new_point.y = bounds.top; |
+ } else if (new_point.y > bounds.bottom) { |
+ new_point.y = bounds.bottom; |
+ } |
+ return new_point; |
+} |
+ |
+} // namespace remoting |