Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade inspector with new capabilities and detached window mode. #86

Open
wants to merge 60 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
1fcdef0
Components: Replace direct calls to UResolution with ResolutionManage…
Sk1er Aug 11, 2022
7e8301a
Components: Replace direct calls to UMouse with MousePositionManager …
Sk1er Aug 11, 2022
02b582c
Components: Replace direct calls to UKeyboard with KeyboardManager to…
Sk1er Aug 11, 2022
012beb2
AspectConstraint: Use state to track value
Sk1er Aug 11, 2022
8e27b32
ChildBasedConstraint: Use state to track padding
Sk1er Aug 11, 2022
bb8f2ed
StateRegistry: Create state registry for providing additional informa…
Sk1er Aug 11, 2022
893c315
Debug: Allow setting of elementa debug at runtime without requiring t…
Sk1er Aug 12, 2022
3f64066
Inspector: Upgrade inspector with new capabilities and detached windo…
Sk1er Aug 12, 2022
0c240f3
Platform: Create `runOnMinecraftThread` function
Sk1er Aug 29, 2022
7a07184
Inspector: Move external display logic into main project
Sk1er Aug 29, 2022
5690908
Inspector: Delete InspectorManager and manage inspector window inside…
Sk1er Aug 29, 2022
310d21c
Window: Set currentWindow inside try block and unset in finally
Sk1er Sep 28, 2022
c0ed8bf
API: Temporarily remove deprecation on guiHint and roundToRealPixels
Sk1er Sep 28, 2022
5ec4812
ScissorEffect: Remove mutability of unroundedScissorBounds and rounde…
Sk1er Sep 28, 2022
01f154f
Extensions: Remove unnecessary this from *Manager utility properties
Sk1er Sep 28, 2022
a2af9c3
KeyboardManager: Remove overloads that do not use the argument
Sk1er Sep 28, 2022
b271326
KeyboardManager: Adjust code style of isCtrlKeyDown
Sk1er Sep 28, 2022
9f558ad
StateRegistry: Import inspector and cleanup comments
Sk1er Sep 28, 2022
992bb3c
StateRegistry: Migrate managedStates to property
Sk1er Sep 28, 2022
3251bfc
StateRegistry: Rename ManagedColorStateNullable to ManagedNullableCol…
Sk1er Sep 28, 2022
6e9ed17
StateRegistry: Rename managed state objects to avoid repetitive `Mana…
Sk1er Sep 28, 2022
4f140c7
StateRegistry: Remove InspectorDisplay interface and require a displa…
Sk1er Sep 28, 2022
e8b3232
StateRegistry: Rename OfObject to OfEnumerable for increased clarity
Sk1er Sep 28, 2022
e4f59a6
RainbowColorConstraint: Map constructor states to self to create muta…
Sk1er Sep 28, 2022
9b43b46
RelativeWindowConstraint: Map constructor states to self to create mu…
Sk1er Sep 28, 2022
162dca0
RoundingConstraint: Map constructor states to self to create mutable …
Sk1er Sep 28, 2022
b6230d8
ScaledTextConstraint: Map constructor states to self to create mutabl…
Sk1er Sep 28, 2022
c8e7628
SiblingConstraint: Map constructor states to self to create mutable copy
Sk1er Sep 29, 2022
1d3a98b
ColumnPositionConstraint: Add documentation and improve variable names
Sk1er Sep 29, 2022
ee8bb8d
ColumnPositionConstraint: Return no horizontal padding if the compone…
Sk1er Sep 29, 2022
6540bc6
ColumnPositionConstraint: DRY component.parent
Sk1er Sep 29, 2022
8aee863
ConstraintResolutionGui: Revert now unnecessary constructor changes
Sk1er Sep 29, 2022
3738cda
Inspector: Toggle hotkeys in inspector with F12
Sk1er Sep 29, 2022
3d82fc9
Inspector: Reset Elementa debug state in finally block
Sk1er Sep 29, 2022
8d13007
Inspector: Move ensureLastComponent call to animationFrame
Sk1er Sep 29, 2022
14c9285
Inspector: Rename WindowDrawObserver to WindowOverlay
Sk1er Sep 29, 2022
6b173f5
Inspector: Fix balanced before/after draw call if component is not mo…
Sk1er Sep 29, 2022
35dbd0e
Inspector: Toggle measuring distance with M instead of dedicated off key
Sk1er Sep 29, 2022
e08ca3a
Inspector: Round distance measurement rendering to real pixels
Sk1er Sep 29, 2022
cb0cf09
Inspector: DRY
Sk1er Sep 29, 2022
84b9714
Inspector: DRY distance drawing text
Sk1er Sep 29, 2022
69a7f19
Inspector: Cleanup distance measurement format
Sk1er Sep 29, 2022
900ef47
MappedTextInput: Rename to StateTextInput
Sk1er Sep 29, 2022
17b05ff
StateTextInput: Clone from Essential
Sk1er Sep 29, 2022
cc0d78d
StateRegistry: Update use of StateTextInput
Sk1er Sep 29, 2022
6bb1042
FrameBufferedWindow: Import platform instead of declaring as property
Sk1er Sep 29, 2022
74273d4
FrameBufferedWindow: Rename parameter for increased clarity
Sk1er Sep 29, 2022
c327dd2
FrameBufferedWindow: Rename renderDirect to renderFrameBufferTexture
Sk1er Sep 29, 2022
da79f0e
FrameBufferedWindow: Simplify render method
Sk1er Sep 29, 2022
ee2d861
FrameBufferedWindow: DRY
Sk1er Sep 29, 2022
a86df75
StateRegistry: Handle parsing of null color case
Sk1er Sep 29, 2022
65dea1d
StateRegistry: DRY
Sk1er Sep 29, 2022
dc71b4c
StateRegistry: Display and allow editing of oppacity byte of color
Sk1er Sep 29, 2022
9f18e2b
Extensions: Remove State<String>.empty
Sk1er Sep 29, 2022
420031b
Extensions: Use mousePositionManger in hoveredState
Sk1er Sep 29, 2022
84482b0
Extensions: Add isInComponentTree
Sk1er Sep 29, 2022
c499f13
Extensions: Check if component is in component tree before performing…
Sk1er Sep 29, 2022
92af06d
AwtMousePositionManager: Update mouse position from events to avoid q…
Sk1er Sep 29, 2022
7ed0e6e
Build: Update ABI
Sk1er Sep 29, 2022
6706faf
Merge branch 'master' into feature/inspector_improvements
Sk1er Oct 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/main/kotlin/gg/essential/elementa/UIComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import gg.essential.elementa.utils.requireMainThread
import gg.essential.elementa.utils.requireState
import gg.essential.universal.UMatrixStack
import gg.essential.universal.UMouse
import gg.essential.universal.UResolution
import org.lwjgl.opengl.GL11
import java.awt.Color
import java.util.*
Expand Down Expand Up @@ -388,8 +387,9 @@ abstract class UIComponent : Observable() {

internal fun pixelCoordinatesToPixelCenter(mouseX: Double, mouseY: Double): Pair<Double, Double> {
// Move the position of a click to the center of a pixel. See [ElementaVersion.v2] for more info
return if ((Window.ofOrNull(this)?.version ?: ElementaVersion.v0) >= ElementaVersion.v2) {
val halfPixel = 0.5 / UResolution.scaleFactor
val window = Window.ofOrNull(this)
return if (window !=null && window.version >= ElementaVersion.v2) {
val halfPixel = 0.5 / window.resolutionManager.scaleFactor
mouseX + halfPixel to mouseY + halfPixel
} else {
mouseX to mouseY
Expand Down Expand Up @@ -1237,8 +1237,9 @@ abstract class UIComponent : Observable() {
/**
* Hints a number with respect to the current GUI scale.
*/
@Deprecated("This relies on global states", replaceWith = ReplaceWith("guiHint(number, roundDown, component)"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've said this before but I guess I'll have to repeat myself:
I don't think we should at this point deprecate this method (as well as the roundToRealPixel ones) because Kotlin's context receiver feature may make the new methods way more ergonomic to use than they currently are, and therefore I don't think we should at this point lock them into the API (and we don't need to anyway because atm the whole resolution manager thing is only used by the inspector, not by any API user).
Don't delete the line though, just comment it out. We will probably need it at some point, and this way it already serves as a hint for internal usages.

fun guiHint(number: Float, roundDown: Boolean): Float {
val factor = UResolution.scaleFactor.toFloat()
val factor = Window.resolutionManager.scaleFactor.toFloat()
return (number * factor).let {
if (roundDown) floor(it) else ceil(it)
} / factor
Expand All @@ -1247,13 +1248,35 @@ abstract class UIComponent : Observable() {
/**
* Hints a number with respect to the current GUI scale.
*/
fun guiHint(number: Float, roundDown: Boolean, component: UIComponent): Float {
val factor = component.resolutionManager.scaleFactor.toFloat()
return (number * factor).let {
if (roundDown) floor(it) else ceil(it)
} / factor
}

/**
* Hints a number with respect to the current GUI scale.
*/
@Deprecated("This relies on global states", replaceWith = ReplaceWith("guiHint(number, roundDown, component)"))
fun guiHint(number: Double, roundDown: Boolean): Double {
val factor = UResolution.scaleFactor
val factor = Window.resolutionManager.scaleFactor
return (number * factor).let {
if (roundDown) floor(it) else ceil(it)
} / factor
}

/**
* Hints a number with respect to the current GUI scale.
*/
fun guiHint(number: Double, roundDown: Boolean, component: UIComponent): Double {
val factor = component.resolutionManager.scaleFactor
return (number * factor).let {
if (roundDown) floor(it) else ceil(it)
} / factor
}


internal fun getMouseX(): Float {
return UMouse.Scaled.x.toFloat()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class VanillaFontRenderer : FontProvider {
override fun getStringHeight(string: String, pointSize: Float): Float =
UGraphics.getFontHeight().toFloat()

@Suppress("DEPRECATION")
override fun drawString(
matrixStack: UMatrixStack,
string: String,
Expand Down
29 changes: 25 additions & 4 deletions src/main/kotlin/gg/essential/elementa/components/Window.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import gg.essential.elementa.constraints.resolution.ConstraintResolverV2
import gg.essential.elementa.effects.ScissorEffect
import gg.essential.elementa.font.FontRenderer
import gg.essential.elementa.impl.Platform.Companion.platform
import gg.essential.elementa.manager.DefaultResolutionManager
import gg.essential.elementa.manager.ResolutionManager
import gg.essential.elementa.utils.elementaDev
import gg.essential.elementa.utils.requireMainThread
import gg.essential.universal.*
Expand Down Expand Up @@ -37,6 +39,11 @@ class Window @JvmOverloads constructor(

internal var clickInterceptor: ((mouseX: Double, mouseY: Double, button: Int) -> Boolean)? = null

/**
* State managers to avoid global states
*/
internal var resolutionManager: ResolutionManager = DefaultResolutionManager

@Deprecated("Add ElementaVersion as the first argument to opt-in to improved behavior.")
@JvmOverloads
constructor(animationFPS: Int = 244) : this(ElementaVersion.v0, animationFPS)
Expand All @@ -59,6 +66,7 @@ class Window @JvmOverloads constructor(
private fun doDraw(matrixStack: UMatrixStack) {
if (cancelDrawing)
return
currentWindow.set(this)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be at the start of the try block below, otherwise it won't be un-set if an exception occurs before that block (e.g. requireMainThread, or one of the renderOperations).
It's also semantically wrong to have this set while renderOperations are being drained because renderOperations is global and not specific to this window.


requireMainThread()

Expand Down Expand Up @@ -137,6 +145,7 @@ class Window @JvmOverloads constructor(
}
}
}
currentWindow.set(null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be in a finally so it is always unset, even when an exception occurs in the catch block.

}

internal fun drawEmbedded(matrixStack: UMatrixStack) {
Expand Down Expand Up @@ -269,11 +278,11 @@ class Window @JvmOverloads constructor(
}

override fun getWidth(): Float {
return UResolution.scaledWidth.toFloat()
return resolutionManager.scaledWidth.toFloat()
}

override fun getHeight(): Float {
return UResolution.scaledHeight.toFloat()
return resolutionManager.scaledHeight.toFloat()
}

override fun getRight() = getWidth()
Expand All @@ -287,12 +296,12 @@ class Window @JvmOverloads constructor(
) return false

val currentScissor = ScissorEffect.currentScissorState ?: return true
val sf = UResolution.scaleFactor
val sf = resolutionManager.scaleFactor

val realX = currentScissor.x / sf
val realWidth = currentScissor.width / sf

val bottomY = ((UResolution.scaledHeight * sf) - currentScissor.y) / sf
val bottomY = ((resolutionManager.scaledHeight * sf) - currentScissor.y) / sf
val realHeight = currentScissor.height / sf

return right > realX &&
Expand Down Expand Up @@ -368,6 +377,18 @@ class Window @JvmOverloads constructor(
companion object {
private val renderOperations = ConcurrentLinkedQueue<() -> Unit>()

/**
* Instance of the Window currently being rendered
*/
internal val currentWindow: ThreadLocal<Window?> = ThreadLocal.withInitial { null }

/**
* Resolution manager of the window currently being rendered or [DefaultResolutionManager]
* if one cannot be resolved.
*/
internal val resolutionManager: ResolutionManager
get() = currentWindow.get()?.resolutionManager ?: DefaultResolutionManager

fun enqueueRenderOperation(operation: Runnable) {
renderOperations.add {
operation.run()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ class CenterConstraint : PositionConstraint {
val parent = constrainTo ?: component.parent

return if (component.isPositionCenter()) {
parent.getLeft() + (parent.getWidth() / 2).roundToRealPixels()
parent.getLeft() + (parent.getWidth() / 2).roundToRealPixels(component)
} else {
parent.getLeft() + (parent.getWidth() / 2 - component.getWidth() / 2).roundToRealPixels()
parent.getLeft() + (parent.getWidth() / 2 - component.getWidth() / 2).roundToRealPixels(component)
}
}

override fun getYPositionImpl(component: UIComponent): Float {
val parent = constrainTo ?: component.parent

return if (component.isPositionCenter()) {
parent.getTop() + (parent.getHeight() / 2).roundToRealPixels()
parent.getTop() + (parent.getHeight() / 2).roundToRealPixels(component)
} else {
parent.getTop() + (parent.getHeight() / 2 - component.getHeight() / 2).roundToRealPixels()
parent.getTop() + (parent.getHeight() / 2 - component.getHeight() / 2).roundToRealPixels(component)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ interface XConstraint : SuperConstraint<Float> {
}

if (recalculate) {
cachedValue = getXPositionImpl(component).roundToRealPixels()
cachedValue = getXPositionImpl(component).roundToRealPixels(component)
recalculate = false
}

Expand All @@ -97,7 +97,7 @@ interface YConstraint : SuperConstraint<Float> {
}

if (recalculate) {
cachedValue = getYPositionImpl(component).roundToRealPixels()
cachedValue = getYPositionImpl(component).roundToRealPixels(component)
recalculate = false
}

Expand Down Expand Up @@ -135,7 +135,7 @@ interface WidthConstraint : SuperConstraint<Float> {
}

if (recalculate) {
cachedValue = getWidthImpl(component).roundToRealPixels()
cachedValue = getWidthImpl(component).roundToRealPixels(component)
recalculate = false
}

Expand All @@ -153,7 +153,7 @@ interface HeightConstraint : SuperConstraint<Float> {
}

if (recalculate) {
cachedValue = getHeightImpl(component).roundToRealPixels()
cachedValue = getHeightImpl(component).roundToRealPixels(component)
recalculate = false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ internal interface ConstraintDebugger {

fun invokeImpl(constraint: SuperConstraint<Float>, type: ConstraintType, component: UIComponent): Float =
when (type) {
ConstraintType.X -> (constraint as XConstraint).getXPositionImpl(component).roundToRealPixels()
ConstraintType.Y -> (constraint as YConstraint).getYPositionImpl(component).roundToRealPixels()
ConstraintType.WIDTH -> (constraint as WidthConstraint).getWidthImpl(component).roundToRealPixels()
ConstraintType.HEIGHT -> (constraint as HeightConstraint).getHeightImpl(component).roundToRealPixels()
ConstraintType.X -> (constraint as XConstraint).getXPositionImpl(component).roundToRealPixels(component)
ConstraintType.Y -> (constraint as YConstraint).getYPositionImpl(component).roundToRealPixels(component)
ConstraintType.WIDTH -> (constraint as WidthConstraint).getWidthImpl(component).roundToRealPixels(component)
ConstraintType.HEIGHT -> (constraint as HeightConstraint).getHeightImpl(component).roundToRealPixels(component)
ConstraintType.RADIUS -> (constraint as RadiusConstraint).getRadiusImpl(component)
else -> throw UnsupportedOperationException()
}
Expand Down
40 changes: 26 additions & 14 deletions src/main/kotlin/gg/essential/elementa/effects/ScissorEffect.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package gg.essential.elementa.effects

import gg.essential.elementa.UIComponent
import gg.essential.elementa.utils.resolutionManager
import gg.essential.elementa.utils.roundToRealPixels
import gg.essential.universal.UMatrixStack
import gg.essential.universal.UResolution
import org.lwjgl.opengl.GL11.*
import kotlin.math.max
import kotlin.math.min
Expand All @@ -22,7 +22,8 @@ class ScissorEffect @JvmOverloads constructor(
private val scissorIntersection: Boolean = true
) : Effect() {
private var oldState: ScissorState? = null
private var scissorBounds: ScissorBounds? = null
private var unroundedScissorBounds: ScissorBounds? = null
private var roundedScissorBounds: ScissorBounds? = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these need to be mutable and you don't need to manually handle initialization state tracking.
To get rid of the mutability of the first field, simply make the primary constructor private and make the field an argument there (and then create a new secondary constructor matching the current primary one for backwards compatibility).
To get rid of the mutability of the second field, simply initialize it via lazy.


/**
* Create a custom bounding box using precise coordinates.
Expand All @@ -35,17 +36,28 @@ class ScissorEffect @JvmOverloads constructor(
y2: Number,
scissorIntersection: Boolean = true
) : this(scissorIntersection = scissorIntersection) {
scissorBounds = ScissorBounds(
x1.toFloat().roundToRealPixels(),
y1.toFloat().roundToRealPixels(),
x2.toFloat().roundToRealPixels(),
y2.toFloat().roundToRealPixels(),
unroundedScissorBounds = ScissorBounds(
x1.toFloat(),
y1.toFloat(),
x2.toFloat(),
y2.toFloat(),
)
}

override fun beforeDraw(matrixStack: UMatrixStack) {
val bounds = customBoundingBox?.getScissorBounds() ?: scissorBounds ?: boundComponent.getScissorBounds()
val scaleFactor = UResolution.scaleFactor.toInt()
val unroundedScissorBounds = roundedScissorBounds
if (unroundedScissorBounds !=null) {
roundedScissorBounds = ScissorBounds(
unroundedScissorBounds.x1.roundToRealPixels(boundComponent),
unroundedScissorBounds.y1.roundToRealPixels(boundComponent),
unroundedScissorBounds.x2.roundToRealPixels(boundComponent),
unroundedScissorBounds.y2.roundToRealPixels(boundComponent),
)
this.unroundedScissorBounds = null
}
val bounds = customBoundingBox?.getScissorBounds() ?: roundedScissorBounds ?: boundComponent.getScissorBounds()
val resolutionManager = boundComponent.resolutionManager
val scaleFactor = resolutionManager.scaleFactor.toInt()

if (currentScissorState == null) {
glEnable(GL_SCISSOR_TEST)
Expand All @@ -57,7 +69,7 @@ class ScissorEffect @JvmOverloads constructor(
// TODO ideally we should respect matrixStack offset and maybe scale, though we do not currently care about
// global gl state either, so not really important until someone needs it
var x = (bounds.x1 * scaleFactor).roundToInt()
var y = UResolution.viewportHeight - (bounds.y2 * scaleFactor).roundToInt()
var y = resolutionManager.viewportHeight - (bounds.y2 * scaleFactor).roundToInt()
var width = (bounds.width * scaleFactor).roundToInt()
var height = (bounds.height * scaleFactor).roundToInt()

Expand Down Expand Up @@ -96,10 +108,10 @@ class ScissorEffect @JvmOverloads constructor(
}

private fun UIComponent.getScissorBounds(): ScissorBounds = ScissorBounds(
getLeft().roundToRealPixels(),
getTop().roundToRealPixels(),
getRight().roundToRealPixels(),
getBottom().roundToRealPixels(),
getLeft().roundToRealPixels(this),
getTop().roundToRealPixels(this),
getRight().roundToRealPixels(this),
getBottom().roundToRealPixels(this),
)

data class ScissorState(val x: Int, val y: Int, val width: Int, val height: Int)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/gg/essential/elementa/font/FontRenderer.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package gg.essential.elementa.font

import gg.essential.elementa.UIComponent
import gg.essential.elementa.components.Window
import gg.essential.elementa.constraints.ConstraintType
import gg.essential.elementa.constraints.resolution.ConstraintVisitor
import gg.essential.elementa.font.data.Font
import gg.essential.elementa.font.data.Glyph
import gg.essential.elementa.utils.readFromLegacyShader
import gg.essential.universal.UGraphics
import gg.essential.universal.UMatrixStack
import gg.essential.universal.UResolution
import gg.essential.universal.shader.BlendState
import gg.essential.universal.shader.Float2Uniform
import gg.essential.universal.shader.Float4Uniform
Expand Down Expand Up @@ -194,7 +194,7 @@ class FontRenderer(
doffsetUniform.setValue(3.5f / currentPointSize)


val guiScale = UResolution.scaleFactor.toFloat()
val guiScale = Window.resolutionManager.scaleFactor.toFloat()

//Reset
obfuscated = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package gg.essential.elementa.manager

import gg.essential.universal.UResolution

/**
* A resolution manager that provides its values from [UResolution].
*/
internal object DefaultResolutionManager : ResolutionManager {

override val windowWidth: Int
get() = UResolution.windowWidth

override val windowHeight: Int
get() = UResolution.windowHeight

override val viewportWidth: Int
get() = UResolution.viewportWidth

override val viewportHeight: Int
get() = UResolution.viewportHeight

override val scaledWidth: Int
get() = UResolution.scaledWidth

override val scaledHeight: Int
get() = UResolution.scaledHeight

override val scaleFactor: Double
get() = UResolution.scaleFactor
}
22 changes: 22 additions & 0 deletions src/main/kotlin/gg/essential/elementa/manager/ResolutionManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gg.essential.elementa.manager


/**
* Provides a non-global way to access different aspects about the current resolution
*/
internal interface ResolutionManager {

val windowWidth: Int

val windowHeight: Int

val viewportWidth: Int

val viewportHeight: Int

val scaledWidth: Int

val scaledHeight: Int

val scaleFactor: Double
}
Loading