mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Initial refactoring of TextInput
This commit is contained in:
parent
8724c4a3ad
commit
81840e4fab
@ -1,7 +1,7 @@
|
|||||||
package ocelot.desktop.util
|
package ocelot.desktop.graphics
|
||||||
|
|
||||||
import ocelot.desktop.geometry.Rect2D
|
import ocelot.desktop.geometry.Rect2D
|
||||||
import ocelot.desktop.graphics.Texture
|
import ocelot.desktop.util.{Logging, Resource}
|
||||||
import org.lwjgl.opengl.GL11
|
import org.lwjgl.opengl.GL11
|
||||||
import totoro.ocelot.brain.util.FontUtils
|
import totoro.ocelot.brain.util.FontUtils
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ import scala.io.{Codec, Source}
|
|||||||
class Font(val name: String, val fontSize: Int) extends Resource with Logging {
|
class Font(val name: String, val fontSize: Int) extends Resource with Logging {
|
||||||
val AtlasWidth = 4096
|
val AtlasWidth = 4096
|
||||||
val AtlasHeight = 4096
|
val AtlasHeight = 4096
|
||||||
var glyphCount = 0
|
private var glyphCount = 0
|
||||||
var outOfRangeGlyphCount = 0
|
private var outOfRangeGlyphCount = 0
|
||||||
|
|
||||||
private val atlas: BufferedImage = {
|
private val atlas: BufferedImage = {
|
||||||
val icmArr = Array(0.toByte, 0xff.toByte)
|
val icmArr = Array(0.toByte, 0xff.toByte)
|
||||||
@ -134,3 +134,14 @@ class Font(val name: String, val fontSize: Int) extends Resource with Logging {
|
|||||||
texture.freeResource()
|
texture.freeResource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Font extends Resource {
|
||||||
|
val NormalFont = new Font("unscii-16", 16)
|
||||||
|
val SmallFont = new Font("unscii-8", 8)
|
||||||
|
|
||||||
|
override def freeResource(): Unit = {
|
||||||
|
super.freeResource()
|
||||||
|
NormalFont.freeResource()
|
||||||
|
SmallFont.freeResource()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@ import ocelot.desktop.graphics.Texture.MinFilteringMode
|
|||||||
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
|
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
|
||||||
import ocelot.desktop.graphics.render.InstanceRenderer
|
import ocelot.desktop.graphics.render.InstanceRenderer
|
||||||
import ocelot.desktop.ui.UiHandler
|
import ocelot.desktop.ui.UiHandler
|
||||||
import ocelot.desktop.util.{Font, Logging, Resource, Spritesheet}
|
import ocelot.desktop.util.{Logging, Resource, Spritesheet}
|
||||||
import org.lwjgl.BufferUtils
|
import org.lwjgl.BufferUtils
|
||||||
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
||||||
|
|
||||||
@ -25,9 +25,7 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
|||||||
private val shaderProgram = new ShaderProgram("general")
|
private val shaderProgram = new ShaderProgram("general")
|
||||||
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
||||||
|
|
||||||
private[graphics] val normalFont = new Font("unscii-16", 16)
|
private var _font: Font = Font.NormalFont
|
||||||
private val smallFont = new Font("unscii-8", 8)
|
|
||||||
private var _font: Font = normalFont
|
|
||||||
private var oldFont: Font = _font
|
private var oldFont: Font = _font
|
||||||
|
|
||||||
private val stack = mutable.Stack[GraphicsState](GraphicsState())
|
private val stack = mutable.Stack[GraphicsState](GraphicsState())
|
||||||
@ -87,8 +85,6 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
|||||||
|
|
||||||
offscreenTexture.freeResource()
|
offscreenTexture.freeResource()
|
||||||
Spritesheet.freeResource()
|
Spritesheet.freeResource()
|
||||||
smallFont.freeResource()
|
|
||||||
normalFont.freeResource()
|
|
||||||
renderer.freeResource()
|
renderer.freeResource()
|
||||||
shaderProgram.freeResource()
|
shaderProgram.freeResource()
|
||||||
screenShaderProgram.freeResource()
|
screenShaderProgram.freeResource()
|
||||||
@ -97,15 +93,15 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
|||||||
def font: Font = _font
|
def font: Font = _font
|
||||||
|
|
||||||
def setNormalFont(): Unit = {
|
def setNormalFont(): Unit = {
|
||||||
if (_font == normalFont) return
|
if (_font == Font.NormalFont) return
|
||||||
flush()
|
flush()
|
||||||
_font = normalFont
|
_font = Font.NormalFont
|
||||||
}
|
}
|
||||||
|
|
||||||
def setSmallFont(): Unit = {
|
def setSmallFont(): Unit = {
|
||||||
if (_font == smallFont) return
|
if (_font == Font.SmallFont) return
|
||||||
flush()
|
flush()
|
||||||
_font = smallFont
|
_font = Font.SmallFont
|
||||||
}
|
}
|
||||||
|
|
||||||
def save(): Unit = {
|
def save(): Unit = {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import ocelot.desktop.color.{Color, RGBAColorNorm}
|
|||||||
import ocelot.desktop.geometry.Transform2D
|
import ocelot.desktop.geometry.Transform2D
|
||||||
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
|
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
|
||||||
import ocelot.desktop.graphics.render.InstanceRenderer
|
import ocelot.desktop.graphics.render.InstanceRenderer
|
||||||
import ocelot.desktop.util.{Font, Resource, Spritesheet}
|
import ocelot.desktop.util.{Resource, Spritesheet}
|
||||||
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -19,7 +19,7 @@ class ScreenViewport(graphics: Graphics, private var _width: Int, private var _h
|
|||||||
private val shaderProgram = graphics.screenShaderProgram
|
private val shaderProgram = graphics.screenShaderProgram
|
||||||
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
||||||
|
|
||||||
private val _font = graphics.normalFont
|
private val _font = Font.NormalFont
|
||||||
private val spriteRect = Spritesheet.sprites("Empty")
|
private val spriteRect = Spritesheet.sprites("Empty")
|
||||||
|
|
||||||
private val emptySpriteTrans =
|
private val emptySpriteTrans =
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package ocelot.desktop.ui
|
|||||||
import buildinfo.BuildInfo
|
import buildinfo.BuildInfo
|
||||||
import ocelot.desktop.audio.{Audio, SoundBuffers, SoundSource}
|
import ocelot.desktop.audio.{Audio, SoundBuffers, SoundSource}
|
||||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.{Font, Graphics}
|
||||||
import ocelot.desktop.ui.event.handlers.HoverHandler
|
import ocelot.desktop.ui.event.handlers.HoverHandler
|
||||||
import ocelot.desktop.ui.event.sources.{BrainEvents, KeyEvents, MouseEvents, ScrollEvents}
|
import ocelot.desktop.ui.event.sources.{BrainEvents, KeyEvents, MouseEvents, ScrollEvents}
|
||||||
import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, HoverEvent, MouseEvent}
|
import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, HoverEvent, MouseEvent}
|
||||||
@ -418,6 +418,7 @@ object UiHandler extends Logging {
|
|||||||
KeyEvents.destroy()
|
KeyEvents.destroy()
|
||||||
MouseEvents.destroy()
|
MouseEvents.destroy()
|
||||||
graphics.freeResource()
|
graphics.freeResource()
|
||||||
|
Font.freeResource()
|
||||||
Audio.removeAllSources()
|
Audio.removeAllSources()
|
||||||
SoundBuffers.freeResource()
|
SoundBuffers.freeResource()
|
||||||
Display.destroy()
|
Display.destroy()
|
||||||
|
|||||||
@ -16,8 +16,8 @@ class Button(tooltip: Option[Tooltip] = None) extends Widget with MouseHandler w
|
|||||||
def this(tooltip: Tooltip) = this(Some(tooltip))
|
def this(tooltip: Tooltip) = this(Some(tooltip))
|
||||||
|
|
||||||
protected def colorScheme: ColorScheme = ColorScheme.General
|
protected def colorScheme: ColorScheme = ColorScheme.General
|
||||||
override protected val hoverAnimationColorDefault: Color = colorScheme("ButtonBackground")
|
override protected val HoverAnimationColorDefault: Color = colorScheme("ButtonBackground")
|
||||||
override protected val hoverAnimationColorActive: Color = colorScheme("ButtonBackgroundActive")
|
override protected val HoverAnimationColorActive: Color = colorScheme("ButtonBackgroundActive")
|
||||||
|
|
||||||
def text: String = ""
|
def text: String = ""
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
|
|||||||
|
|
||||||
override def onInput(text: String): Unit = {
|
override def onInput(text: String): Unit = {
|
||||||
tickInterval = parseInput(text).map { interval =>
|
tickInterval = parseInput(text).map { interval =>
|
||||||
inputTPS.setInput(formatTPS(interval))
|
inputTPS.text = formatTPS(interval)
|
||||||
interval
|
interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
|
|||||||
|
|
||||||
override def onInput(text: String): Unit = {
|
override def onInput(text: String): Unit = {
|
||||||
tickInterval = parseInput(text).map { interval =>
|
tickInterval = parseInput(text).map { interval =>
|
||||||
inputMSPT.setInput(formatMSPT(interval))
|
inputMSPT.text = formatMSPT(interval)
|
||||||
interval
|
interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import ocelot.desktop.util.DrawUtils
|
|||||||
class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall: Boolean = false)
|
class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall: Boolean = false)
|
||||||
extends Widget with MouseHandler with HoverAnimation {
|
extends Widget with MouseHandler with HoverAnimation {
|
||||||
|
|
||||||
override protected val hoverAnimationColorDefault: Color = ColorScheme("CheckboxBackground")
|
override protected val HoverAnimationColorDefault: Color = ColorScheme("CheckboxBackground")
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("CheckboxBackgroundActive")
|
override protected val HoverAnimationColorActive: Color = ColorScheme("CheckboxBackgroundActive")
|
||||||
|
|
||||||
private var _checked: Boolean = initialValue
|
private var _checked: Boolean = initialValue
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import ocelot.desktop.util.DrawUtils
|
|||||||
class Slider(var value: Float, val text: String, val snapPoints: Int = 0)
|
class Slider(var value: Float, val text: String, val snapPoints: Int = 0)
|
||||||
extends Widget with MouseHandler with HoverAnimation {
|
extends Widget with MouseHandler with HoverAnimation {
|
||||||
|
|
||||||
override protected val hoverAnimationColorDefault: Color = ColorScheme("SliderBackground")
|
override protected val HoverAnimationColorDefault: Color = ColorScheme("SliderBackground")
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("SliderBackgroundActive")
|
override protected val HoverAnimationColorActive: Color = ColorScheme("SliderBackgroundActive")
|
||||||
|
|
||||||
def onValueChanged(value: Float): Unit = {}
|
def onValueChanged(value: Float): Unit = {}
|
||||||
def onValueFinal(value: Float): Unit = {}
|
def onValueFinal(value: Float): Unit = {}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package ocelot.desktop.ui.widget
|
|||||||
import ocelot.desktop.ColorScheme
|
import ocelot.desktop.ColorScheme
|
||||||
import ocelot.desktop.color.Color
|
import ocelot.desktop.color.Color
|
||||||
import ocelot.desktop.geometry.Size2D
|
import ocelot.desktop.geometry.Size2D
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.{Font, Graphics}
|
||||||
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
|
||||||
@ -13,12 +13,33 @@ import ocelot.desktop.util.DrawUtils
|
|||||||
import ocelot.desktop.util.animation.ColorAnimation
|
import ocelot.desktop.util.animation.ColorAnimation
|
||||||
import org.lwjgl.input.Keyboard
|
import org.lwjgl.input.Keyboard
|
||||||
|
|
||||||
import scala.collection.mutable.ArrayBuffer
|
|
||||||
|
|
||||||
class TextInput(val initialText: String = "") extends Widget with MouseHandler with HoverAnimation {
|
class TextInput(val initialText: String = "") extends Widget with MouseHandler with HoverAnimation {
|
||||||
override protected val hoverAnimationColorDefault: Color = ColorScheme("TextInputBackground")
|
private val CursorBlinkTime = 2f
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("TextInputBackgroundActive")
|
private val PlaceholderForegroundColor: Color = ColorScheme("TextInputForegroundDisabled")
|
||||||
|
override protected val HoverAnimationColorDefault: Color = ColorScheme("TextInputBackground")
|
||||||
|
override protected val HoverAnimationColorActive: Color = ColorScheme("TextInputBackgroundActive")
|
||||||
|
|
||||||
|
// text state
|
||||||
|
private var chars = initialText.toCharArray
|
||||||
|
private var textChanged = false
|
||||||
|
|
||||||
|
// cursor state
|
||||||
|
private var cursorPos = 0
|
||||||
|
private var cursorOffset = 0f
|
||||||
|
|
||||||
|
// view state
|
||||||
|
private var isFocused = false
|
||||||
|
private var scroll = 0f
|
||||||
|
private var blinkTimer = 0f
|
||||||
|
private var prevEnabled = enabled
|
||||||
|
|
||||||
|
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
|
||||||
|
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
|
||||||
|
|
||||||
|
|
||||||
|
// public API
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
def onInput(text: String): Unit = {}
|
def onInput(text: String): Unit = {}
|
||||||
|
|
||||||
def onConfirm(): Unit = {
|
def onConfirm(): Unit = {
|
||||||
@ -28,125 +49,24 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
def validator(text: String): Boolean = true
|
def validator(text: String): Boolean = true
|
||||||
final def isInputValid: Boolean = validator(text)
|
final def isInputValid: Boolean = validator(text)
|
||||||
|
|
||||||
var isFocused = false
|
|
||||||
|
|
||||||
def text: String = chars.mkString
|
def text: String = chars.mkString
|
||||||
def text_=(value: String): Unit = chars = value.toCharArray
|
def text_=(value: String): Unit = {
|
||||||
|
chars = value.toCharArray
|
||||||
protected var placeholder: Array[Char] = "".toCharArray
|
|
||||||
def placeholder_=(value: String): Unit = placeholder = value.toCharArray
|
|
||||||
|
|
||||||
private var cursorPos = 0
|
|
||||||
private var cursorOffset = 0f
|
|
||||||
private var scroll = 0f
|
|
||||||
private val CursorBlinkTime = 2f
|
|
||||||
private var blinkTimer = 0f
|
|
||||||
private var chars = initialText.toCharArray
|
|
||||||
private var textWidth = 0
|
|
||||||
private var textChanged = false
|
|
||||||
private val events = ArrayBuffer[TextEvent]()
|
|
||||||
|
|
||||||
override protected def receiveClickEvents: Boolean = true
|
|
||||||
|
|
||||||
// TODO: implement text selection
|
|
||||||
// override protected def receiveDragEvents: Boolean = true
|
|
||||||
|
|
||||||
override protected def allowClickReleaseOutsideThreshold: Boolean = false
|
|
||||||
|
|
||||||
override def receiveAllMouseEvents = true
|
|
||||||
|
|
||||||
private var prevEnabled = enabled
|
|
||||||
|
|
||||||
eventHandlers += {
|
|
||||||
case MouseEvent(MouseEvent.State.Released, MouseEvent.Button.Left) =>
|
|
||||||
if (isFocused && !clippedBounds.contains(UiHandler.mousePosition)) {
|
|
||||||
unfocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
case ClickEvent(MouseEvent.Button.Left, pos) if enabled =>
|
|
||||||
focus()
|
|
||||||
events += new CursorMoveTo(pos.x)
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_LEFT, _) if isFocused =>
|
|
||||||
events += new CursorMoveLeft
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_RIGHT, _) if isFocused =>
|
|
||||||
events += new CursorMoveRight
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) if isFocused =>
|
|
||||||
events += new CursorMoveStart
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_END, _) if isFocused =>
|
|
||||||
events += new CursorMoveEnd
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_BACK, _) if isFocused =>
|
|
||||||
events += new EraseCharBack
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_DELETE, _) if isFocused =>
|
|
||||||
events += new EraseCharFront
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_INSERT, _) if isFocused =>
|
|
||||||
// TODO: insert the whole clipboard string at once instead of going char-by-char.
|
|
||||||
for (char <- UiHandler.clipboard.toCharArray) {
|
|
||||||
events += new WriteChar(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_V, _)
|
|
||||||
if isFocused
|
|
||||||
&& KeyEvents.isControlDown =>
|
|
||||||
|
|
||||||
// TODO: insert the whole clipboard string at once instead of going char-by-char.
|
|
||||||
for (char <- UiHandler.clipboard.toCharArray) {
|
|
||||||
events += new WriteChar(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused =>
|
|
||||||
onConfirm()
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, _, char) if isFocused && !char.isControl =>
|
|
||||||
events += new WriteChar(char)
|
|
||||||
event.consume()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setInput(text: String): Unit = {
|
|
||||||
this.chars = text.toCharArray
|
|
||||||
cursorPos = 0
|
cursorPos = 0
|
||||||
cursorOffset = 0
|
cursorOffset = 0
|
||||||
textWidth = 0
|
|
||||||
textChanged = true
|
textChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override def minimumSize: Size2D = Size2D(200, 24)
|
protected var placeholder: Array[Char] = "".toCharArray
|
||||||
override def maximumSize: Size2D = Size2D(Float.PositiveInfinity, 24)
|
def placeholder_=(value: String): Unit = placeholder = value.toCharArray
|
||||||
|
|
||||||
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
|
|
||||||
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
|
|
||||||
|
|
||||||
private def updateAnimationTargets(): Unit = {
|
|
||||||
foregroundAnimation.goto(targetForegroundColor)
|
|
||||||
borderAnimation.goto(targetBorderColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
def focus(): Unit = {
|
def focus(): Unit = {
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
isFocused = true
|
isFocused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAnimationTargets()
|
updateAnimationTargets()
|
||||||
}
|
}
|
||||||
|
|
||||||
blinkTimer = 0
|
blinkTimer = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +77,181 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// widget management
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
|
override def minimumSize: Size2D = Size2D(200, 24)
|
||||||
|
override def maximumSize: Size2D = Size2D(Float.PositiveInfinity, 24)
|
||||||
|
|
||||||
|
override protected def receiveClickEvents: Boolean = true
|
||||||
|
override protected def receiveDragEvents: Boolean = true
|
||||||
|
override protected def allowClickReleaseOutsideThreshold: Boolean = false
|
||||||
|
override def receiveAllMouseEvents = true
|
||||||
|
|
||||||
|
protected def font(): Font = Font.NormalFont
|
||||||
|
|
||||||
|
private def charWidth(c: Char): Int = font().charWidth(c)
|
||||||
|
|
||||||
|
private def charsWidth(chars: Array[Char]): Int = charsWidth(chars, 0, chars.length - 1)
|
||||||
|
|
||||||
|
private def charsWidth(chars: Array[Char], first: Int, last: Int): Int = {
|
||||||
|
var width = 0
|
||||||
|
for (index <- first to last) {
|
||||||
|
width += font().charWidth(chars(index))
|
||||||
|
}
|
||||||
|
width
|
||||||
|
}
|
||||||
|
|
||||||
|
eventHandlers += {
|
||||||
|
case MouseEvent(MouseEvent.State.Released, MouseEvent.Button.Left) =>
|
||||||
|
if (isFocused && !clippedBounds.contains(UiHandler.mousePosition)) {
|
||||||
|
unfocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
case ClickEvent(MouseEvent.Button.Left, mouse) if enabled =>
|
||||||
|
focus()
|
||||||
|
val absoluteX = mouse.x - bounds.x + scroll - 4
|
||||||
|
var width = 0
|
||||||
|
var pos = 0
|
||||||
|
while (width < absoluteX && pos < chars.length) {
|
||||||
|
width += charWidth(chars(pos))
|
||||||
|
if (width < absoluteX) pos += 1
|
||||||
|
}
|
||||||
|
cursorPos = chars.length.min(pos).max(0)
|
||||||
|
cursorOffset = if (cursorPos > 0) charsWidth(chars, 0, (cursorPos - 1).max(0)) else 0
|
||||||
|
blinkTimer = 0
|
||||||
|
adjustScroll()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_LEFT, _) if isFocused =>
|
||||||
|
if (cursorPos != 0) {
|
||||||
|
cursorOffset -= charWidth(chars(cursorPos - 1))
|
||||||
|
cursorPos -= 1
|
||||||
|
blinkTimer = 0
|
||||||
|
adjustScroll()
|
||||||
|
}
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_RIGHT, _) if isFocused =>
|
||||||
|
if (cursorPos < chars.length) {
|
||||||
|
cursorOffset += charWidth(chars(cursorPos))
|
||||||
|
cursorPos += 1
|
||||||
|
blinkTimer = 0
|
||||||
|
adjustScroll()
|
||||||
|
}
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) if isFocused =>
|
||||||
|
cursorPos = 0
|
||||||
|
cursorOffset = 0
|
||||||
|
blinkTimer = 0
|
||||||
|
scroll = 0
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_END, _) if isFocused =>
|
||||||
|
val textWidth = charsWidth(chars)
|
||||||
|
cursorPos = chars.length
|
||||||
|
cursorOffset = textWidth
|
||||||
|
blinkTimer = 0
|
||||||
|
scroll = (textWidth - size.width + 16).max(0)
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_BACK, _) if isFocused =>
|
||||||
|
val (lhs, rhs) = chars.splitAt(cursorPos)
|
||||||
|
if (!lhs.isEmpty) {
|
||||||
|
val cw = charWidth(lhs.last)
|
||||||
|
chars = lhs.take(lhs.length - 1) ++ rhs
|
||||||
|
textChanged = true
|
||||||
|
cursorOffset -= cw
|
||||||
|
cursorPos -= 1
|
||||||
|
blinkTimer = 0
|
||||||
|
scroll = (scroll - cw).max(0)
|
||||||
|
}
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_DELETE, _) if isFocused =>
|
||||||
|
val (lhs, rhs) = chars.splitAt(cursorPos)
|
||||||
|
if (!rhs.isEmpty) {
|
||||||
|
val cw = charWidth(rhs.head)
|
||||||
|
chars = lhs ++ rhs.drop(1)
|
||||||
|
textChanged = true
|
||||||
|
blinkTimer = 0
|
||||||
|
if (rhs.drop(1).map(charWidth).sum < size.width - 16) {
|
||||||
|
scroll = (scroll - cw).max(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_INSERT, _) if isFocused =>
|
||||||
|
// TODO: insert the whole clipboard string at once instead of going char-by-char.
|
||||||
|
for (char <- UiHandler.clipboard.toCharArray) {
|
||||||
|
writeChar(char)
|
||||||
|
}
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_V, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown =>
|
||||||
|
// TODO: insert the whole clipboard string at once instead of going char-by-char.
|
||||||
|
for (char <- UiHandler.clipboard.toCharArray) {
|
||||||
|
writeChar(char)
|
||||||
|
}
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused =>
|
||||||
|
onConfirm()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, _, char) if isFocused && !char.isControl =>
|
||||||
|
writeChar(char)
|
||||||
|
event.consume()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def writeChar(char: Char): Unit = {
|
||||||
|
val (lhs, rhs) = chars.splitAt(cursorPos)
|
||||||
|
chars = lhs ++ Array(char) ++ rhs
|
||||||
|
textChanged = true
|
||||||
|
cursorOffset += charWidth(chars(cursorPos))
|
||||||
|
cursorPos += 1
|
||||||
|
blinkTimer = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private def adjustScroll(): Unit = {
|
||||||
|
if (cursorOffset < scroll)
|
||||||
|
scroll = cursorOffset
|
||||||
|
if (cursorOffset - scroll > size.width - 16)
|
||||||
|
scroll = cursorOffset - size.width + 16
|
||||||
|
}
|
||||||
|
|
||||||
|
override def update(): Unit = {
|
||||||
|
super.update()
|
||||||
|
|
||||||
|
if (textChanged) {
|
||||||
|
onInput(text)
|
||||||
|
updateAnimationTargets()
|
||||||
|
textChanged = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextEnabled = enabled
|
||||||
|
if (nextEnabled != prevEnabled) {
|
||||||
|
updateAnimationTargets()
|
||||||
|
prevEnabled = nextEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFocused && !enabled) {
|
||||||
|
unfocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
foregroundAnimation.update()
|
||||||
|
borderAnimation.update()
|
||||||
|
blinkTimer = (blinkTimer + UiHandler.dt) % CursorBlinkTime
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def updateAnimationTargets(): Unit = {
|
||||||
|
foregroundAnimation.goto(targetForegroundColor)
|
||||||
|
borderAnimation.goto(targetBorderColor)
|
||||||
|
}
|
||||||
|
|
||||||
private def targetBorderColor: Color = ColorScheme(
|
private def targetBorderColor: Color = ColorScheme(
|
||||||
if (validator(chars.mkString)) {
|
if (validator(chars.mkString)) {
|
||||||
if (isFocused) "TextInputBorderFocused"
|
if (isFocused) "TextInputBorderFocused"
|
||||||
@ -174,29 +269,14 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
else "TextInputForeground"
|
else "TextInputForeground"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val placeholderForegroundColor: Color = ColorScheme("TextInputForegroundDisabled")
|
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
if (textWidth == 0f && chars.nonEmpty)
|
|
||||||
textWidth = chars.map(g.font.charWidth(_)).sum
|
|
||||||
|
|
||||||
textChanged = false
|
|
||||||
for (event <- events) event.handle(g)
|
|
||||||
events.clear()
|
|
||||||
|
|
||||||
if (textChanged) {
|
|
||||||
val str = chars.mkString
|
|
||||||
onInput(str)
|
|
||||||
updateAnimationTargets()
|
|
||||||
}
|
|
||||||
|
|
||||||
g.rect(bounds, hoverAnimation.color)
|
g.rect(bounds, hoverAnimation.color)
|
||||||
DrawUtils.ring(g, position.x, position.y, size.width, size.height, thickness = 2, borderAnimation.color)
|
DrawUtils.ring(g, position.x, position.y, size.width, size.height, thickness = 2, borderAnimation.color)
|
||||||
|
|
||||||
g.setScissor(position.x + 4, position.y, size.width - 8f, size.height)
|
g.setScissor(position.x + 4, position.y, size.width - 8f, size.height)
|
||||||
|
|
||||||
g.background = Color.Transparent
|
g.background = Color.Transparent
|
||||||
g.foreground = if (chars.nonEmpty || isFocused) foregroundAnimation.color else placeholderForegroundColor
|
g.foreground = if (chars.nonEmpty || isFocused) foregroundAnimation.color else PlaceholderForegroundColor
|
||||||
|
|
||||||
var charOffset = 0
|
var charOffset = 0
|
||||||
val charsToDisplay = if (chars.nonEmpty || isFocused) chars else placeholder
|
val charsToDisplay = if (chars.nonEmpty || isFocused) chars else placeholder
|
||||||
@ -209,149 +289,4 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
g.rect(position.x + 7 + cursorOffset - scroll, position.y + 4, 2, 16, borderAnimation.color)
|
g.rect(position.x + 7 + cursorOffset - scroll, position.y + 4, 2, 16, borderAnimation.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def update(): Unit = {
|
|
||||||
super.update()
|
|
||||||
|
|
||||||
val nextEnabled = enabled
|
|
||||||
|
|
||||||
if (nextEnabled != prevEnabled) {
|
|
||||||
updateAnimationTargets()
|
|
||||||
prevEnabled = nextEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFocused && !enabled) {
|
|
||||||
unfocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
foregroundAnimation.update()
|
|
||||||
borderAnimation.update()
|
|
||||||
blinkTimer = (blinkTimer + UiHandler.dt) % CursorBlinkTime
|
|
||||||
}
|
|
||||||
|
|
||||||
private def charWidth(g: Graphics, c: Char): Int = g.font.charWidth(c)
|
|
||||||
|
|
||||||
// noinspection SameParameterValue
|
|
||||||
private def charsWidth(g: Graphics, chars: Array[Char], first: Int, last: Int): Int = {
|
|
||||||
var width = 0
|
|
||||||
for (index <- first to last) {
|
|
||||||
width += g.font.charWidth(chars(index))
|
|
||||||
}
|
|
||||||
width
|
|
||||||
}
|
|
||||||
|
|
||||||
private def adjustScroll(): Unit = {
|
|
||||||
if (cursorOffset < scroll)
|
|
||||||
scroll = cursorOffset
|
|
||||||
if (cursorOffset - scroll > size.width - 16)
|
|
||||||
scroll = cursorOffset - size.width + 16
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class TextEvent {
|
|
||||||
def handle(g: Graphics): Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor this mess. have actions only move the cursor position explicitly.
|
|
||||||
// then calculate the cursor offset (incl. the scroll offset) based on that automatically
|
|
||||||
// rather than incrementally in the actions.
|
|
||||||
private class CursorMoveLeft extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
if (cursorPos == 0) return
|
|
||||||
|
|
||||||
cursorOffset -= charWidth(g, chars(cursorPos - 1))
|
|
||||||
cursorPos -= 1
|
|
||||||
blinkTimer = 0
|
|
||||||
|
|
||||||
adjustScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveRight extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
if (cursorPos >= chars.length) return
|
|
||||||
|
|
||||||
cursorOffset += charWidth(g, chars(cursorPos))
|
|
||||||
cursorPos += 1
|
|
||||||
blinkTimer = 0
|
|
||||||
|
|
||||||
adjustScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveStart extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
cursorPos = 0
|
|
||||||
cursorOffset = 0
|
|
||||||
blinkTimer = 0
|
|
||||||
scroll = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveEnd extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
cursorPos = chars.length
|
|
||||||
cursorOffset = textWidth
|
|
||||||
blinkTimer = 0
|
|
||||||
scroll = (textWidth - size.width + 16).max(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveTo(mouseX: Float) extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val absoluteX = mouseX - bounds.x + scroll - 4
|
|
||||||
var width = 0
|
|
||||||
var pos = 0
|
|
||||||
while (width < absoluteX && pos < chars.length) {
|
|
||||||
width += g.font.charWidth(chars(pos))
|
|
||||||
if (width < absoluteX) pos += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cursorPos = chars.length.min(pos).max(0)
|
|
||||||
cursorOffset = if (cursorPos > 0) charsWidth(g, chars, 0, (cursorPos - 1).max(0)) else 0
|
|
||||||
blinkTimer = 0
|
|
||||||
adjustScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EraseCharBack extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val (lhs, rhs) = chars.splitAt(cursorPos)
|
|
||||||
if (lhs.isEmpty) return
|
|
||||||
|
|
||||||
val cw = charWidth(g, lhs.last)
|
|
||||||
chars = lhs.take(lhs.length - 1) ++ rhs
|
|
||||||
textChanged = true
|
|
||||||
textWidth -= cw
|
|
||||||
cursorOffset -= cw
|
|
||||||
cursorPos -= 1
|
|
||||||
blinkTimer = 0
|
|
||||||
scroll = (scroll - cw).max(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EraseCharFront extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val (lhs, rhs) = chars.splitAt(cursorPos)
|
|
||||||
if (rhs.isEmpty) return
|
|
||||||
|
|
||||||
val cw = charWidth(g, rhs.head)
|
|
||||||
chars = lhs ++ rhs.drop(1)
|
|
||||||
textChanged = true
|
|
||||||
textWidth -= cw
|
|
||||||
blinkTimer = 0
|
|
||||||
|
|
||||||
if (rhs.drop(1).map(charWidth(g, _)).sum < size.width - 16)
|
|
||||||
scroll = (scroll - cw).max(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class WriteChar(char: Char) extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val (lhs, rhs) = chars.splitAt(cursorPos)
|
|
||||||
chars = lhs ++ Array(char) ++ rhs
|
|
||||||
textChanged = true
|
|
||||||
textWidth += charWidth(g, char)
|
|
||||||
(new CursorMoveRight).handle(g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,7 +158,7 @@ class SystemSettingsTab extends SettingsTab with Logging {
|
|||||||
|
|
||||||
private def setConfigPath(path: String): Unit = {
|
private def setConfigPath(path: String): Unit = {
|
||||||
Settings.get.brainCustomConfigPath = Some(path)
|
Settings.get.brainCustomConfigPath = Some(path)
|
||||||
textInput.setInput(path)
|
textInput.text = path
|
||||||
restartWarning.isVisible = true
|
restartWarning.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -157,8 +157,8 @@ class StatusBar extends Widget {
|
|||||||
|
|
||||||
override def receiveMouseEvents: Boolean = true
|
override def receiveMouseEvents: Boolean = true
|
||||||
|
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("StatusBarActive")
|
override protected val HoverAnimationColorActive: Color = ColorScheme("StatusBarActive")
|
||||||
override protected val hoverAnimationColorDefault: Color = hoverAnimationColorActive.toRGBANorm.withAlpha(0)
|
override protected val HoverAnimationColorDefault: Color = HoverAnimationColorActive.toRGBANorm.withAlpha(0)
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
g.rect(bounds, hoverAnimation.color)
|
g.rect(bounds, hoverAnimation.color)
|
||||||
|
|||||||
@ -13,23 +13,23 @@ import ocelot.desktop.util.animation.ColorAnimation
|
|||||||
*/
|
*/
|
||||||
trait HoverAnimation extends Widget with EventAware with HoverHandler with Updatable {
|
trait HoverAnimation extends Widget with EventAware with HoverHandler with Updatable {
|
||||||
//noinspection ScalaWeakerAccess
|
//noinspection ScalaWeakerAccess
|
||||||
protected val hoverAnimationSpeedEnter: Float = AnimationSpeedHoverEnter
|
protected val HoverAnimationSpeedEnter: Float = AnimationSpeedHoverEnter
|
||||||
//noinspection ScalaWeakerAccess
|
//noinspection ScalaWeakerAccess
|
||||||
protected val hoverAnimationSpeedLeave: Float = AnimationSpeedHoverLeave
|
protected val HoverAnimationSpeedLeave: Float = AnimationSpeedHoverLeave
|
||||||
protected val hoverAnimationColorDefault: Color = ColorScheme("ButtonBackground")
|
protected val HoverAnimationColorDefault: Color = ColorScheme("ButtonBackground")
|
||||||
protected val hoverAnimationColorActive: Color = ColorScheme("ButtonBackgroundActive")
|
protected val HoverAnimationColorActive: Color = ColorScheme("ButtonBackgroundActive")
|
||||||
|
|
||||||
protected lazy val hoverAnimation: ColorAnimation =
|
protected lazy val hoverAnimation: ColorAnimation =
|
||||||
new ColorAnimation(hoverAnimationColorDefault, hoverAnimationSpeedEnter)
|
new ColorAnimation(HoverAnimationColorDefault, HoverAnimationSpeedEnter)
|
||||||
|
|
||||||
eventHandlers += {
|
eventHandlers += {
|
||||||
case Capturing(HoverEvent(HoverEvent.State.Enter)) =>
|
case Capturing(HoverEvent(HoverEvent.State.Enter)) =>
|
||||||
hoverAnimation.speed = hoverAnimationSpeedEnter
|
hoverAnimation.speed = HoverAnimationSpeedEnter
|
||||||
hoverAnimation.goto(hoverAnimationColorActive)
|
hoverAnimation.goto(HoverAnimationColorActive)
|
||||||
|
|
||||||
case Capturing(HoverEvent(HoverEvent.State.Leave)) =>
|
case Capturing(HoverEvent(HoverEvent.State.Leave)) =>
|
||||||
hoverAnimation.speed = hoverAnimationSpeedLeave
|
hoverAnimation.speed = HoverAnimationSpeedLeave
|
||||||
hoverAnimation.goto(hoverAnimationColorDefault)
|
hoverAnimation.goto(HoverAnimationColorDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def update(): Unit = {
|
override def update(): Unit = {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWin
|
|||||||
children :+= new TextInput() {
|
children :+= new TextInput() {
|
||||||
override def onConfirm(): Unit = {
|
override def onConfirm(): Unit = {
|
||||||
pushLine(text)
|
pushLine(text)
|
||||||
setInput("")
|
text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user