blob: 9b9d58c9114d7c5a38d3f116f273984a4a80d634 [file] [log] [blame]
/*
* Copyright (C) 2014 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.systemui.recents.views;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* The layout logic for the contents of the freeform workspace.
*/
public class FreeformWorkspaceLayoutAlgorithm {
private static final String TAG = "FreeformWorkspaceLayoutAlgorithm";
private static final boolean DEBUG = false;
// Optimization, allows for quick lookup of task -> rect
private HashMap<Task.TaskKey, RectF> mTaskRectMap = new HashMap<>();
/**
* Updates the layout for each of the freeform workspace tasks. This is called after the stack
* layout is updated.
*/
public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
Collections.reverse(freeformTasks);
mTaskRectMap.clear();
int numFreeformTasks = stackLayout.mNumFreeformTasks;
if (!freeformTasks.isEmpty()) {
// Normalize the widths so that we can calculate the best layout below
int workspaceWidth = stackLayout.mFreeformRect.width();
int workspaceHeight = stackLayout.mFreeformRect.height();
float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
float normalizedWorkspaceHeight = 1f;
float[] normalizedTaskWidths = new float[numFreeformTasks];
for (int i = 0; i < numFreeformTasks; i++) {
Task task = freeformTasks.get(i);
float rowTaskWidth;
if (task.bounds != null) {
rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
} else {
// If this is a stack task that was dragged into the freeform workspace, then
// the task will not yet have an associated bounds, so assume the full workspace
// width for the time being
rowTaskWidth = normalizedWorkspaceWidth;
}
// Bound the task width to the workspace width so that at the worst case, it will
// fit its own row
normalizedTaskWidths[i] = Math.min(rowTaskWidth,
normalizedWorkspaceWidth);
}
// Determine the scale to best fit each of the tasks in the workspace
float rowScale = 0.85f;
float rowWidth = 0f;
float maxRowWidth = 0f;
int rowCount = 1;
for (int i = 0; i < numFreeformTasks;) {
float width = normalizedTaskWidths[i] * rowScale;
if (rowWidth + width > normalizedWorkspaceWidth) {
// That is too long for this row, create new row
rowWidth = 0f;
if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
// The new row is too high, so we need to try fitting again. Update the
// scale to be the smaller of the scale needed to fit the task in the
// previous row, or the scale needed to fit the new row
rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
normalizedWorkspaceHeight / (rowCount + 1));
rowCount = 1;
i = 0;
} else {
// The new row fits, so continue
rowCount++;
i++;
}
} else {
// Task is OK in this row
rowWidth += width;
i++;
}
maxRowWidth = Math.max(rowWidth, maxRowWidth);
}
// Normalize each of the actual rects to that scale
int height = (int) (rowScale * workspaceHeight);
float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
workspaceWidth) / 2f;
float rowLeft = defaultRowLeft;
for (int i = 0; i < numFreeformTasks; i++) {
Task task = freeformTasks.get(i);
int width = (int) (height * normalizedTaskWidths[i]);
if (rowLeft + width > workspaceWidth) {
// This goes on the next line
rowTop += height;
rowLeft = defaultRowLeft;
}
RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + height);
rowLeft += width;
mTaskRectMap.put(task.key, rect);
}
}
}
/**
* Returns whether the transform is available for the given task.
*/
public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
if (stackLayout.mNumFreeformTasks == 0 || task == null) {
return false;
}
return mTaskRectMap.containsKey(task.key);
}
/**
* Returns the transform for the given task. Any rect returned will be offset by the actual
* transform for the freeform workspace.
*/
public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
TaskStackLayoutAlgorithm stackLayout) {
if (mTaskRectMap.containsKey(task.key)) {
Rect taskRect = stackLayout.mTaskRect;
RectF ffRect = mTaskRectMap.get(task.key);
float scale = Math.max(ffRect.width() / taskRect.width(),
ffRect.height() / taskRect.height());
int topOffset = (stackLayout.mFreeformRect.top - taskRect.top);
int scaleXOffset = (int) (((1f - scale) * taskRect.width()) / 2);
int scaleYOffset = (int) (((1f - scale) * taskRect.height()) / 2);
transformOut.scale = scale * 0.95f;
transformOut.translationX = (int) (ffRect.left - scaleXOffset);
transformOut.translationY = (int) (topOffset + ffRect.top - scaleYOffset);
transformOut.translationZ = stackLayout.mMaxTranslationZ;
transformOut.clipBottom = (int) (taskRect.height() - (ffRect.height() / scale));
transformOut.clipRight = (int) (taskRect.width() - (ffRect.width() / scale));
if (task.thumbnail != null) {
transformOut.thumbnailScale = Math.min(
((float) taskRect.width() - transformOut.clipRight) /
task.thumbnail.getWidth(),
((float) taskRect.height() - transformOut.clipBottom) /
task.thumbnail.getHeight());
} else {
transformOut.thumbnailScale = 1f;
}
transformOut.rect.set(taskRect);
transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.rect.right -= transformOut.clipRight * scale;
transformOut.rect.bottom -= transformOut.clipBottom * scale;
transformOut.visible = true;
transformOut.p = 1f;
if (DEBUG) {
Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
}
return transformOut;
}
return null;
}
}