/* * SPDX-License-Identifier: MPL-2.0 * Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) */ package emu.skyline.input.onscreen import android.annotation.SuppressLint import android.graphics.PointF import android.os.Build import android.os.Bundle import android.os.Vibrator import android.os.VibratorManager import android.util.TypedValue import android.view.* import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isGone import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import emu.skyline.R import emu.skyline.databinding.OnScreenEditActivityBinding import emu.skyline.databinding.OscSliderBinding import emu.skyline.settings.AppSettings import emu.skyline.utils.SwitchColors import emu.skyline.utils.SwitchColors.* import petrov.kristiyan.colorpicker.DoubleColorPicker import petrov.kristiyan.colorpicker.DoubleColorPicker.OnChooseDoubleColorListener import javax.inject.Inject import kotlin.math.roundToInt @AndroidEntryPoint class OnScreenEditActivity : AppCompatActivity() { private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) } @Inject lateinit var appSettings : AppSettings private fun paletteAction() { DoubleColorPicker(this@OnScreenEditActivity).apply { setTitle(this@OnScreenEditActivity.getString(R.string.osc_background_color)) setDefaultColorButton(binding.onScreenControllerView.getButtonBackgroundColor()) setRoundColorButton(true) setColors(*SwitchColors.colors.toIntArray()) setDefaultDoubleColorButton(binding.onScreenControllerView.getButtonTextColor()) setSecondTitle(this@OnScreenEditActivity.getString(R.string.osc_text_color)) setOnChooseDoubleColorListener(object : OnChooseDoubleColorListener { override fun onChooseColor(position : Int, color : Int, position2 : Int, color2 : Int) { binding.onScreenControllerView.setButtonBackgroundColor(SwitchColors.colors[position]) binding.onScreenControllerView.setButtonTextColor(SwitchColors.colors[position2]) } override fun onCancel() {} }) show() } } private fun toggleGridAction() { val snapToGrid = !appSettings.onScreenControlSnapToGrid appSettings.onScreenControlSnapToGrid = snapToGrid binding.onScreenControllerView.setSnapToGrid(snapToGrid) binding.alignmentGrid.isGone = !snapToGrid binding.gridButton.setIconResource(if (!snapToGrid) R.drawable.ic_grid_on else R.drawable.ic_grid_off) } private fun resetAction() { MaterialAlertDialogBuilder(this) .setTitle(getString(R.string.osc_reset, binding.onScreenControllerView.editButton.buttonId.short)) .setMessage(R.string.osc_reset_confirm) .setPositiveButton(R.string.confirm) { _, _ -> binding.onScreenControllerView.resetButton() } .setNegativeButton(R.string.cancel, null) .setOnDismissListener { fullScreen() } .show() } @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES setContentView(binding.root) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android might not allow child views to overlap the system bars // Override this behavior and force content to extend into the cutout area window.setDecorFitsSystemWindows(false) window.insetsController?.let { it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE it.hide(WindowInsets.Type.systemBars()) } } binding.onScreenControllerView.vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) (getSystemService(VIBRATOR_MANAGER_SERVICE) as VibratorManager).defaultVibrator else @Suppress("DEPRECATION") getSystemService(VIBRATOR_SERVICE) as Vibrator binding.onScreenControllerView.recenterSticks = appSettings.onScreenControlRecenterSticks val snapToGrid = appSettings.onScreenControlSnapToGrid binding.onScreenControllerView.setSnapToGrid(snapToGrid) binding.alignmentGrid.isGone = !snapToGrid binding.alignmentGrid.gridSize = OnScreenEditInfo.GridSize binding.onScreenControllerView.setOnEditButtonChangedListener { button -> updateActiveButtonDisplayInfo(button) } binding.selectAllButton.setOnClickListener { binding.onScreenControllerView.selectAllButtons() } populateSlider(binding.scaleSlider, getString(R.string.osc_scale)) { binding.onScreenControllerView.setButtonScale(it) } populateSlider(binding.opacitySlider, getString(R.string.osc_opacity)) { binding.onScreenControllerView.setButtonOpacity(it) } binding.enabledCheckbox.setOnClickListener { _ -> binding.onScreenControllerView.setButtonEnabled(binding.enabledCheckbox.isChecked) } binding.moveUpButton.setOnClickListener { binding.onScreenControllerView.moveButtonUp() } binding.moveDownButton.setOnClickListener { binding.onScreenControllerView.moveButtonDown() } binding.moveLeftButton.setOnClickListener { binding.onScreenControllerView.moveButtonLeft() } binding.moveRightButton.setOnClickListener { binding.onScreenControllerView.moveButtonRight() } binding.colorButton.setOnClickListener { paletteAction() } binding.gridButton.setOnClickListener { toggleGridAction() } binding.gridButton.setIconResource(if (!snapToGrid) R.drawable.ic_grid_on else R.drawable.ic_grid_off) binding.resetButton.setOnClickListener { resetAction() } binding.dragHandle.setOnTouchListener(dragPanelListener) binding.closeButton.setOnClickListener { togglePanelVisibility() } binding.onScreenControllerView.setEditMode(true) } override fun onResume() { super.onResume() updateActiveButtonDisplayInfo(binding.onScreenControllerView.editButton) fullScreen() } private fun fullScreen() { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { @Suppress("DEPRECATION") window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN) } } /** * Initializes the slider in the range [0,100], with the given label and value listener */ @SuppressLint("SetTextI18n") private fun populateSlider(slider : OscSliderBinding, label : String, valueListener : ((Int) -> Unit)? = null) { slider.title.text = label slider.slider.apply { valueFrom = 0f valueTo = 100f stepSize = 0f // Always update the value label addOnChangeListener { _, value, _ -> slider.valueLabel.text = "${value.roundToInt()}%" } // Only call the value listener if the user is dragging the slider addOnChangeListener { _, value, fromUser -> if (fromUser) valueListener?.invoke(value.roundToInt()) } } } /** * Updates the control panel UI elements to reflect the currently selected button */ private fun updateActiveButtonDisplayInfo(button : ConfigurableButton) { binding.enabledCheckbox.checkedState = button.config.groupEnabled binding.currentButton.text = getString(R.string.osc_current_button, button.buttonId.short) binding.scaleSlider.slider.value = (button.config.scale - OnScreenConfiguration.MinScale) / (OnScreenConfiguration.MaxScale - OnScreenConfiguration.MinScale) * 100f binding.opacitySlider.slider.value = (button.config.alpha - OnScreenConfiguration.MinAlpha) / (OnScreenConfiguration.MaxAlpha - OnScreenConfiguration.MinAlpha).toFloat() * 100f } @SuppressLint("ClickableViewAccessibility") private val dragPanelListener = { view : View, event : MotionEvent -> if (event.action == MotionEvent.ACTION_MOVE) { binding.controlPanel.x = event.rawX - binding.controlPanel.width / 2 binding.controlPanel.y = event.rawY - view.height / 2 } true } private var isPanelVisible = true private val openPanelTranslation = PointF() private fun togglePanelVisibility() { isPanelVisible = !isPanelVisible binding.content.isGone = !isPanelVisible binding.dragHandle.isGone = !isPanelVisible binding.closeButton.apply { if (isPanelVisible) { animate().rotation(0f).start() val buttonSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32f, resources.displayMetrics).toInt() // 32dp layoutParams.width = buttonSize layoutParams.height = buttonSize } else { animate().rotation(-225f).start() val buttonSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40f, resources.displayMetrics).toInt() // 40dp layoutParams.width = buttonSize layoutParams.height = buttonSize } } if (!isPanelVisible) { // Save the current open position to restore later openPanelTranslation.set(binding.controlPanel.translationX, binding.controlPanel.translationY) // Animate to the closed position binding.controlPanel.animate() .translationX(0f) .translationY(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12f, resources.displayMetrics)) // 12dp .start() } else { // Animate to the previously saved open position binding.controlPanel.animate() .translationX(openPanelTranslation.x) .translationY(openPanelTranslation.y) .start() } binding.onScreenControllerView.setEditMode(isPanelVisible) } }