Loading packages/SystemUI/customization/res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ <dimen name="small_clock_padding_top">28dp</dimen> <dimen name="clock_padding_start">28dp</dimen> <dimen name="weather_date_icon_padding">28dp</dimen> <dimen name="clock_vertical_digit_buffer">8dp</dimen> <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> Loading packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt→packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt +12 −15 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * Copyright (C) 2025 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. Loading @@ -16,20 +16,17 @@ package com.android.systemui.shared.clocks import android.graphics.Rect import android.view.View import android.graphics.Canvas fun computeLayoutDiff( view: View, targetRegion: Rect, isLargeClock: Boolean, ): Pair<Float, Float> { val parent = view.parent if (parent is View && parent.isLaidOut() && isLargeClock) { return Pair( targetRegion.centerX() - parent.width / 2f, targetRegion.centerY() - parent.height / 2f ) object CanvasUtil { fun Canvas.translate(pt: VPointF) = this.translate(pt.x, pt.y) fun Canvas.translate(pt: VPoint) = this.translate(pt.x.toFloat(), pt.y.toFloat()) fun <T> Canvas.use(func: (Canvas) -> T): T { val saveNum = save() val result = func(this) restoreToCount(saveNum) return result } return Pair(0f, 0f) } packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt +18 −36 Original line number Diff line number Diff line Loading @@ -20,57 +20,42 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.TimeInterpolator import android.animation.ValueAnimator import android.graphics.Point import com.android.systemui.shared.clocks.VPointF.Companion.times class DigitTranslateAnimator(val updateCallback: () -> Unit) { val DEFAULT_ANIMATION_DURATION = 500L val updatedTranslate = Point(0, 0) class DigitTranslateAnimator(private val updateCallback: (VPointF) -> Unit) { var currentTranslation = VPointF.ZERO var baseTranslation = VPointF.ZERO var targetTranslation = VPointF.ZERO val baseTranslation = Point(0, 0) var targetTranslation: Point? = null val bounceAnimator: ValueAnimator = private val bounceAnimator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { duration = DEFAULT_ANIMATION_DURATION addUpdateListener { updateTranslation(it.animatedFraction, updatedTranslate) updateCallback() } addUpdateListener { updateCallback(getInterpolatedTranslation(it.animatedFraction)) } addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rebase() baseTranslation = currentTranslation } override fun onAnimationCancel(animation: Animator) { rebase() baseTranslation = currentTranslation } } ) } fun rebase() { baseTranslation.x = updatedTranslate.x baseTranslation.y = updatedTranslate.y } fun animatePosition( animate: Boolean = true, delay: Long = 0, duration: Long = -1L, duration: Long, interpolator: TimeInterpolator? = null, targetTranslation: Point? = null, targetTranslation: VPointF, onAnimationEnd: Runnable? = null, ) { this.targetTranslation = targetTranslation ?: Point(0, 0) this.targetTranslation = targetTranslation if (animate) { bounceAnimator.cancel() bounceAnimator.startDelay = delay bounceAnimator.duration = if (duration == -1L) { DEFAULT_ANIMATION_DURATION } else { duration } bounceAnimator.duration = duration interpolator?.let { bounceAnimator.interpolator = it } if (onAnimationEnd != null) { val listener = Loading @@ -89,16 +74,13 @@ class DigitTranslateAnimator(val updateCallback: () -> Unit) { bounceAnimator.start() } else { // No animation is requested, thus set base and target state to the same state. updateTranslation(1F, updatedTranslate) rebase() updateCallback() currentTranslation = targetTranslation baseTranslation = targetTranslation updateCallback(targetTranslation) } } fun updateTranslation(progress: Float, outPoint: Point) { outPoint.x = (baseTranslation.x + progress * (targetTranslation!!.x - baseTranslation.x)).toInt() outPoint.y = (baseTranslation.y + progress * (targetTranslation!!.y - baseTranslation.y)).toInt() fun getInterpolatedTranslation(progress: Float): VPointF { return baseTranslation + progress * (targetTranslation - baseTranslation) } } packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +7 −5 Original line number Diff line number Diff line Loading @@ -35,12 +35,14 @@ import com.android.systemui.plugins.clocks.DefaultClockFaceLayout import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff import com.android.systemui.shared.clocks.view.FlexClockView import com.android.systemui.shared.clocks.view.HorizontalAlignment import com.android.systemui.shared.clocks.view.VerticalAlignment import java.util.Locale import java.util.TimeZone import kotlin.math.max import kotlin.math.roundToInt // TODO(b/364680879): Merge w/ ComposedDigitalLayerController class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: Boolean) : Loading Loading @@ -168,17 +170,17 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: else targetRegion.height() / maxHeight FrameLayout.LayoutParams( (maxWidth * scale).toInt(), (maxHeight * scale).toInt(), (maxWidth * scale).roundToInt(), (maxHeight * scale).roundToInt(), ) } lp.gravity = Gravity.CENTER view.layoutParams = lp targetRegion?.let { val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock) view.translationX = xDiff view.translationY = yDiff val diff = view.computeLayoutDiff(it, isLargeClock) view.translationX = diff.x view.translationY = diff.y } } Loading packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt 0 → 100644 +224 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.shared.clocks import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF import kotlin.math.abs import kotlin.math.max import kotlin.math.min import kotlin.math.sqrt private val X_MASK: ULong = 0xFFFFFFFF00000000U private val Y_MASK: ULong = 0x00000000FFFFFFFFU private fun unpackX(data: ULong): Int = ((data and X_MASK) shr 32).toInt() private fun unpackY(data: ULong): Int = (data and Y_MASK).toInt() private fun pack(x: Int, y: Int): ULong { return ((x.toULong() shl 32) and X_MASK) or (y.toULong() and Y_MASK) } @JvmInline value class VPointF(private val data: ULong) { val x: Float get() = Float.fromBits(unpackX(data)) val y: Float get() = Float.fromBits(unpackY(data)) constructor() : this(0f, 0f) constructor(pt: PointF) : this(pt.x, pt.y) constructor(x: Int, y: Int) : this(x.toFloat(), y.toFloat()) constructor(x: Int, y: Float) : this(x.toFloat(), y) constructor(x: Float, y: Int) : this(x, y.toFloat()) constructor(x: Float, y: Float) : this(pack(x.toBits(), y.toBits())) fun toPointF() = PointF(x, y) fun lengthSq(): Float = x * x + y * y fun length(): Float = sqrt(lengthSq()) fun abs() = VPointF(abs(x), abs(y)) fun dot(pt: VPointF): Float = x * pt.x + y * pt.y fun normalize(): VPointF { val length = this.length() return VPointF(x / length, y / length) } operator fun component1(): Float = x operator fun component2(): Float = y override fun toString() = "($x, $y)" operator fun plus(pt: VPoint) = VPointF(x + pt.x, y + pt.y) operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y) operator fun plus(value: Int) = VPointF(x + value, y + value) operator fun plus(value: Float) = VPointF(x + value, y + value) operator fun minus(pt: VPoint) = VPointF(x - pt.x, y - pt.y) operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y) operator fun minus(value: Int) = VPointF(x - value, y - value) operator fun minus(value: Float) = VPointF(x - value, y - value) operator fun times(pt: VPoint) = VPointF(x * pt.x, y * pt.y) operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y) operator fun times(value: Int) = VPointF(x * value, y * value) operator fun times(value: Float) = VPointF(x * value, y * value) operator fun div(pt: VPoint) = VPointF(x / pt.x, y / pt.y) operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y) operator fun div(value: Int) = VPointF(x / value, y / value) operator fun div(value: Float) = VPointF(x / value, y / value) companion object { val ZERO = VPointF(0, 0) fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) operator fun Float.plus(value: VPointF) = VPointF(this + value.x, this + value.y) operator fun Int.minus(value: VPointF) = VPointF(this - value.x, this - value.y) operator fun Float.minus(value: VPointF) = VPointF(this - value.x, this - value.y) operator fun Int.times(value: VPointF) = VPointF(this * value.x, this * value.y) operator fun Float.times(value: VPointF) = VPointF(this * value.x, this * value.y) operator fun Int.div(value: VPointF) = VPointF(this / value.x, this / value.y) operator fun Float.div(value: VPointF) = VPointF(this / value.x, this / value.y) val RectF.center: VPointF get() = VPointF(centerX(), centerY()) val RectF.size: VPointF get() = VPointF(width(), height()) } } @JvmInline value class VPoint(private val data: ULong) { val x: Int get() = unpackX(data) val y: Int get() = unpackY(data) constructor() : this(0, 0) constructor(x: Int, y: Int) : this(pack(x, y)) fun toPoint() = Point(x, y) fun abs() = VPoint(abs(x), abs(y)) operator fun component1(): Int = x operator fun component2(): Int = y override fun toString() = "($x, $y)" operator fun plus(pt: VPoint) = VPoint(x + pt.x, y + pt.y) operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y) operator fun plus(value: Int) = VPoint(x + value, y + value) operator fun plus(value: Float) = VPointF(x + value, y + value) operator fun minus(pt: VPoint) = VPoint(x - pt.x, y - pt.y) operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y) operator fun minus(value: Int) = VPoint(x - value, y - value) operator fun minus(value: Float) = VPointF(x - value, y - value) operator fun times(pt: VPoint) = VPoint(x * pt.x, y * pt.y) operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y) operator fun times(value: Int) = VPoint(x * value, y * value) operator fun times(value: Float) = VPointF(x * value, y * value) operator fun div(pt: VPoint) = VPoint(x / pt.x, y / pt.y) operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y) operator fun div(value: Int) = VPoint(x / value, y / value) operator fun div(value: Float) = VPointF(x / value, y / value) companion object { val ZERO = VPoint(0, 0) fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) operator fun Int.plus(value: VPoint) = VPoint(this + value.x, this + value.y) operator fun Float.plus(value: VPoint) = VPointF(this + value.x, this + value.y) operator fun Int.minus(value: VPoint) = VPoint(this - value.x, this - value.y) operator fun Float.minus(value: VPoint) = VPointF(this - value.x, this - value.y) operator fun Int.times(value: VPoint) = VPoint(this * value.x, this * value.y) operator fun Float.times(value: VPoint) = VPointF(this * value.x, this * value.y) operator fun Int.div(value: VPoint) = VPoint(this / value.x, this / value.y) operator fun Float.div(value: VPoint) = VPointF(this / value.x, this / value.y) val Rect.center: VPoint get() = VPoint(centerX(), centerY()) val Rect.size: VPoint get() = VPoint(width(), height()) } } Loading
packages/SystemUI/customization/res/values/dimens.xml +1 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ <dimen name="small_clock_padding_top">28dp</dimen> <dimen name="clock_padding_start">28dp</dimen> <dimen name="weather_date_icon_padding">28dp</dimen> <dimen name="clock_vertical_digit_buffer">8dp</dimen> <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> Loading
packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt→packages/SystemUI/customization/src/com/android/systemui/shared/clocks/CanvasUtil.kt +12 −15 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * Copyright (C) 2025 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. Loading @@ -16,20 +16,17 @@ package com.android.systemui.shared.clocks import android.graphics.Rect import android.view.View import android.graphics.Canvas fun computeLayoutDiff( view: View, targetRegion: Rect, isLargeClock: Boolean, ): Pair<Float, Float> { val parent = view.parent if (parent is View && parent.isLaidOut() && isLargeClock) { return Pair( targetRegion.centerX() - parent.width / 2f, targetRegion.centerY() - parent.height / 2f ) object CanvasUtil { fun Canvas.translate(pt: VPointF) = this.translate(pt.x, pt.y) fun Canvas.translate(pt: VPoint) = this.translate(pt.x.toFloat(), pt.y.toFloat()) fun <T> Canvas.use(func: (Canvas) -> T): T { val saveNum = save() val result = func(this) restoreToCount(saveNum) return result } return Pair(0f, 0f) }
packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DigitTranslateAnimator.kt +18 −36 Original line number Diff line number Diff line Loading @@ -20,57 +20,42 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.TimeInterpolator import android.animation.ValueAnimator import android.graphics.Point import com.android.systemui.shared.clocks.VPointF.Companion.times class DigitTranslateAnimator(val updateCallback: () -> Unit) { val DEFAULT_ANIMATION_DURATION = 500L val updatedTranslate = Point(0, 0) class DigitTranslateAnimator(private val updateCallback: (VPointF) -> Unit) { var currentTranslation = VPointF.ZERO var baseTranslation = VPointF.ZERO var targetTranslation = VPointF.ZERO val baseTranslation = Point(0, 0) var targetTranslation: Point? = null val bounceAnimator: ValueAnimator = private val bounceAnimator: ValueAnimator = ValueAnimator.ofFloat(1f).apply { duration = DEFAULT_ANIMATION_DURATION addUpdateListener { updateTranslation(it.animatedFraction, updatedTranslate) updateCallback() } addUpdateListener { updateCallback(getInterpolatedTranslation(it.animatedFraction)) } addListener( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { rebase() baseTranslation = currentTranslation } override fun onAnimationCancel(animation: Animator) { rebase() baseTranslation = currentTranslation } } ) } fun rebase() { baseTranslation.x = updatedTranslate.x baseTranslation.y = updatedTranslate.y } fun animatePosition( animate: Boolean = true, delay: Long = 0, duration: Long = -1L, duration: Long, interpolator: TimeInterpolator? = null, targetTranslation: Point? = null, targetTranslation: VPointF, onAnimationEnd: Runnable? = null, ) { this.targetTranslation = targetTranslation ?: Point(0, 0) this.targetTranslation = targetTranslation if (animate) { bounceAnimator.cancel() bounceAnimator.startDelay = delay bounceAnimator.duration = if (duration == -1L) { DEFAULT_ANIMATION_DURATION } else { duration } bounceAnimator.duration = duration interpolator?.let { bounceAnimator.interpolator = it } if (onAnimationEnd != null) { val listener = Loading @@ -89,16 +74,13 @@ class DigitTranslateAnimator(val updateCallback: () -> Unit) { bounceAnimator.start() } else { // No animation is requested, thus set base and target state to the same state. updateTranslation(1F, updatedTranslate) rebase() updateCallback() currentTranslation = targetTranslation baseTranslation = targetTranslation updateCallback(targetTranslation) } } fun updateTranslation(progress: Float, outPoint: Point) { outPoint.x = (baseTranslation.x + progress * (targetTranslation!!.x - baseTranslation.x)).toInt() outPoint.y = (baseTranslation.y + progress * (targetTranslation!!.y - baseTranslation.y)).toInt() fun getInterpolatedTranslation(progress: Float): VPointF { return baseTranslation + progress * (targetTranslation - baseTranslation) } }
packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt +7 −5 Original line number Diff line number Diff line Loading @@ -35,12 +35,14 @@ import com.android.systemui.plugins.clocks.DefaultClockFaceLayout import com.android.systemui.plugins.clocks.ThemeConfig import com.android.systemui.plugins.clocks.WeatherData import com.android.systemui.plugins.clocks.ZenData import com.android.systemui.shared.clocks.ViewUtils.computeLayoutDiff import com.android.systemui.shared.clocks.view.FlexClockView import com.android.systemui.shared.clocks.view.HorizontalAlignment import com.android.systemui.shared.clocks.view.VerticalAlignment import java.util.Locale import java.util.TimeZone import kotlin.math.max import kotlin.math.roundToInt // TODO(b/364680879): Merge w/ ComposedDigitalLayerController class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: Boolean) : Loading Loading @@ -168,17 +170,17 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock: else targetRegion.height() / maxHeight FrameLayout.LayoutParams( (maxWidth * scale).toInt(), (maxHeight * scale).toInt(), (maxWidth * scale).roundToInt(), (maxHeight * scale).roundToInt(), ) } lp.gravity = Gravity.CENTER view.layoutParams = lp targetRegion?.let { val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock) view.translationX = xDiff view.translationY = yDiff val diff = view.computeLayoutDiff(it, isLargeClock) view.translationX = diff.x view.translationY = diff.y } } Loading
packages/SystemUI/customization/src/com/android/systemui/shared/clocks/VPoint.kt 0 → 100644 +224 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.shared.clocks import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF import kotlin.math.abs import kotlin.math.max import kotlin.math.min import kotlin.math.sqrt private val X_MASK: ULong = 0xFFFFFFFF00000000U private val Y_MASK: ULong = 0x00000000FFFFFFFFU private fun unpackX(data: ULong): Int = ((data and X_MASK) shr 32).toInt() private fun unpackY(data: ULong): Int = (data and Y_MASK).toInt() private fun pack(x: Int, y: Int): ULong { return ((x.toULong() shl 32) and X_MASK) or (y.toULong() and Y_MASK) } @JvmInline value class VPointF(private val data: ULong) { val x: Float get() = Float.fromBits(unpackX(data)) val y: Float get() = Float.fromBits(unpackY(data)) constructor() : this(0f, 0f) constructor(pt: PointF) : this(pt.x, pt.y) constructor(x: Int, y: Int) : this(x.toFloat(), y.toFloat()) constructor(x: Int, y: Float) : this(x.toFloat(), y) constructor(x: Float, y: Int) : this(x, y.toFloat()) constructor(x: Float, y: Float) : this(pack(x.toBits(), y.toBits())) fun toPointF() = PointF(x, y) fun lengthSq(): Float = x * x + y * y fun length(): Float = sqrt(lengthSq()) fun abs() = VPointF(abs(x), abs(y)) fun dot(pt: VPointF): Float = x * pt.x + y * pt.y fun normalize(): VPointF { val length = this.length() return VPointF(x / length, y / length) } operator fun component1(): Float = x operator fun component2(): Float = y override fun toString() = "($x, $y)" operator fun plus(pt: VPoint) = VPointF(x + pt.x, y + pt.y) operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y) operator fun plus(value: Int) = VPointF(x + value, y + value) operator fun plus(value: Float) = VPointF(x + value, y + value) operator fun minus(pt: VPoint) = VPointF(x - pt.x, y - pt.y) operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y) operator fun minus(value: Int) = VPointF(x - value, y - value) operator fun minus(value: Float) = VPointF(x - value, y - value) operator fun times(pt: VPoint) = VPointF(x * pt.x, y * pt.y) operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y) operator fun times(value: Int) = VPointF(x * value, y * value) operator fun times(value: Float) = VPointF(x * value, y * value) operator fun div(pt: VPoint) = VPointF(x / pt.x, y / pt.y) operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y) operator fun div(value: Int) = VPointF(x / value, y / value) operator fun div(value: Float) = VPointF(x / value, y / value) companion object { val ZERO = VPointF(0, 0) fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) operator fun Float.plus(value: VPointF) = VPointF(this + value.x, this + value.y) operator fun Int.minus(value: VPointF) = VPointF(this - value.x, this - value.y) operator fun Float.minus(value: VPointF) = VPointF(this - value.x, this - value.y) operator fun Int.times(value: VPointF) = VPointF(this * value.x, this * value.y) operator fun Float.times(value: VPointF) = VPointF(this * value.x, this * value.y) operator fun Int.div(value: VPointF) = VPointF(this / value.x, this / value.y) operator fun Float.div(value: VPointF) = VPointF(this / value.x, this / value.y) val RectF.center: VPointF get() = VPointF(centerX(), centerY()) val RectF.size: VPointF get() = VPointF(width(), height()) } } @JvmInline value class VPoint(private val data: ULong) { val x: Int get() = unpackX(data) val y: Int get() = unpackY(data) constructor() : this(0, 0) constructor(x: Int, y: Int) : this(pack(x, y)) fun toPoint() = Point(x, y) fun abs() = VPoint(abs(x), abs(y)) operator fun component1(): Int = x operator fun component2(): Int = y override fun toString() = "($x, $y)" operator fun plus(pt: VPoint) = VPoint(x + pt.x, y + pt.y) operator fun plus(pt: VPointF) = VPointF(x + pt.x, y + pt.y) operator fun plus(value: Int) = VPoint(x + value, y + value) operator fun plus(value: Float) = VPointF(x + value, y + value) operator fun minus(pt: VPoint) = VPoint(x - pt.x, y - pt.y) operator fun minus(pt: VPointF) = VPointF(x - pt.x, y - pt.y) operator fun minus(value: Int) = VPoint(x - value, y - value) operator fun minus(value: Float) = VPointF(x - value, y - value) operator fun times(pt: VPoint) = VPoint(x * pt.x, y * pt.y) operator fun times(pt: VPointF) = VPointF(x * pt.x, y * pt.y) operator fun times(value: Int) = VPoint(x * value, y * value) operator fun times(value: Float) = VPointF(x * value, y * value) operator fun div(pt: VPoint) = VPoint(x / pt.x, y / pt.y) operator fun div(pt: VPointF) = VPointF(x / pt.x, y / pt.y) operator fun div(value: Int) = VPoint(x / value, y / value) operator fun div(value: Float) = VPointF(x / value, y / value) companion object { val ZERO = VPoint(0, 0) fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) operator fun Int.plus(value: VPoint) = VPoint(this + value.x, this + value.y) operator fun Float.plus(value: VPoint) = VPointF(this + value.x, this + value.y) operator fun Int.minus(value: VPoint) = VPoint(this - value.x, this - value.y) operator fun Float.minus(value: VPoint) = VPointF(this - value.x, this - value.y) operator fun Int.times(value: VPoint) = VPoint(this * value.x, this * value.y) operator fun Float.times(value: VPoint) = VPointF(this * value.x, this * value.y) operator fun Int.div(value: VPoint) = VPoint(this / value.x, this / value.y) operator fun Float.div(value: VPoint) = VPointF(this / value.x, this / value.y) val Rect.center: VPoint get() = VPoint(centerX(), centerY()) val Rect.size: VPoint get() = VPoint(width(), height()) } }