Files
Lawnchair/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
T
Sergey Pinkevich f595fc3164 Place app icons in correct place in RTL for Seascape mode
The goal for this CL is to show icons for split apps (similar to Landscape mode). The aligning (perfect pixel, aligned to the task bounds) can be done in a separate task

Bug: 317072320
Flag: NONE
Test: manually

Old behavior: https://drive.google.com/file/d/11UQyEB-EhKYu1eisjAW_MnhV-drrT38E/view?usp=sharing (icons are missing in RTL mode)

New behavior: https://drive.google.com/file/d/1GrZe6J8zBIoHtP7DzgZnpJiyz2XWujLf/view?usp=sharing

Change-Id: I2fe7defd1c668326ab0e0114a4cd9793e66d6175
2024-01-10 19:50:49 +00:00

393 lines
17 KiB
Java

/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.touch;
import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.CENTER_VERTICAL;
import static android.view.Gravity.END;
import static android.view.Gravity.RIGHT;
import static android.view.Gravity.START;
import static android.view.Gravity.TOP;
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.android.launcher3.Flags.enableOverviewIconMenu;
import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Pair;
import android.view.Gravity;
import android.view.Surface;
import android.view.View;
import android.widget.FrameLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.BaseDragLayer;
import java.util.Collections;
import java.util.List;
public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
@Override
public int getSecondaryTranslationDirectionFactor() {
return -1;
}
@Override
public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
return -1;
} else {
return 1;
}
}
@Override
public boolean getRecentsRtlSetting(Resources resources) {
return Utilities.isRtl(resources);
}
@Override
public float getDegreesRotated() {
return 270;
}
@Override
public int getRotation() {
return Surface.ROTATION_270;
}
@Override
public void adjustFloatingIconStartVelocity(PointF velocity) {
float oldX = velocity.x;
float oldY = velocity.y;
velocity.set(oldY, -oldX);
}
@Override
public float getTaskMenuX(float x, View thumbnailView,
DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
if (enableOverviewIconMenu()) {
return x + (taskViewIcon.getHeight() * 2);
}
return x + taskInsetMargin;
}
@Override
public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
View taskMenuView, float taskInsetMargin, View taskViewIcon) {
if (enableOverviewIconMenu()) {
return y + taskViewIcon.getWidth() + (
thumbnailView.getLayoutDirection() == LAYOUT_DIRECTION_RTL ? taskInsetMargin
/ 2f : -taskViewIcon.getHeight());
}
BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskMenuView.getLayoutParams();
int taskMenuWidth = lp.width;
if (stagePosition == STAGE_POSITION_UNDEFINED) {
return y + taskInsetMargin
+ (thumbnailView.getMeasuredHeight() + taskMenuWidth) / 2f;
} else {
return y + taskMenuWidth + taskInsetMargin;
}
}
@Override
public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, SplitBounds splitInfo,
int desiredStagePosition) {
float topLeftTaskPercent = splitInfo.appsStackedVertically
? splitInfo.topTaskPercent
: splitInfo.leftTaskPercent;
float dividerBarPercent = splitInfo.appsStackedVertically
? splitInfo.dividerHeightPercent
: splitInfo.dividerWidthPercent;
// In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
// the screen. This is to preserve consistency when the user rotates: From the user's POV,
// the primary should always be on the left.
if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
outRect.top += (int) (outRect.height() * ((1 - topLeftTaskPercent)));
} else {
outRect.bottom -= (int) (outRect.height() * (topLeftTaskPercent + dividerBarPercent));
}
}
@Override
public Pair<Float, Float> getDwbLayoutTranslations(int taskViewWidth,
int taskViewHeight, SplitBounds splitBounds, DeviceProfile deviceProfile,
View[] thumbnailViews, int desiredTaskId, View banner) {
boolean isRtl = banner.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
float translationX = 0;
float translationY = 0;
FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
banner.setPivotX(0);
banner.setPivotY(0);
banner.setRotation(getDegreesRotated());
translationX = taskViewWidth - banner.getHeight();
FrameLayout.LayoutParams snapshotParams =
(FrameLayout.LayoutParams) thumbnailViews[0]
.getLayoutParams();
bannerParams.gravity = BOTTOM | (isRtl ? END : START);
if (splitBounds == null) {
// Single, fullscreen case
bannerParams.width = taskViewHeight - snapshotParams.topMargin;
translationY = banner.getHeight();
return new Pair<>(translationX, translationY);
}
// Set correct width
if (desiredTaskId == splitBounds.leftTopTaskId) {
bannerParams.width = thumbnailViews[0].getMeasuredHeight();
} else {
bannerParams.width = thumbnailViews[1].getMeasuredHeight();
}
// Set translations
if (desiredTaskId == splitBounds.rightBottomTaskId) {
translationY = banner.getHeight();
}
if (desiredTaskId == splitBounds.leftTopTaskId) {
float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
? (1f - splitBounds.topTaskPercent)
: (1f - splitBounds.leftTaskPercent);
translationY = banner.getHeight()
- ((taskViewHeight - snapshotParams.topMargin)
* bottomRightTaskPlusDividerPercent);
}
return new Pair<>(translationX, translationY);
}
@Override
public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
return dp.widthPx - rect.right;
}
@Override
public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
// Add "right" option which is actually the top
return Collections.singletonList(new SplitPositionOption(
R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
}
@Override
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
int splitInstructionsWidth) {
out.setPivotX(0);
out.setPivotY(splitInstructionsHeight);
out.setRotation(getDegreesRotated());
int distanceToEdge = out.getResources().getDimensionPixelSize(
R.dimen.split_instructions_bottom_margin_phone_landscape);
// Adjust for any insets on the right edge
int insetCorrectionX = dp.getInsets().right;
// Center the view in case of unbalanced insets on top or bottom of screen
int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
out.setTranslationX(splitInstructionsWidth - distanceToEdge + insetCorrectionX);
out.setTranslationY(((-splitInstructionsHeight + splitInstructionsWidth) / 2f)
+ insetCorrectionY);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
// Setting gravity to RIGHT instead of the lint-recommended END because we always want this
// view to be screen-right when phone is in seascape, regardless of the RtL setting.
lp.gravity = RIGHT | CENTER_VERTICAL;
out.setLayoutParams(lp);
}
@Override
public void setTaskIconParams(FrameLayout.LayoutParams iconParams,
int taskIconMargin, int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
iconParams.rightMargin = 0;
iconParams.bottomMargin = 0;
if (enableOverviewIconMenu()) {
iconParams.setMarginStart(taskIconMargin);
iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
iconParams.leftMargin = 0;
iconParams.topMargin = 0;
} else {
iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
iconParams.leftMargin = -taskIconHeight - taskIconMargin / 2;
iconParams.topMargin = thumbnailTopMargin / 2;
}
}
@Override
public void setIconAppChipMenuParams(View iconAppChipMenuView,
FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
boolean isRtl = iconAppChipMenuView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
iconMenuParams.gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
iconMenuParams.setMarginStart(0);
iconMenuParams.topMargin = isRtl ? iconMenuMargin : 0;
iconMenuParams.bottomMargin = 0;
iconMenuParams.setMarginEnd(isRtl ? thumbnailTopMargin : 0);
// Use half menu height to place the pivot within the X/Y center of icon in the menu.
float iconCenter = iconAppChipMenuView.getHeight() / 2f;
iconAppChipMenuView.setPivotX(isRtl ? iconMenuParams.width / 2f : iconCenter);
iconAppChipMenuView.setPivotY(
isRtl ? iconMenuParams.width / 2f : iconCenter - iconMenuMargin);
iconAppChipMenuView.setTranslationY(0);
iconAppChipMenuView.setRotation(getDegreesRotated());
}
@Override
public void setSplitIconParams(View primaryIconView, View secondaryIconView,
int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
DeviceProfile deviceProfile, SplitBounds splitConfig) {
super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
primarySnapshotWidth, primarySnapshotHeight, groupedTaskViewHeight,
groupedTaskViewWidth, isRtl, deviceProfile, splitConfig);
FrameLayout.LayoutParams primaryIconParams =
(FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
FrameLayout.LayoutParams secondaryIconParams =
(FrameLayout.LayoutParams) secondaryIconView.getLayoutParams();
// We calculate the "midpoint" of the thumbnail area, and place the icons there.
// This is the place where the thumbnail area splits by default, in a near-50/50 split.
// It is usually not exactly 50/50, due to insets/screen cutouts.
int fullscreenInsetThickness = deviceProfile.getInsets().top
- deviceProfile.getInsets().bottom;
int fullscreenMidpointFromBottom = ((deviceProfile.heightPx
- fullscreenInsetThickness) / 2);
float midpointFromBottomPct = (float) fullscreenMidpointFromBottom / deviceProfile.heightPx;
float insetPct = (float) fullscreenInsetThickness / deviceProfile.heightPx;
int spaceAboveSnapshots = deviceProfile.overviewTaskThumbnailTopMarginPx;
int overviewThumbnailAreaThickness = groupedTaskViewHeight - spaceAboveSnapshots;
int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness * midpointFromBottomPct);
int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
int gravity = (isRtl ? TOP : BOTTOM) | (isRtl ? END : START);
primaryIconParams.gravity = gravity;
secondaryIconParams.gravity = gravity;
primaryIconView.setTranslationX(0);
secondaryIconView.setTranslationX(0);
if (enableOverviewIconMenu()) {
if (isRtl) {
primaryIconView.setTranslationY(groupedTaskViewHeight - primarySnapshotHeight);
secondaryIconView.setTranslationY(0);
} else {
secondaryIconView.setTranslationY(-primarySnapshotHeight);
primaryIconView.setTranslationY(0);
}
} else if (splitConfig.initiatedFromSeascape) {
// if the split was initiated from seascape,
// the task on the right (secondary) is slightly larger
if (isRtl) {
primaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset
+ taskIconHeight);
secondaryIconView.setTranslationY(bottomToMidpointOffset - insetOffset);
} else {
primaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset
+ taskIconHeight);
secondaryIconView.setTranslationY(-bottomToMidpointOffset - insetOffset);
}
} else {
// if not,
// the task on the left (primary) is slightly larger
if (isRtl) {
primaryIconView.setTranslationY(bottomToMidpointOffset + taskIconHeight);
secondaryIconView.setTranslationY(bottomToMidpointOffset);
} else {
primaryIconView.setTranslationY(-bottomToMidpointOffset + taskIconHeight);
secondaryIconView.setTranslationY(-bottomToMidpointOffset);
}
}
primaryIconView.setLayoutParams(primaryIconParams);
secondaryIconView.setLayoutParams(secondaryIconParams);
}
@Override
public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
int parentWidth, int parentHeight, SplitBounds splitBoundsConfig, DeviceProfile dp,
boolean isRtl) {
FrameLayout.LayoutParams primaryParams =
(FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
FrameLayout.LayoutParams secondaryParams =
(FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
// Swap the margins that are set in TaskView#setRecentsOrientedState()
secondaryParams.topMargin = dp.overviewTaskThumbnailTopMarginPx;
primaryParams.topMargin = 0;
// Measure and layout the thumbnails bottom up, since the primary is on the visual left
// (portrait bottom) and secondary is on the right (portrait top)
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
int dividerBar = Math.round(totalThumbnailHeight * (splitBoundsConfig.appsStackedVertically
? splitBoundsConfig.dividerHeightPercent
: splitBoundsConfig.dividerWidthPercent));
int primarySnapshotHeight;
int primarySnapshotWidth;
int secondarySnapshotHeight;
int secondarySnapshotWidth;
float taskPercent = splitBoundsConfig.appsStackedVertically ?
splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent;
primarySnapshotWidth = parentWidth;
primarySnapshotHeight = (int) (totalThumbnailHeight * (taskPercent));
secondarySnapshotWidth = parentWidth;
secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar;
secondarySnapshot.setTranslationY(0);
primarySnapshot.setTranslationY(secondarySnapshotHeight + spaceAboveSnapshot + dividerBar);
primarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)
);
secondarySnapshot.measure(
View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)
);
}
/* ---------- The following are only used by TaskViewTouchHandler. ---------- */
@Override
public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
return HORIZONTAL;
}
@Override
public int getUpDirection(boolean isRtl) {
return isRtl ? SingleAxisSwipeDetector.DIRECTION_POSITIVE
: SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
}
@Override
public boolean isGoingUp(float displacement, boolean isRtl) {
return isRtl ? displacement > 0 : displacement < 0;
}
@Override
public int getTaskDragDisplacementFactor(boolean isRtl) {
return isRtl ? -1 : 1;
}
/* -------------------- */
}