Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e075d9ce authored by Matt Casey's avatar Matt Casey
Browse files

Add MagnifierView to long screenshot UI.

- Add a listener for CropView so MagnifierView can react to its motion
  events.
- Make CropView track deltas over the entire motion sequence instead of
  per-input (tiny deltas were causing floating precision drift)
- Animate the MagnifierView in and out.

Lots more to do including smarter view placement, better handling of
display at the edges, etc, but this seems like a good point to pause and
check in.

Bug: 179499370
Test: Invocation of long screenshot, observe magnification when altering
      crop boundaries.
Change-Id: Iae721afe835882f383b5f46f6159c06c4c2ebca4
parent 18908772
Loading
Loading
Loading
Loading
+18 −4
Original line number Diff line number Diff line
@@ -74,7 +74,7 @@
        android:id="@+id/preview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:layout_marginBottom="42dp"
        android:layout_marginHorizontal="48dp"
        android:adjustViewBounds="true"
        app:layout_constrainedHeight="true"
@@ -91,19 +91,33 @@
        android:id="@+id/crop_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:layout_marginBottom="42dp"
        app:layout_constrainedHeight="true"
        app:layout_constrainedWidth="true"
        app:layout_constraintTop_toBottomOf="@id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:handleThickness="3dp"
        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
        app:handleColor="@*android:color/accent_device_default"
        app:scrimColor="#9444"
        app:scrimColor="@color/screenshot_crop_scrim"
        tools:background="?android:colorBackground"
        tools:minHeight="100dp"
        tools:minWidth="100dp" />

    <com.android.systemui.screenshot.MagnifierView
        android:id="@+id/magnifier"
        android:visibility="invisible"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:layout_constraintTop_toBottomOf="@id/guideline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
        app:handleColor="@*android:color/accent_device_default"
        app:scrimColor="@color/screenshot_crop_scrim"
        app:borderThickness="4dp"
        app:borderColor="#fff"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
+8 −0
Original line number Diff line number Diff line
@@ -178,6 +178,14 @@
        <attr name="scrimColor" format="color" />
    </declare-styleable>

    <declare-styleable name="MagnifierView">
        <attr name="handleThickness" format="dimension" />
        <attr name="handleColor" format="color" />
        <attr name="scrimColor" format="color" />
        <attr name="borderThickness" format="dimension" />
        <attr name="borderColor" format="color" />
    </declare-styleable>

    <declare-styleable name="RoundedCornerProgressDrawable">
        <attr name="android:drawable" />
    </declare-styleable>
+3 −0
Original line number Diff line number Diff line
@@ -197,6 +197,9 @@
    <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
    <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->

    <!-- Long screenshot UI -->
    <color name="screenshot_crop_scrim">#9444</color>

    <!-- GM2 colors -->
    <color name="GM2_grey_50">#F8F9FA</color>
    <color name="GM2_grey_100">#F1F3F4</color>
+1 −0
Original line number Diff line number Diff line
@@ -345,6 +345,7 @@
    <dimen name="screenshot_action_chip_padding_end">16dp</dimen>
    <dimen name="screenshot_action_chip_text_size">14sp</dimen>
    <dimen name="screenshot_dismissal_height_delta">80dp</dimen>
    <dimen name="screenshot_crop_handle_thickness">3dp</dimen>


    <!-- The width of the view containing navigation buttons -->
+77 −28
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ import com.android.systemui.R;
 * cropped out.
 */
public class CropView extends View {
    private enum CropBoundary {
    public enum CropBoundary {
        NONE, TOP, BOTTOM
    }

@@ -48,8 +48,14 @@ public class CropView extends View {
    private float mTopCrop = 0f;
    private float mBottomCrop = 1f;

    // When the user is dragging a handle, these variables store the distance between the top/bottom
    // crop values and
    private float mTopDelta = 0f;
    private float mBottomDelta = 0f;

    private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
    private float mLastY;
    private float mStartingY;  // y coordinate of ACTION_DOWN
    private CropInteractionListener mCropInteractionListener;

    public CropView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
@@ -73,54 +79,84 @@ public class CropView extends View {
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawShade(canvas, 0, mTopCrop);
        drawShade(canvas, mBottomCrop, 1f);
        drawHandle(canvas, mTopCrop);
        drawHandle(canvas, mBottomCrop);
        float top = mTopCrop + mTopDelta;
        float bottom = mBottomCrop + mBottomDelta;
        drawShade(canvas, 0, top);
        drawShade(canvas, bottom, 1f);
        drawHandle(canvas, top);
        drawHandle(canvas, bottom);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int topPx = fractionToPixels(mTopCrop);
        int bottomPx = fractionToPixels(mBottomCrop);
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                mLastY = event.getY();
                    mStartingY = event.getY();
                    updateListener(event);
                }
                return true;
        }
        if (event.getAction() == MotionEvent.ACTION_MOVE
                && mCurrentDraggingBoundary != CropBoundary.NONE) {
            float delta = event.getY() - mLastY;
            case MotionEvent.ACTION_MOVE:
                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                    float delta = event.getY() - mStartingY;
                    if (mCurrentDraggingBoundary == CropBoundary.TOP) {
                mTopCrop = pixelsToFraction((int) MathUtils.constrain(topPx + delta, 0,
                        bottomPx - 2 * mCropTouchMargin));
                        mTopDelta = pixelsToFraction((int) MathUtils.constrain(delta, -topPx,
                                bottomPx - 2 * mCropTouchMargin - topPx));
                    } else {  // Bottom
                mBottomCrop = pixelsToFraction((int) MathUtils.constrain(bottomPx + delta,
                        topPx + 2 * mCropTouchMargin, getMeasuredHeight()));
                        mBottomDelta = pixelsToFraction((int) MathUtils.constrain(delta,
                                topPx + 2 * mCropTouchMargin - bottomPx,
                                getMeasuredHeight() - bottomPx));
                    }
            mLastY = event.getY();
                    updateListener(event);
                    invalidate();
                    return true;
                }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
                    // Commit the delta to the stored crop values.
                    mTopCrop += mTopDelta;
                    mBottomCrop += mBottomDelta;
                    mTopDelta = 0;
                    mBottomDelta = 0;
                    updateListener(event);
                }
        }
        return super.onTouchEvent(event);
    }

    /**
     * @return value [0,1] representing the position of the top crop boundary.
     * @return value [0,1] representing the position of the top crop boundary. Does not reflect
     * changes from any in-progress touch input.
     */
    public float getTopBoundary() {
        return mTopCrop;
    }

    /**
     * @return value [0,1] representing the position of the bottom crop boundary.
     * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect
     * changes from any in-progress touch input.
     */
    public float getBottomBoundary() {
        return mBottomCrop;
    }

    public void setCropInteractionListener(CropInteractionListener listener) {
        mCropInteractionListener = listener;
    }

    private void updateListener(MotionEvent event) {
        if (mCropInteractionListener != null) {
            float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
                    ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta;
            mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary,
                    boundaryPosition, fractionToPixels(boundaryPosition));
        }
    }

    private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
        canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(),
                fractionToPixels(fracEnd), mShadePaint);
@@ -148,4 +184,17 @@ public class CropView extends View {
        }
        return CropBoundary.NONE;
    }

    /**
     * Listen for crop motion events and state.
     */
    public interface CropInteractionListener {
        /**
         * Called whenever CropView has a MotionEvent that can impact the position of the crop
         * boundaries.
         */
        void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
                int boundaryPositionPx);

    }
}
Loading