Implement OSC snap to grid functionality

This commit is contained in:
lynxnb 2023-03-17 19:55:46 +01:00 committed by Niccolò Betto
parent 88084016a1
commit 88e6fc9888
9 changed files with 170 additions and 4 deletions

View File

@ -179,8 +179,40 @@ abstract class OnScreenButton(
* Moves this button to the given coordinates
*/
open fun move(x : Float, y : Float) {
relativeX = x / width
relativeY = (y - heightDiff) / adjustedHeight
var adjustedX = x
var adjustedY = y
if (editInfo.snapToGrid) {
val centerX = width / 2f
val centerY = height / 2f
val gridSize = editInfo.gridSize
// The coordinates of the first grid line for each axis, because the grid is centered and might not start at [0,0]
val startX = centerX % gridSize
val startY = centerY % gridSize
/**
* The offset to apply to a coordinate to snap it to the grid is the remainder of
* the coordinate divided by the grid size.
* Since the grid is centered on the screen and might not start at [0,0] we need to
* subtract the grid start offset, otherwise we'd be calculating the offset for a grid that starts at [0,0].
*
* Example: Touch event X: 158 | Grid size: 50 | Grid start X: 40 -> Grid lines at 40, 90, 140, 190, ...
* Snap offset: 158 - 40 = 118 -> 118 % 50 = 18
* Apply offset to X: 158 - 18 = 140 which is a grid line
*
* If we didn't subtract the grid start offset:
* Snap offset: 158 % 50 = 8
* Apply offset to X: 158 - 8 = 150 which is not a grid line
*/
val snapOffsetX = (x - startX) % gridSize
val snapOffsetY = (y - startY) % gridSize
adjustedX = x - snapOffsetX
adjustedY = y - snapOffsetY
}
relativeX = adjustedX / width
relativeY = (adjustedY - heightDiff) / adjustedHeight
}
/**

View File

@ -69,6 +69,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
lateinit var vibrator : Vibrator
private val effectClick = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
// Ensure controls init happens after editInfo is initialized so that the buttons have a valid reference to it
private val controls = Controls(this)
override fun onDraw(canvas : Canvas) {
@ -263,8 +264,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
fun setEditMode(editMode : EditMode) {
editInfo.editMode = editMode
setOnTouchListener(if (editMode == EditMode.None) playingTouchHandler else editingTouchHandler)
invalidate()
setOnTouchListener(if (isEditing) editingTouchHandler else playingTouchHandler )
}
fun resetControls() {
@ -286,6 +286,10 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
invalidate()
}
fun setSnapToGrid(snap : Boolean) {
editInfo.snapToGrid = snap
}
fun increaseOpacity() {
controls.alpha = (controls.alpha + ALPHA_STEP).coerceIn(ALPHA_RANGE)
invalidate()

View File

@ -12,6 +12,7 @@ import android.os.VibratorManager
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import dagger.hilt.android.AndroidEntryPoint
@ -108,12 +109,26 @@ class OnScreenEditActivity : AppCompatActivity() {
}
}
private val enableGridAction = {
appSettings.onScreenControlSnapToGrid = true
binding.onScreenControllerView.setSnapToGrid(true)
binding.alignmentGrid.isGone = false
}
private val disableGridAction = {
appSettings.onScreenControlSnapToGrid = false
binding.onScreenControllerView.setSnapToGrid(false)
binding.alignmentGrid.isGone = true
}
private val actions : List<Pair<Int, () -> Unit>> = listOf(
Pair(R.drawable.ic_palette, paletteAction),
Pair(R.drawable.ic_restore) { binding.onScreenControllerView.resetControls() },
Pair(R.drawable.ic_toggle, toggleAction),
Pair(R.drawable.ic_move, moveAction),
Pair(R.drawable.ic_resize, resizeAction),
Pair(R.drawable.ic_grid_on, enableGridAction),
Pair(R.drawable.ic_grid_off, disableGridAction),
Pair(R.drawable.ic_zoom_out) { binding.onScreenControllerView.decreaseScale() },
Pair(R.drawable.ic_zoom_in) { binding.onScreenControllerView.increaseScale() },
Pair(R.drawable.ic_opacity_minus) { binding.onScreenControllerView.decreaseOpacity() },
@ -147,6 +162,12 @@ class OnScreenEditActivity : AppCompatActivity() {
binding.onScreenControllerView.recenterSticks = appSettings.onScreenControlRecenterSticks
val snapToGrid = appSettings.onScreenControlSnapToGrid
binding.onScreenControllerView.setSnapToGrid(snapToGrid)
binding.alignmentGrid.isGone = !snapToGrid
binding.alignmentGrid.gridSize = OnScreenEditInfo.GridSize
actions.forEach { pair ->
binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply {
(this as FloatingActionButton).setImageDrawable(ContextCompat.getDrawable(context, pair.first))

View File

@ -5,6 +5,9 @@
package emu.skyline.input.onscreen
import android.util.TypedValue
import emu.skyline.SkylineApplication
enum class EditMode {
None,
Move,
@ -26,5 +29,20 @@ class OnScreenEditInfo {
*/
var editButton : OnScreenButton? = null
/**
* Whether the buttons should snap to a grid when in edit mode
*/
var snapToGrid : Boolean = false
var gridSize : Int = GridSize
val isEditing get() = editMode != EditMode.None
companion object {
/**
* The size of the grid, calculated from the value of 8dp
*/
val GridSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, SkylineApplication.context.resources.displayMetrics).toInt()
}
}

View File

@ -31,6 +31,7 @@ class AppSettings @Inject constructor(@ApplicationContext private val context :
var onScreenControl by sharedPreferences(context, true)
var onScreenControlFeedback by sharedPreferences(context, true)
var onScreenControlRecenterSticks by sharedPreferences(context, true)
var onScreenControlSnapToGrid by sharedPreferences(context, false)
// Other
var romFormatFilter by sharedPreferences(context, 0)

View File

@ -0,0 +1,65 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
/**
* A view that draws a grid, used for aligning on-screen controller buttons
*/
class AlignmentGridView @JvmOverloads constructor(context : Context, attrs : AttributeSet? = null, defStyleAttr : Int = 0) : View(context, attrs, defStyleAttr) {
var gridSize = 0
set(value) {
field = value
invalidate()
}
private val gridPaint = Paint().apply {
color = Color.WHITE
alpha = 135
}
private val gridCenterPaint = Paint().apply {
color = Color.WHITE
alpha = 55
strokeWidth = 10f
}
override fun onDraw(canvas : Canvas) {
super.onDraw(canvas)
drawGrid(canvas)
}
/**
* Draws a centered grid on the given canvas
*/
private fun drawGrid(canvas : Canvas) {
val centerX = width / 2f
val centerY = height / 2f
val gridSize = gridSize
// Compute the coordinates of the first grid line for each axis, because we want a centered grid, which might not start at [0,0]
val startX = centerX % gridSize
val startY = centerY % gridSize
// Draw the center lines with a thicker stroke
canvas.drawLine(centerX, 0f, centerX, height.toFloat(), gridCenterPaint)
canvas.drawLine(0f, centerY, width.toFloat(), centerY, gridCenterPaint)
// Draw the rest of the grid starting from the start coordinates
// Draw vertical lines
for (i in 0..width step gridSize) {
canvas.drawLine(startX + i, 0f, startX + i, height.toFloat(), gridPaint)
}
// Draw horizontal lines
for (i in 0..height step gridSize) {
canvas.drawLine(0f, startY + i, width.toFloat(), startY + i, gridPaint)
}
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M8,4v1.45l2,2L10,4h4v4h-3.45l2,2L14,10v1.45l2,2L16,10h4v4h-3.45l2,2L20,16v1.45l2,2L22,4c0,-1.1 -0.9,-2 -2,-2L4.55,2l2,2L8,4zM16,4h4v4h-4L16,4zM1.27,1.27L0,2.55l2,2L2,20c0,1.1 0.9,2 2,2h15.46l2,2 1.27,-1.27L1.27,1.27zM10,12.55L11.45,14L10,14v-1.45zM4,6.55L5.45,8L4,8L4,6.55zM8,20L4,20v-4h4v4zM8,14L4,14v-4h3.45l0.55,0.55L8,14zM14,20h-4v-4h3.45l0.55,0.54L14,20zM16,20v-1.46L17.46,20L16,20z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#000000"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM8,20L4,20v-4h4v4zM8,14L4,14v-4h4v4zM8,8L4,8L4,4h4v4zM14,20h-4v-4h4v4zM14,14h-4v-4h4v4zM14,8h-4L10,4h4v4zM20,20h-4v-4h4v4zM20,14h-4v-4h4v4zM20,8h-4L16,4h4v4z" />
</vector>

View File

@ -4,6 +4,11 @@
android:layout_height="match_parent"
android:background="@android:color/black">
<emu.skyline.views.AlignmentGridView
android:id="@+id/alignment_grid"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<emu.skyline.input.onscreen.OnScreenControllerView
android:id="@+id/on_screen_controller_view"
android:layout_width="match_parent"