mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Allow selecting current word by double click
This commit is contained in:
parent
a8dc52f1b2
commit
4e9d7c96e2
@ -0,0 +1,5 @@
|
|||||||
|
package ocelot.desktop.ui.event
|
||||||
|
|
||||||
|
import ocelot.desktop.geometry.Vector2D
|
||||||
|
|
||||||
|
case class DoubleClickEvent(button: MouseEvent.Button.Value, mousePos: Vector2D) extends Event
|
||||||
@ -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.Tolerance
|
import ocelot.desktop.ui.event.handlers.MouseHandler.{DoubleClickTime, Tolerance}
|
||||||
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, MouseEvent}
|
import ocelot.desktop.ui.event.{ClickEvent, DoubleClickEvent, DragEvent, MouseEvent}
|
||||||
import ocelot.desktop.ui.widget.Widget
|
import ocelot.desktop.ui.widget.Widget
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
@ -13,13 +13,18 @@ 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 lastClickTime: Long = 0
|
||||||
|
private var lastClickButton = MouseEvent.Button.Left
|
||||||
|
|
||||||
override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents
|
override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents
|
||||||
|
|
||||||
protected def receiveClickEvents: Boolean = false
|
protected def receiveClickEvents: Boolean = false
|
||||||
|
|
||||||
protected def receiveDragEvents: Boolean = false
|
protected def receiveDragEvents: Boolean = false
|
||||||
|
|
||||||
/** If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
|
/**
|
||||||
|
* If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
|
||||||
* outside the tolerance threshold, as long as it stays within the widget's bounds.
|
* outside the tolerance threshold, as long as it stays within the widget's bounds.
|
||||||
*/
|
*/
|
||||||
protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents
|
protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents
|
||||||
@ -39,7 +44,7 @@ trait MouseHandler extends Widget {
|
|||||||
if (allowClickReleaseOutsideThreshold) {
|
if (allowClickReleaseOutsideThreshold) {
|
||||||
clippedBounds.contains(mousePos)
|
clippedBounds.contains(mousePos)
|
||||||
} else {
|
} else {
|
||||||
(p - mousePos).lengthSquared < Tolerance * Tolerance
|
withinTolerance(p, mousePos)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -50,11 +55,23 @@ trait MouseHandler extends Widget {
|
|||||||
|
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
handleEvent(ClickEvent(button, mousePos))
|
handleEvent(ClickEvent(button, mousePos))
|
||||||
|
|
||||||
|
val inTimeForDoubleClick = (System.currentTimeMillis() - lastClickTime) < DoubleClickTime * 1000
|
||||||
|
val sameButton = lastClickButton == button
|
||||||
|
val roughlySamePosition = withinTolerance(lastClickPosition, mousePos)
|
||||||
|
if (inTimeForDoubleClick && sameButton && roughlySamePosition) {
|
||||||
|
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()
|
||||||
|
|
||||||
@ -65,7 +82,7 @@ trait MouseHandler extends Widget {
|
|||||||
val mousePos = UiHandler.mousePosition
|
val mousePos = UiHandler.mousePosition
|
||||||
|
|
||||||
for ((button, startPos) <- startPositions) {
|
for ((button, startPos) <- startPositions) {
|
||||||
if (!dragButtons.contains(button) && (startPos - mousePos).lengthSquared > Tolerance * Tolerance) {
|
if (!dragButtons.contains(button) && !withinTolerance(startPos, mousePos)) {
|
||||||
handleEvent(DragEvent(DragEvent.State.Start, button, mousePos, startPos, Vector2D(0, 0)))
|
handleEvent(DragEvent(DragEvent.State.Start, button, mousePos, startPos, Vector2D(0, 0)))
|
||||||
dragButtons += button
|
dragButtons += button
|
||||||
prevPositions += (button -> mousePos)
|
prevPositions += (button -> mousePos)
|
||||||
@ -90,4 +107,5 @@ trait MouseHandler extends Widget {
|
|||||||
|
|
||||||
object MouseHandler {
|
object MouseHandler {
|
||||||
private val Tolerance = 8
|
private val Tolerance = 8
|
||||||
|
private val DoubleClickTime = 0.2
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.{DragEvent, KeyEvent, MouseEvent}
|
import ocelot.desktop.ui.event.{DoubleClickEvent, DragEvent, KeyEvent, MouseEvent}
|
||||||
import ocelot.desktop.ui.widget.TextInput.{Cursor, Selector, Text}
|
import ocelot.desktop.ui.widget.TextInput.{Cursor, Selector, 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
|
||||||
@ -15,6 +15,8 @@ import ocelot.desktop.util.{DrawUtils, Register, Watcher}
|
|||||||
import ocelot.desktop.util.animation.ColorAnimation
|
import ocelot.desktop.util.animation.ColorAnimation
|
||||||
import org.lwjgl.input.Keyboard
|
import org.lwjgl.input.Keyboard
|
||||||
|
|
||||||
|
import java.lang.Character.isWhitespace
|
||||||
|
|
||||||
|
|
||||||
class TextInput(val initialText: String = "") extends Widget with MouseHandler with HoverAnimation {
|
class TextInput(val initialText: String = "") extends Widget with MouseHandler with HoverAnimation {
|
||||||
private val CursorBlinkTime = 2f
|
private val CursorBlinkTime = 2f
|
||||||
@ -98,8 +100,12 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
// -------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
override def minimumSize: Size2D = Size2D(200, 24)
|
override def minimumSize: Size2D = Size2D(200, 24)
|
||||||
override def maximumSize: Size2D = Size2D(Float.PositiveInfinity, 24)
|
override def maximumSize: Size2D = Size2D(Float.PositiveInfinity, 24)
|
||||||
|
|
||||||
override def receiveAllMouseEvents = true
|
override def receiveAllMouseEvents = true
|
||||||
override def receiveDragEvents: Boolean = true
|
override def receiveDragEvents: Boolean = true
|
||||||
|
override def receiveClickEvents: Boolean = true
|
||||||
|
|
||||||
|
private def mouseInBounds: Boolean = clippedBounds.contains(UiHandler.mousePosition)
|
||||||
|
|
||||||
protected def font: Font = Font.NormalFont
|
protected def font: Font = Font.NormalFont
|
||||||
|
|
||||||
@ -121,7 +127,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
|
|
||||||
eventHandlers += {
|
eventHandlers += {
|
||||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) if enabled =>
|
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) if enabled =>
|
||||||
val inBounds = clippedBounds.contains(UiHandler.mousePosition)
|
val inBounds = mouseInBounds
|
||||||
if (isFocused && !inBounds) unfocus()
|
if (isFocused && !inBounds) unfocus()
|
||||||
if (!isFocused && inBounds) focus()
|
if (!isFocused && inBounds) focus()
|
||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
@ -132,8 +138,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
}
|
}
|
||||||
|
|
||||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Right) if isFocused =>
|
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Right) if isFocused =>
|
||||||
val inBounds = clippedBounds.contains(UiHandler.mousePosition)
|
if (mouseInBounds) {
|
||||||
if (inBounds) {
|
|
||||||
val menu = new ContextMenu
|
val menu = new ContextMenu
|
||||||
if (selector.active) {
|
if (selector.active) {
|
||||||
menu.addEntry(ContextMenuEntry("Cut", IconSource.Icons.Cut) { cutSelection() })
|
menu.addEntry(ContextMenuEntry("Cut", IconSource.Icons.Cut) { cutSelection() })
|
||||||
@ -149,6 +154,12 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
root.get.contextMenus.open(menu)
|
root.get.contextMenus.open(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case event @ DoubleClickEvent(MouseEvent.Button.Left, _) if isFocused =>
|
||||||
|
if (mouseInBounds) {
|
||||||
|
selectWord()
|
||||||
|
event.consume()
|
||||||
|
}
|
||||||
|
|
||||||
case event @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, mouse) if isFocused =>
|
case event @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, mouse) if isFocused =>
|
||||||
val pos = pixelToCursorPosition(mouse.x - bounds.x)
|
val pos = pixelToCursorPosition(mouse.x - bounds.x)
|
||||||
selector.active = true
|
selector.active = true
|
||||||
@ -238,6 +249,11 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
selectAll()
|
selectAll()
|
||||||
event.consume()
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_W, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown =>
|
||||||
|
selectWord()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_C, _)
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_C, _)
|
||||||
if isFocused && KeyEvents.isControlDown && selector.active =>
|
if isFocused && KeyEvents.isControlDown && selector.active =>
|
||||||
copySelection()
|
copySelection()
|
||||||
@ -288,6 +304,13 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
selector.to = _text.chars.length
|
selector.to = _text.chars.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def selectWord(): Unit = {
|
||||||
|
selector.active = true
|
||||||
|
selector.from = 0 max (_text.chars.lastIndexWhere(isWhitespace, cursor.position - 1) + 1)
|
||||||
|
val to = _text.chars.indexWhere(isWhitespace, cursor.position)
|
||||||
|
selector.to = if (to >= 0 && to < _text.chars.length) to else _text.chars.length
|
||||||
|
}
|
||||||
|
|
||||||
private def copySelection(): Unit = {
|
private def copySelection(): Unit = {
|
||||||
UiHandler.clipboard = selectedText
|
UiHandler.clipboard = selectedText
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user