Select all text on triple-click

This commit is contained in:
Fingercomp 2025-09-04 00:24:05 +03:00
parent 4cee456454
commit c0eec1fffc
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
4 changed files with 49 additions and 22 deletions

View File

@ -2,4 +2,18 @@ package ocelot.desktop.ui.event
import ocelot.desktop.geometry.Vector2D 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 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

View File

@ -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

View File

@ -2,8 +2,8 @@ package ocelot.desktop.ui.event.handlers
import ocelot.desktop.geometry.Vector2D import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.handlers.MouseHandler.{DoubleClickTimeMillis, Tolerance} import ocelot.desktop.ui.event.handlers.MouseHandler.{ClickInfo, withinTolerance}
import ocelot.desktop.ui.event.{ClickEvent, DoubleClickEvent, DragEvent, MouseEvent} import ocelot.desktop.ui.event.{ClickEvent, DoubleClickEvent, DragEvent, MouseEvent, TripleClickEvent}
import ocelot.desktop.ui.widget.Widget import ocelot.desktop.ui.widget.Widget
import scala.collection.mutable import scala.collection.mutable
@ -13,9 +13,7 @@ trait MouseHandler extends Widget {
private val prevPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]() private val prevPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]()
private val dragButtons = new mutable.HashSet[MouseEvent.Button.Value]() private val dragButtons = new mutable.HashSet[MouseEvent.Button.Value]()
private var lastClickPosition: Vector2D = Vector2D.Zero private var lastClickInfo = Option.empty[ClickInfo]
private var lastClickTime: Long = 0
private var lastClickButton = MouseEvent.Button.Left
override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents
@ -59,24 +57,29 @@ trait MouseHandler extends Widget {
} }
if (clicked) { 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)) handleEvent(ClickEvent(button, mousePos))
val inTimeForDoubleClick = System.currentTimeMillis() - lastClickTime < DoubleClickTimeMillis info.count match {
val sameButton = lastClickButton == button case 2 => handleEvent(DoubleClickEvent(button, mousePos))
val roughlySamePosition = withinTolerance(lastClickPosition, mousePos) case 3 => handleEvent(TripleClickEvent(button, mousePos))
if (inTimeForDoubleClick && sameButton && roughlySamePosition) { case _ =>
handleEvent(DoubleClickEvent(button, mousePos))
} }
lastClickTime = System.currentTimeMillis()
lastClickPosition = mousePos
lastClickButton = button
} }
startPositions.remove(button) startPositions.remove(button)
} }
private def withinTolerance(a: Vector2D, b: Vector2D): Boolean = (b - a).lengthSquared < Tolerance * Tolerance
override def update(): Unit = { override def update(): Unit = {
super.update() super.update()
@ -112,5 +115,17 @@ trait MouseHandler extends Widget {
object MouseHandler { object MouseHandler {
private val Tolerance = 8 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
}
}
} }

View File

@ -7,7 +7,7 @@ import ocelot.desktop.graphics.{Font, Graphics, IconSource}
import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.handlers.MouseHandler import ocelot.desktop.ui.event.handlers.MouseHandler
import ocelot.desktop.ui.event.sources.KeyEvents 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.TextInput.{Cursor, Selection, Text}
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.traits.HoverAnimation 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 => case DoubleClickEvent(MouseEvent.Button.Left, _) if isFocused && mouseInBounds =>
selectWord() selectWord()
case TripleClickEvent(MouseEvent.Button.Left, _) if isFocused && mouseInBounds =>
selectAll()
case DragEvent(DragEvent.State.Start | DragEvent.State.Drag, MouseEvent.Button.Left, mouse) if isFocused => case DragEvent(DragEvent.State.Start | DragEvent.State.Drag, MouseEvent.Button.Left, mouse) if isFocused =>
val pos = if (mouse.y < bounds.y) { val pos = if (mouse.y < bounds.y) {
0 0