All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.me.saket.telephoto.zoomable.ZoomableContentTransformation.kt Maven / Gradle / Ivy

The newest version!
package me.saket.telephoto.zoomable

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ScaleFactor
import me.saket.telephoto.zoomable.internal.Zero

/**
 * Graphics transformations generated by [Modifier.zoomable] when pan & zoom gestures are received.
 *
 * By default, these are automatically applied by `Modifier.zoomable()` to its content, but this
 * can be disabled using [ZoomableState.autoApplyTransformations] if you prefer applying them in
 * a bespoke manner.
 */
@Immutable
interface ZoomableContentTransformation {
  /**
   * Whether this object contains non-placeholder values. This will be false when
   * [Modifier.zoomable] is initializing and sufficient information to position
   * and show its content is not yet available.
   *
   * Until specified transformations are received, zoomable content should stay hidden.
   * This is done automatically if [ZoomableState.autoApplyTransformations] is `true`.
   */
  val isSpecified: Boolean

  /**
   * The size of the zoomable content that is currently known to [Modifier.zoomable].
   * This is calculated using the value given to [ZoomableState.setContentLocation] and
   * does not account for the zoom and pan transformations. Useful for synchronizing
   * other elements with the zoomable content.
   */
  val contentSize: Size

  /**
   * The scale of the zoomable content along the x and y axes, generated in response
   * to zoom gestures. This value represents the scaling factor that should be applied
   * to the content to achieve the desired zoom level.
   *
   * Values on both the axes can be less than 0 if the content size is larger than
   * its viewport size.
   */
  val scale: ScaleFactor

  val scaleMetadata: ScaleMetadata

  /**
   * The rotation of the zoomable content around the z-axis. This parameter is currently
   * unused and defaults to `0`, but may be used in the future if [Modifier.zoomable]
   * adds support for rotation gestures.
   */
  val rotationZ: Float

  /**
   * The translation of the zoomable content along the x and y axes, generated in
   * response to pan gestures. This value represents the amount by which the content
   * should be translated to achieve the desired pan position.
   */
  val offset: Offset

  /**
   * Offset percentage along the x and y axis for which [scale] and [offset] must be applied.
   */
  val transformOrigin: TransformOrigin get() = TransformOrigin.Zero

  /**
   * Central point around which the last pan/zoom gesture was made with respect to the
   * viewport bounds. Will be `null` if the content hasn't been interacted with yet.
   */
  val centroid: Offset?

  /** Inverse of [isSpecified]. */
  val isUnspecified: Boolean get() = !isSpecified

  interface ScaleMetadata {
    /**
     * The default scale needed to position the content within its layout bounds with
     * respect to [ZoomableState.contentScale].
     */
    val initialScale: ScaleFactor

    /**
     * Scale applied by the user using zoom gestures. The final scale of the content
     * is calculated using [initialScale] x [userZoom], and will always be lower or equal
     * to [ZoomSpec.maxZoomFactor].
     *
     * For example, a `userZoom` of `1.5f` indicates that the content is zoomed 150% from its
     * [initialScale].
     */
    val userZoom: Float
  }
}

@Stable
internal fun Modifier.applyTransformation(transformation: () -> ZoomableContentTransformation): Modifier {
  return graphicsLayer {
    @Suppress("NAME_SHADOWING")
    val transformation = transformation()
    scaleX = transformation.scale.scaleX
    scaleY = transformation.scale.scaleY
    rotationZ = transformation.rotationZ
    translationX = transformation.offset.x
    translationY = transformation.offset.y
    transformOrigin = transformation.transformOrigin
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy