mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 11:09:20 +01:00
220 lines
6.3 KiB
Scala
220 lines
6.3 KiB
Scala
package ocelot.desktop.ui.widget
|
|
|
|
import ocelot.desktop.ColorScheme
|
|
import ocelot.desktop.audio.{SoundSource, SoundSources}
|
|
import ocelot.desktop.color.Color
|
|
import ocelot.desktop.geometry.Size2D
|
|
import ocelot.desktop.graphics.Graphics
|
|
import ocelot.desktop.ui.event.handlers.{ClickHandler, HoverHandler}
|
|
import ocelot.desktop.ui.event.{HoverEvent, MouseEvent}
|
|
import ocelot.desktop.ui.widget.IconButton.Mode
|
|
import ocelot.desktop.ui.widget.tooltip.LabelTooltip
|
|
import ocelot.desktop.util.animation.{ColorAnimation, ValueAnimation}
|
|
import ocelot.desktop.util.{DrawUtils, Spritesheet}
|
|
|
|
class IconButton(
|
|
releasedIcon: String,
|
|
pressedIcon: String,
|
|
releasedColor: Color = Color.White,
|
|
pressedColor: Color = Color.White,
|
|
sizeMultiplier: Float = 1,
|
|
mode: Mode = Mode.Regular,
|
|
drawBackground: Boolean = false,
|
|
padding: Float = 0,
|
|
tooltip: Option[String] = None,
|
|
val model: IconButton.Model = IconButton.DefaultModel(false),
|
|
) extends Widget
|
|
with ClickHandler
|
|
with HoverHandler
|
|
with ClickSoundSource {
|
|
|
|
private val speed = 10f
|
|
|
|
override def receiveMouseEvents = true
|
|
|
|
private var prevModelValue = model.pressed
|
|
|
|
eventHandlers += {
|
|
case HoverEvent(HoverEvent.State.Enter) => onHoverEnter()
|
|
case HoverEvent(HoverEvent.State.Leave) => onHoverLeave()
|
|
|
|
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
|
|
mode match {
|
|
case Mode.Switch if model.pressed => handleRelease()
|
|
case Mode.Radio if model.pressed =>
|
|
case _ => handlePress()
|
|
}
|
|
|
|
clickSoundSource.play()
|
|
|
|
case MouseEvent(MouseEvent.State.Release, MouseEvent.Button.Left) =>
|
|
mode match {
|
|
case Mode.Regular => handleRelease()
|
|
case Mode.Switch =>
|
|
case Mode.Radio =>
|
|
}
|
|
}
|
|
|
|
def onPressed(): Unit = {}
|
|
|
|
def onReleased(): Unit = {}
|
|
|
|
private def onHoverEnter(): Unit = {
|
|
for (tooltip <- labelTooltip) {
|
|
root.get.tooltipPool.addTooltip(tooltip)
|
|
}
|
|
}
|
|
|
|
private def onHoverLeave(): Unit = {
|
|
for (tooltip <- labelTooltip) {
|
|
root.get.tooltipPool.closeTooltip(tooltip)
|
|
}
|
|
}
|
|
|
|
private def targetColor: Color = if (model.pressed) pressedColor else releasedColor
|
|
private def targetAlpha: Float = if (model.pressed) 1f else 0f
|
|
|
|
private val colorAnimation = new ColorAnimation(targetColor, speed)
|
|
private val alphaAnimation = new ValueAnimation(targetAlpha, speed)
|
|
|
|
private def handlePress(): Unit = {
|
|
model.pressed = true
|
|
onPressed()
|
|
}
|
|
|
|
private def handleRelease(): Unit = {
|
|
model.pressed = false
|
|
onReleased()
|
|
}
|
|
|
|
size = minimumSize
|
|
|
|
private def releasedIconSize: Size2D = Spritesheet.spriteSize(releasedIcon) * sizeMultiplier
|
|
|
|
private def pressedIconSize: Size2D = Spritesheet.spriteSize(pressedIcon) * sizeMultiplier
|
|
|
|
override def minimumSize: Size2D = releasedIconSize.max(pressedIconSize) + (padding * 2.0f)
|
|
override def maximumSize: Size2D = minimumSize
|
|
|
|
private val labelTooltip = tooltip.map(label => new LabelTooltip(label))
|
|
|
|
override def draw(g: Graphics): Unit = {
|
|
if (drawBackground) {
|
|
g.rect(bounds, ColorScheme("ButtonBackground"))
|
|
DrawUtils.ring(g, position.x, position.y, width, height, 2, ColorScheme("ButtonBorder"))
|
|
}
|
|
|
|
if (alphaAnimation.value < 1f) {
|
|
g.sprite(
|
|
releasedIcon,
|
|
position + (size - releasedIconSize) / 2f,
|
|
releasedIconSize,
|
|
releasedColor.toRGBANorm.mapA(_ * (1f - alphaAnimation.value)),
|
|
)
|
|
}
|
|
|
|
if (alphaAnimation.value > 0f) {
|
|
g.sprite(
|
|
pressedIcon,
|
|
position + (size - pressedIconSize) / 2f,
|
|
pressedIconSize,
|
|
pressedColor.toRGBANorm.mapA(_ * alphaAnimation.value),
|
|
)
|
|
}
|
|
}
|
|
|
|
override def update(): Unit = {
|
|
super.update()
|
|
|
|
val nextModelValue = model.pressed
|
|
|
|
if (prevModelValue != nextModelValue) {
|
|
prevModelValue = nextModelValue
|
|
colorAnimation.goto(targetColor)
|
|
alphaAnimation.goto(targetAlpha)
|
|
}
|
|
|
|
colorAnimation.update()
|
|
alphaAnimation.update()
|
|
}
|
|
|
|
override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClick
|
|
}
|
|
|
|
object IconButton {
|
|
sealed trait Mode
|
|
|
|
object Mode {
|
|
/**
|
|
* Your regular, run-of-the-mill button you love.
|
|
*
|
|
* When the LMB is depressed, [[IconButton.onPressed]] is called.
|
|
* After it's released, [[IconButton.onReleased]] is called.
|
|
*/
|
|
case object Regular extends Mode
|
|
|
|
/**
|
|
* A toggleable button, or a switch.
|
|
*
|
|
* Pressing the LMB while the button is pressed releases it, and [[IconButton.onReleased]] is called.
|
|
* Pressing the LMB while the button is released depresses it, and [[IconButton.onPressed]] is called.
|
|
*/
|
|
case object Switch extends Mode
|
|
|
|
/**
|
|
* A radio button is like a switch except clicking on it while it's depressed doesn't do anything.
|
|
*
|
|
* [[IconButton.onReleased]] is never called (unless you do it yourself).
|
|
*
|
|
* Managing a group of radio buttons is your responsibility.
|
|
*/
|
|
case object Radio extends Mode
|
|
}
|
|
|
|
/**
|
|
* This is the visible button state.
|
|
*
|
|
* When [[IconButton.update]] notices a state change, it runs its animations.
|
|
*
|
|
* Note that callbacks are run after running the setter regardless of whether it does anything.
|
|
*/
|
|
trait Model {
|
|
/**
|
|
* Whether to display the pressed button state.
|
|
*/
|
|
def pressed: Boolean
|
|
|
|
/**
|
|
* This setter is used by [[IconButton]] to respond to user interaction.
|
|
*
|
|
* You may want to override this method to do nothing
|
|
* if you're proxying another model and updating it in callbacks.
|
|
*/
|
|
def pressed_=(newValue: Boolean): Unit
|
|
}
|
|
|
|
/**
|
|
* This is the default model that simply reads from a variable and writes to it.
|
|
*/
|
|
case class DefaultModel(override var pressed: Boolean) extends Model
|
|
|
|
/**
|
|
* This is a model that ignores user input (presumably you handle it in callbacks).
|
|
*/
|
|
class ReadOnlyModel(f: () => Boolean) extends Model {
|
|
override def pressed: Boolean = f()
|
|
|
|
override def pressed_=(newValue: Boolean): Unit = {}
|
|
}
|
|
|
|
object ReadOnlyModel {
|
|
/**
|
|
* A utility method to create a new instance of [[ReadOnlyModel]].
|
|
*
|
|
* The value returned by [[ReadOnlyModel.pressed]] is obtained
|
|
* by evaluating the provided expression '''every time'''.
|
|
*/
|
|
def apply(value: => Boolean): ReadOnlyModel = new ReadOnlyModel(value _)
|
|
}
|
|
}
|