diff --git a/src/main/scala/ocelot/desktop/ui/event/ClickEvent.scala b/src/main/scala/ocelot/desktop/ui/event/ClickEvent.scala index 12809e3..38dbab0 100644 --- a/src/main/scala/ocelot/desktop/ui/event/ClickEvent.scala +++ b/src/main/scala/ocelot/desktop/ui/event/ClickEvent.scala @@ -2,4 +2,18 @@ package ocelot.desktop.ui.event import ocelot.desktop.geometry.Vector2D +/** + * A synthetic event dispatched by [[ocelot.desktop.ui.event.handlers.MouseHandler MouseHandler]] on mouse click. + */ case class ClickEvent(button: MouseEvent.Button.Value, mousePos: Vector2D) extends Event + +/** + * A synthetic event dispatched by [[ocelot.desktop.ui.event.handlers.MouseHandler MouseHandler]] on double-click + * in addition to (and after) [[ClickEvent]]. + */ +case class DoubleClickEvent(button: MouseEvent.Button.Value, mousePos: Vector2D) extends Event +/** + * A synthetic event dispatched by [[ocelot.desktop.ui.event.handlers.MouseHandler MouseHandler]] on triple-click + * in addition to (and after) [[ClickEvent]]. + */ +case class TripleClickEvent(button: MouseEvent.Button.Value, mousePos: Vector2D) extends Event diff --git a/src/main/scala/ocelot/desktop/ui/event/DoubleClickEvent.scala b/src/main/scala/ocelot/desktop/ui/event/DoubleClickEvent.scala deleted file mode 100644 index de2c60b..0000000 --- a/src/main/scala/ocelot/desktop/ui/event/DoubleClickEvent.scala +++ /dev/null @@ -1,5 +0,0 @@ -package ocelot.desktop.ui.event - -import ocelot.desktop.geometry.Vector2D - -case class DoubleClickEvent(button: MouseEvent.Button.Value, mousePos: Vector2D) extends Event diff --git a/src/main/scala/ocelot/desktop/ui/event/handlers/MouseHandler.scala b/src/main/scala/ocelot/desktop/ui/event/handlers/MouseHandler.scala index 19576d7..3228ee9 100644 --- a/src/main/scala/ocelot/desktop/ui/event/handlers/MouseHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/event/handlers/MouseHandler.scala @@ -2,8 +2,8 @@ package ocelot.desktop.ui.event.handlers import ocelot.desktop.geometry.Vector2D import ocelot.desktop.ui.UiHandler -import ocelot.desktop.ui.event.handlers.MouseHandler.{DoubleClickTimeMillis, Tolerance} -import ocelot.desktop.ui.event.{ClickEvent, DoubleClickEvent, DragEvent, MouseEvent} +import ocelot.desktop.ui.event.handlers.MouseHandler.{ClickInfo, withinTolerance} +import ocelot.desktop.ui.event.{ClickEvent, DoubleClickEvent, DragEvent, MouseEvent, TripleClickEvent} import ocelot.desktop.ui.widget.Widget import scala.collection.mutable @@ -13,9 +13,7 @@ trait MouseHandler extends Widget { private val prevPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]() private val dragButtons = new mutable.HashSet[MouseEvent.Button.Value]() - private var lastClickPosition: Vector2D = Vector2D.Zero - private var lastClickTime: Long = 0 - private var lastClickButton = MouseEvent.Button.Left + private var lastClickInfo = Option.empty[ClickInfo] override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents @@ -59,24 +57,29 @@ trait MouseHandler extends Widget { } if (clicked) { + val time = System.currentTimeMillis() + val info = ClickInfo(mousePos, time, button, 1) + + for (lastInfo <- lastClickInfo if lastInfo.isConsecutive(info)) { + if (lastInfo.count < 3) { + info.count = lastInfo.count + 1 + } + } + + lastClickInfo = Some(info) + handleEvent(ClickEvent(button, mousePos)) - val inTimeForDoubleClick = System.currentTimeMillis() - lastClickTime < DoubleClickTimeMillis - val sameButton = lastClickButton == button - val roughlySamePosition = withinTolerance(lastClickPosition, mousePos) - if (inTimeForDoubleClick && sameButton && roughlySamePosition) { - handleEvent(DoubleClickEvent(button, mousePos)) + info.count match { + case 2 => handleEvent(DoubleClickEvent(button, mousePos)) + case 3 => handleEvent(TripleClickEvent(button, mousePos)) + case _ => } - lastClickTime = System.currentTimeMillis() - lastClickPosition = mousePos - lastClickButton = button } startPositions.remove(button) } - private def withinTolerance(a: Vector2D, b: Vector2D): Boolean = (b - a).lengthSquared < Tolerance * Tolerance - override def update(): Unit = { super.update() @@ -112,5 +115,17 @@ trait MouseHandler extends Widget { object MouseHandler { private val Tolerance = 8 - private val DoubleClickTimeMillis = 1e3f / 2 + private val ConsecutiveClickTimeMillis = 1e3f / 2 + + private def withinTolerance(a: Vector2D, b: Vector2D): Boolean = (b - a).lengthSquared < Tolerance * Tolerance + + private case class ClickInfo(position: Vector2D, time: Long, button: MouseEvent.Button.Value, var count: Int) { + def isConsecutive(prev: ClickInfo): Boolean = { + val quickEnough = time - prev.time < ConsecutiveClickTimeMillis + val sameButton = button == prev.button + val closeEnough = withinTolerance(position, prev.position) + + quickEnough && sameButton && closeEnough + } + } } diff --git a/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala b/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala index 5fe6047..8cd4246 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala @@ -7,7 +7,7 @@ import ocelot.desktop.graphics.{Font, Graphics, IconSource} import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.event.handlers.MouseHandler import ocelot.desktop.ui.event.sources.KeyEvents -import ocelot.desktop.ui.event.{DoubleClickEvent, DragEvent, KeyEvent, MouseEvent} +import ocelot.desktop.ui.event.{DoubleClickEvent, DragEvent, KeyEvent, MouseEvent, TripleClickEvent} import ocelot.desktop.ui.widget.TextInput.{Cursor, Selection, Text} import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} import ocelot.desktop.ui.widget.traits.HoverAnimation @@ -183,6 +183,9 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w case DoubleClickEvent(MouseEvent.Button.Left, _) if isFocused && mouseInBounds => selectWord() + case TripleClickEvent(MouseEvent.Button.Left, _) if isFocused && mouseInBounds => + selectAll() + case DragEvent(DragEvent.State.Start | DragEvent.State.Drag, MouseEvent.Button.Left, mouse) if isFocused => val pos = if (mouse.y < bounds.y) { 0