Make HoverEvent a CapturingEvent

This commit is contained in:
Fingercomp 2025-08-20 18:24:33 +03:00
parent 448089ccb9
commit 99af664d84
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
17 changed files with 57 additions and 81 deletions

View File

@ -103,7 +103,7 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
override def update(): Unit = {
super.update()
if (isHovered || isMoving) {
if (mouseOver || isMoving) {
root.get.statusBar.addMouseEntry(IconSource.Icons.RMB, "Menu")
root.get.statusBar.addMouseEntry(IconSource.Icons.DragLMB, "Move node")
@ -245,7 +245,7 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
private def stopMoving(): Unit = {
isMoving = false
if (isHovered)
if (mouseOver)
highlight.goto(HoveredHighlight)
else
highlight.goto(NoHighlight)

View File

@ -50,7 +50,7 @@ class NodeTypeWidget(val nodeType: NodeType) extends Widget with MouseHandler wi
override def update(): Unit = {
super.update()
if (isHovered) {
if (mouseOver) {
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Add node")
}
}

View File

@ -22,7 +22,7 @@ trait ShiftClickNode extends Node {
override def update(): Unit = {
super.update()
if (isHovered || isMoving)
if (mouseOver || isMoving)
root.get.statusBar.addKeyMouseEntry(IconSource.Icons.LMB, "SHIFT", hoveredShiftStatusBarText)
}
}

View File

@ -13,7 +13,7 @@ trait WindowedNode[T <: Window] extends Node with Windowed[T] {
}
override def update(): Unit = {
if (isHovered || isMoving)
if (mouseOver || isMoving)
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, if (windowCreated && window.isOpen) "Close" else "Open")
super.update()

View File

@ -26,7 +26,7 @@ abstract class NoteBlockNodeBase(entity: Entity with Environment) extends Entity
override def update(): Unit = {
super.update()
if (isHovered || isMoving) {
if (mouseOver || isMoving) {
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Play sample")
}
}

View File

@ -6,7 +6,7 @@ import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.ui.event.handlers.HoverHandler
import ocelot.desktop.ui.event.sources.{BrainEvents, KeyEvents, MouseEvents, ScrollEvents}
import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, MouseEvent}
import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, HoverEvent, MouseEvent}
import ocelot.desktop.ui.widget.{RootWidget, Widget}
import ocelot.desktop.util._
import ocelot.desktop.{OcelotDesktop, Settings}
@ -28,7 +28,6 @@ import scala.collection.mutable.ArrayBuffer
import scala.concurrent.duration.DurationInt
import scala.util.Try
object UiHandler extends Logging {
var root: RootWidget = _
var graphics: Graphics = _
@ -506,12 +505,16 @@ object UiHandler extends Logging {
}
hierarchy.reverseIterator.foreach {
case handler: HoverHandler if !mouseTarget.contains(handler) => handler.setHovered(false)
case h: HoverHandler if !mouseTarget.contains(h) && h._mouseOver.update(false) =>
dispatchCapturing(h)(HoverEvent(HoverEvent.State.Leave))
case _ =>
}
mouseTarget.foreach {
case handler: HoverHandler => handler.setHovered(true)
case h: HoverHandler if h._mouseOver.update(true) =>
dispatchCapturing(h)(HoverEvent(HoverEvent.State.Enter))
case _ =>
}

View File

@ -6,4 +6,4 @@ object HoverEvent {
}
}
case class HoverEvent(state: HoverEvent.State.Value) extends Event
case class HoverEvent(state: HoverEvent.State.Value) extends CapturingEvent

View File

@ -1,21 +1,11 @@
package ocelot.desktop.ui.event.handlers
import ocelot.desktop.ui.event.HoverEvent
import ocelot.desktop.ui.widget.Widget
import ocelot.desktop.util.Register
trait HoverHandler extends Widget {
private var wasHovered = false
private var _isHovered: Boolean = false
// must only be updated in UiHandler.
val _mouseOver: Register.Writeable[Boolean] = Register.apply(false)
def isHovered: Boolean = _isHovered
def setHovered(v: Boolean): Unit = {
_isHovered = v
if (_isHovered && !wasHovered)
handleEvent(HoverEvent(HoverEvent.State.Enter))
else if (!_isHovered && wasHovered)
handleEvent(HoverEvent(HoverEvent.State.Leave))
wasHovered = _isHovered
}
def mouseOver: Boolean = _mouseOver.value
}

View File

@ -114,8 +114,8 @@ class IconButton(
protected def pressedActiveColor: Color = pressedColor.toRGBANorm.mapRgb(_ * (1 - darkenActiveColorFactor))
protected def releasedActiveColor: Color = releasedColor.toRGBANorm.mapRgb(_ * (1 - darkenActiveColorFactor))
private def targetPressedColor: Color = if (isHovered) pressedActiveColor else pressedColor
private def targetReleasedColor: Color = if (isHovered) releasedActiveColor else releasedColor
private def targetPressedColor: Color = if (mouseOver) pressedActiveColor else pressedColor
private def targetReleasedColor: Color = if (mouseOver) releasedActiveColor else releasedColor
private def targetIconMix: Float = if (model.pressed) 1f else 0f

View File

@ -112,7 +112,7 @@ abstract class Plot extends Widget {
}
}
if (panel.isHovered) {
if (panel.mouseOver) {
g.foreground = ColorScheme("PlotLine")
val pos = UiHandler.mousePosition
val x = xAxis.inversePos(pos.x - pBounds.x, pBounds.w)

View File

@ -144,8 +144,8 @@ class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandle
val mousePos = UiHandler.mousePosition
val vAnimDir = if (isHovered && vThumbHoverArea.contains(mousePos) || dragState.isVertical) 1 else -1
val hAnimDir = if (isHovered && hThumbHoverArea.contains(mousePos) || dragState.isHorizontal) 1 else -1
val vAnimDir = if (mouseOver && vThumbHoverArea.contains(mousePos) || dragState.isVertical) 1 else -1
val hAnimDir = if (mouseOver && hThumbHoverArea.contains(mousePos) || dragState.isHorizontal) 1 else -1
vAnim = (vAnim + UiHandler.dt / 0.2f * vAnimDir).clamped()
hAnim = (hAnim + UiHandler.dt / 0.2f * hAnimDir).clamped()

View File

@ -149,7 +149,7 @@ abstract class Viewport3DWidget extends Widget with MouseHandler with HoverHandl
scene = createScene()
if (isHovered) {
if (mouseOver) {
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Rotate view")
root.get.statusBar.addKeyMouseEntry(IconSource.Icons.LMB, "SHIFT", "Pan view")
}

View File

@ -584,7 +584,7 @@ class WorkspaceView extends Widget with Persistable with MouseHandler with Hover
g.restore()
gridAlpha.update()
if (KeyEvents.isControlDown && (isHovered || nodes.exists(_.isHovered))) {
if (KeyEvents.isControlDown && (mouseOver || nodes.exists(_.mouseOver))) {
gridAlpha.goto(0.1f)
drawGrid(g)
} else {
@ -663,15 +663,15 @@ class WorkspaceView extends Widget with Persistable with MouseHandler with Hover
nodes.foreach(_.update())
particleSystem.update(UiHandler.dt)
if (isHovered) {
if (mouseOver) {
root.get.statusBar.addKeyEntry(Settings.get.keymap.name(Center), "Reset camera")
}
if (isHovered || nodes.exists(_.isHovered)) {
if (mouseOver || nodes.exists(_.mouseOver)) {
root.get.statusBar.addKeyEntry("CTRL", "Show grid")
}
if (isHovered && newConnection.isEmpty) {
if (mouseOver && newConnection.isEmpty) {
root.get.statusBar.addMouseEntry(
IconSource.Icons.LMB,
if (nodeSelector.isClosed) "Add node" else "Close selector",

View File

@ -23,7 +23,7 @@ class ContextMenuSubmenu(
super.update()
if (
!isClosing && !bounds.inflated(4).contains(UiHandler.mousePosition)
&& !parentEntry.isHovered
&& !parentEntry.mouseOver
) close()
}
}
@ -45,7 +45,7 @@ class ContextMenuSubmenu(
}
override def update(): Unit = {
if (!isHovered && submenu.isClosed) super.leave()
if (!mouseOver && submenu.isClosed) super.leave()
super.update()
}

View File

@ -5,7 +5,7 @@ import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.handlers.{HoverHandler, MouseHandler}
import ocelot.desktop.ui.event.{ClickEvent, HoverEvent, MouseEvent}
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
import ocelot.desktop.ui.layout.{AlignItems, LinearLayout}
import ocelot.desktop.ui.widget._
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
@ -55,7 +55,7 @@ class StatusBar extends Widget {
override def hThumbVisible: Boolean = false
}
private lazy val hoverBoxWithPauseButton: HoverBox = new HoverBox(new IconButton(
children :+= new HoverBox(new IconButton(
IconSource.Icons.Pause,
IconSource.Icons.Play,
mode = IconButton.Mode.Switch,
@ -64,23 +64,18 @@ class StatusBar extends Widget {
tooltip = Some("Pause emulation"),
model = new IconButton.ReadOnlyModel(() => OcelotDesktop.emulationPaused),
) {
eventHandlers += {
case HoverEvent(HoverEvent.State.Enter) =>
hoverBoxWithPauseButton.startHoverEnterAnimation()
case HoverEvent(HoverEvent.State.Leave) =>
hoverBoxWithPauseButton.startHoverLeaveAnimation()
}
override def onPressed(): Unit = {
OcelotDesktop.emulationPaused = true
labelTooltip.foreach(_.setText("Resume emulation"))
}
override def onReleased(): Unit = {
OcelotDesktop.emulationPaused = false
labelTooltip.foreach(_.setText("Pause emulation"))
}
}, Padding2D(left = 2, right = 2))
private lazy val hoverBoxWithTpsLabel: HoverBox = new HoverBox(new Label with MouseHandler with HoverHandler {
children :+= new HoverBox(new Label with MouseHandler with HoverHandler {
override protected def receiveClickEvents: Boolean = true
eventHandlers += {
@ -113,18 +108,12 @@ class StatusBar extends Widget {
}
root.get.contextMenus.open(menu, pos)
case HoverEvent(HoverEvent.State.Enter) =>
hoverBoxWithTpsLabel.startHoverEnterAnimation()
case HoverEvent(HoverEvent.State.Leave) =>
hoverBoxWithTpsLabel.startHoverLeaveAnimation()
}
override def update(): Unit = {
super.update()
if (isHovered) {
if (mouseOver) {
root.get.statusBar.addMouseEntry(IconSource.Icons.RMB, "Change simulation speed")
}
}
@ -136,9 +125,6 @@ class StatusBar extends Widget {
override def text: String = f"TPS: ${OcelotDesktop.tpsCounter.fps}%02.1f"
})
children :+= hoverBoxWithPauseButton
children :+= new PaddingBox(hoverBoxWithTpsLabel, Padding2D(left = 8))
def addMouseEntry(icon: IconSource, text: String): Unit = {
if (!keyMouseEntries.children.collect({ case e: MouseEntry => e.icon }).contains(icon)) {
keyMouseEntries.children :+= new MouseEntry(icon, text)
@ -165,17 +151,15 @@ class StatusBar extends Widget {
keyMouseEntries.children = ArraySeq.empty
}
private class HoverBox(widget: Widget, padding: Padding2D = Padding2D(left = 8, right = 8)) extends PaddingBox(widget, padding) with HoverAnimation {
private class HoverBox(widget: Widget, padding: Padding2D = Padding2D(left = 8, right = 8))
extends PaddingBox(widget, padding)
with HoverAnimation {
override def receiveMouseEvents: Boolean = true
override protected val hoverAnimationColorActive: Color = ColorScheme("StatusBarActive")
override protected val hoverAnimationColorDefault: Color = hoverAnimationColorActive.toRGBANorm.withAlpha(0)
// public re-exports
override def startHoverEnterAnimation(): Unit = super.startHoverEnterAnimation()
override def startHoverLeaveAnimation(): Unit = super.startHoverLeaveAnimation()
override def draw(g: Graphics): Unit = {
g.rect(bounds, hoverAnimation.color)
super.draw(g)

View File

@ -3,17 +3,18 @@ package ocelot.desktop.ui.widget.traits
import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color
import ocelot.desktop.ui.Const._
import ocelot.desktop.ui.event.{EventAware, HoverEvent}
import ocelot.desktop.ui.event.{Capturing, EventAware, HoverEvent}
import ocelot.desktop.ui.event.handlers.HoverHandler
import ocelot.desktop.ui.widget.{Updatable, Widget}
import ocelot.desktop.util.animation.ColorAnimation
/** Helper trait that manages color animations for UI widgets that should be highlighted on mouse hover. */
//noinspection ScalaWeakerAccess
/**
* Helper trait that manages color animations for UI widgets that should be highlighted on mouse hover.
*/
trait HoverAnimation extends Widget with EventAware with HoverHandler with Updatable {
//noinspection ScalaWeakerAccess
protected val hoverAnimationSpeedEnter: Float = AnimationSpeedHoverEnter
//noinspection ScalaWeakerAccess
protected val hoverAnimationSpeedLeave: Float = AnimationSpeedHoverLeave
protected val hoverAnimationColorDefault: Color = ColorScheme("ButtonBackground")
protected val hoverAnimationColorActive: Color = ColorScheme("ButtonBackgroundActive")
@ -22,21 +23,13 @@ trait HoverAnimation extends Widget with EventAware with HoverHandler with Updat
new ColorAnimation(hoverAnimationColorDefault, hoverAnimationSpeedEnter)
eventHandlers += {
case HoverEvent(HoverEvent.State.Enter) =>
startHoverEnterAnimation()
case Capturing(HoverEvent(HoverEvent.State.Enter)) =>
hoverAnimation.speed = hoverAnimationSpeedEnter
hoverAnimation.goto(hoverAnimationColorActive)
case HoverEvent(HoverEvent.State.Leave) =>
startHoverLeaveAnimation()
}
protected def startHoverEnterAnimation(): Unit = {
hoverAnimation.speed = hoverAnimationSpeedEnter
hoverAnimation.goto(hoverAnimationColorActive)
}
protected def startHoverLeaveAnimation(): Unit = {
hoverAnimation.speed = hoverAnimationSpeedLeave
hoverAnimation.goto(hoverAnimationColorDefault)
case Capturing(HoverEvent(HoverEvent.State.Leave)) =>
hoverAnimation.speed = hoverAnimationSpeedLeave
hoverAnimation.goto(hoverAnimationColorDefault)
}
override def update(): Unit = {

View File

@ -35,6 +35,12 @@ object Register {
changed
}
def update(nextValue: T): Boolean = {
this.nextValue = nextValue
update()
}
}
class Sampling[T](next: () => T) extends Register[T] {