mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-27 16:55:29 +03:00
Implement OSC snap to grid functionality
This commit is contained in:
parent
88084016a1
commit
88e6fc9888
@ -179,8 +179,40 @@ abstract class OnScreenButton(
|
|||||||
* Moves this button to the given coordinates
|
* Moves this button to the given coordinates
|
||||||
*/
|
*/
|
||||||
open fun move(x : Float, y : Float) {
|
open fun move(x : Float, y : Float) {
|
||||||
relativeX = x / width
|
var adjustedX = x
|
||||||
relativeY = (y - heightDiff) / adjustedHeight
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,6 +69,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
lateinit var vibrator : Vibrator
|
lateinit var vibrator : Vibrator
|
||||||
private val effectClick = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
|
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)
|
private val controls = Controls(this)
|
||||||
|
|
||||||
override fun onDraw(canvas : Canvas) {
|
override fun onDraw(canvas : Canvas) {
|
||||||
@ -263,8 +264,7 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
|
|
||||||
fun setEditMode(editMode : EditMode) {
|
fun setEditMode(editMode : EditMode) {
|
||||||
editInfo.editMode = editMode
|
editInfo.editMode = editMode
|
||||||
setOnTouchListener(if (editMode == EditMode.None) playingTouchHandler else editingTouchHandler)
|
setOnTouchListener(if (isEditing) editingTouchHandler else playingTouchHandler )
|
||||||
invalidate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetControls() {
|
fun resetControls() {
|
||||||
@ -286,6 +286,10 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSnapToGrid(snap : Boolean) {
|
||||||
|
editInfo.snapToGrid = snap
|
||||||
|
}
|
||||||
|
|
||||||
fun increaseOpacity() {
|
fun increaseOpacity() {
|
||||||
controls.alpha = (controls.alpha + ALPHA_STEP).coerceIn(ALPHA_RANGE)
|
controls.alpha = (controls.alpha + ALPHA_STEP).coerceIn(ALPHA_RANGE)
|
||||||
invalidate()
|
invalidate()
|
||||||
|
@ -12,6 +12,7 @@ import android.os.VibratorManager
|
|||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isGone
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
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(
|
private val actions : List<Pair<Int, () -> Unit>> = listOf(
|
||||||
Pair(R.drawable.ic_palette, paletteAction),
|
Pair(R.drawable.ic_palette, paletteAction),
|
||||||
Pair(R.drawable.ic_restore) { binding.onScreenControllerView.resetControls() },
|
Pair(R.drawable.ic_restore) { binding.onScreenControllerView.resetControls() },
|
||||||
Pair(R.drawable.ic_toggle, toggleAction),
|
Pair(R.drawable.ic_toggle, toggleAction),
|
||||||
Pair(R.drawable.ic_move, moveAction),
|
Pair(R.drawable.ic_move, moveAction),
|
||||||
Pair(R.drawable.ic_resize, resizeAction),
|
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_out) { binding.onScreenControllerView.decreaseScale() },
|
||||||
Pair(R.drawable.ic_zoom_in) { binding.onScreenControllerView.increaseScale() },
|
Pair(R.drawable.ic_zoom_in) { binding.onScreenControllerView.increaseScale() },
|
||||||
Pair(R.drawable.ic_opacity_minus) { binding.onScreenControllerView.decreaseOpacity() },
|
Pair(R.drawable.ic_opacity_minus) { binding.onScreenControllerView.decreaseOpacity() },
|
||||||
@ -147,6 +162,12 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
binding.onScreenControllerView.recenterSticks = appSettings.onScreenControlRecenterSticks
|
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 ->
|
actions.forEach { pair ->
|
||||||
binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply {
|
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))
|
(this as FloatingActionButton).setImageDrawable(ContextCompat.getDrawable(context, pair.first))
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
package emu.skyline.input.onscreen
|
package emu.skyline.input.onscreen
|
||||||
|
|
||||||
|
import android.util.TypedValue
|
||||||
|
import emu.skyline.SkylineApplication
|
||||||
|
|
||||||
enum class EditMode {
|
enum class EditMode {
|
||||||
None,
|
None,
|
||||||
Move,
|
Move,
|
||||||
@ -26,5 +29,20 @@ class OnScreenEditInfo {
|
|||||||
*/
|
*/
|
||||||
var editButton : OnScreenButton? = null
|
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
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ class AppSettings @Inject constructor(@ApplicationContext private val context :
|
|||||||
var onScreenControl by sharedPreferences(context, true)
|
var onScreenControl by sharedPreferences(context, true)
|
||||||
var onScreenControlFeedback by sharedPreferences(context, true)
|
var onScreenControlFeedback by sharedPreferences(context, true)
|
||||||
var onScreenControlRecenterSticks by sharedPreferences(context, true)
|
var onScreenControlRecenterSticks by sharedPreferences(context, true)
|
||||||
|
var onScreenControlSnapToGrid by sharedPreferences(context, false)
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
var romFormatFilter by sharedPreferences(context, 0)
|
var romFormatFilter by sharedPreferences(context, 0)
|
||||||
|
65
app/src/main/java/emu/skyline/views/AlignmentGridView.kt
Normal file
65
app/src/main/java/emu/skyline/views/AlignmentGridView.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/res/drawable/ic_grid_off.xml
Normal file
10
app/src/main/res/drawable/ic_grid_off.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_grid_on.xml
Normal file
10
app/src/main/res/drawable/ic_grid_on.xml
Normal 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>
|
@ -4,6 +4,11 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@android:color/black">
|
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
|
<emu.skyline.input.onscreen.OnScreenControllerView
|
||||||
android:id="@+id/on_screen_controller_view"
|
android:id="@+id/on_screen_controller_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
Loading…
Reference in New Issue
Block a user