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
/**
* 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

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.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
}
}
}

View File

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