mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Merge branch 'feature/qol-ocelot-interface' into develop
This commit is contained in:
commit
5e172ee804
@ -55,7 +55,7 @@ object OcelotDesktop extends LoggingConfiguration with Logging {
|
|||||||
def emulationPaused_=(paused: Boolean): Unit = {
|
def emulationPaused_=(paused: Boolean): Unit = {
|
||||||
_emulationPaused = paused
|
_emulationPaused = paused
|
||||||
// avoid sudden jumps of TPS counter after a pause
|
// avoid sudden jumps of TPS counter after a pause
|
||||||
if (!paused) tpsCounter.skipFrame()
|
if (!paused) tpsCounter.skipSecond()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TickerIntervalHistorySize = 5
|
private val TickerIntervalHistorySize = 5
|
||||||
|
|||||||
@ -3,29 +3,60 @@ package ocelot.desktop.ui.widget
|
|||||||
import ocelot.desktop.ColorScheme
|
import ocelot.desktop.ColorScheme
|
||||||
import ocelot.desktop.color.RGBAColorNorm
|
import ocelot.desktop.color.RGBAColorNorm
|
||||||
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D, Vector2D}
|
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D, Vector2D}
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||||
import ocelot.desktop.ui.layout.{Layout, LinearLayout}
|
import ocelot.desktop.ui.UiHandler
|
||||||
|
import ocelot.desktop.ui.event.handlers.MouseHandler
|
||||||
|
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
|
||||||
|
import ocelot.desktop.ui.layout.{CopyLayout, Layout}
|
||||||
import ocelot.desktop.ui.widget.LogWidget.{BorderThickness, EntryMargin, EntryPadding, LogEntry}
|
import ocelot.desktop.ui.widget.LogWidget.{BorderThickness, EntryMargin, EntryPadding, LogEntry}
|
||||||
import ocelot.desktop.util.{DrawUtils, Orientation}
|
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
|
||||||
|
import ocelot.desktop.util.DrawUtils
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
abstract class LogWidget extends Widget {
|
abstract class LogWidget extends Widget {
|
||||||
override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical)
|
override protected val layout: Layout = new CopyLayout(this)
|
||||||
|
|
||||||
private object MessageListWidget extends Widget { messageList =>
|
private object MessageListWidget extends Widget with MouseHandler { messageList =>
|
||||||
override protected val layout: Layout = new Layout(this)
|
override protected val layout: Layout = new Layout(this) {
|
||||||
|
override def recalculateBounds(): Unit = {
|
||||||
|
super.recalculateBounds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val entries = mutable.ArrayDeque.empty[Entry]
|
private val entries = mutable.ArrayDeque.empty[Entry]
|
||||||
|
private var needRelayout = false
|
||||||
|
|
||||||
val dummyEntry: Entry = new RxEntry("", nextMessageY)
|
val dummyEntry: Entry = new RxEntry("", nextMessageY)
|
||||||
|
|
||||||
|
override def receiveClickEvents: Boolean = true
|
||||||
|
|
||||||
|
eventHandlers += {
|
||||||
|
case ClickEvent(MouseEvent.Button.Right, mouse) =>
|
||||||
|
visibleEntries.find(_.absoluteBounds.contains(mouse)).foreach {
|
||||||
|
case entry: MessageEntry =>
|
||||||
|
val menu = new ContextMenu
|
||||||
|
menu.addEntry(ContextMenuEntry("Copy", IconSource.Icons.Copy) {
|
||||||
|
UiHandler.clipboard = entry.message
|
||||||
|
})
|
||||||
|
root.get.contextMenus.open(menu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when entries are removed from the head, all consecutive entries need to be shifted up.
|
||||||
|
// doing that for every single removal is **very** inefficient.
|
||||||
|
// instead, we keep the widgets where they are without shifting, having the offset accumulate, and update their
|
||||||
|
// positions only once per frame (in `draw`).
|
||||||
private def removedOffset: Float = entries.headOption.map(_.y - EntryMargin).getOrElse(0)
|
private def removedOffset: Float = entries.headOption.map(_.y - EntryMargin).getOrElse(0)
|
||||||
|
|
||||||
private def applyRemovedOffset(): Unit = {
|
private def applyRemovedOffset(): Unit = {
|
||||||
val offset = removedOffset
|
val offset = removedOffset
|
||||||
|
|
||||||
|
if (offset == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for (entry <- entries) {
|
for (entry <- entries) {
|
||||||
entry.y -= offset
|
entry.y -= offset
|
||||||
}
|
}
|
||||||
@ -36,6 +67,8 @@ abstract class LogWidget extends Widget {
|
|||||||
nextMessageY - removedOffset,
|
nextMessageY - removedOffset,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override def maximumSize: Size2D = minimumSize
|
||||||
|
|
||||||
private def nextMessageY: Float = {
|
private def nextMessageY: Float = {
|
||||||
entries.lastOption.map(_.maxY).getOrElse(0f) + EntryMargin
|
entries.lastOption.map(_.maxY).getOrElse(0f) + EntryMargin
|
||||||
}
|
}
|
||||||
@ -46,7 +79,7 @@ abstract class LogWidget extends Widget {
|
|||||||
case LogEntry.Tx(message) => new TxEntry(message, nextMessageY)
|
case LogEntry.Tx(message) => new TxEntry(message, nextMessageY)
|
||||||
})
|
})
|
||||||
|
|
||||||
parent.get.recalculateBoundsAndRelayout()
|
needRelayout = true
|
||||||
}
|
}
|
||||||
|
|
||||||
def removeFirst(count: Int): Unit = {
|
def removeFirst(count: Int): Unit = {
|
||||||
@ -56,7 +89,7 @@ abstract class LogWidget extends Widget {
|
|||||||
entries.dropInPlace(count)
|
entries.dropInPlace(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.get.recalculateBoundsAndRelayout()
|
needRelayout = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec
|
@tailrec
|
||||||
@ -72,15 +105,25 @@ abstract class LogWidget extends Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
private def visibleEntries: Iterator[Entry] = {
|
||||||
applyRemovedOffset()
|
|
||||||
|
|
||||||
val firstVisibleIdx = firstVisibleIdxSearch(parent.get.asInstanceOf[ScrollView].offset.y)
|
val firstVisibleIdx = firstVisibleIdxSearch(parent.get.asInstanceOf[ScrollView].offset.y)
|
||||||
|
entries.iterator.drop(firstVisibleIdx).takeWhile(_.absoluteBounds.y <= clippedBounds.max.y)
|
||||||
|
}
|
||||||
|
|
||||||
for (entry <- entries.iterator.drop(firstVisibleIdx).takeWhile(_.absoluteBounds.y <= clippedBounds.max.y)) {
|
override def update(): Unit = {
|
||||||
entry.draw(g)
|
if (needRelayout) {
|
||||||
|
recalculateBoundsAndRelayout()
|
||||||
|
needRelayout = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def draw(g: Graphics): Unit = {
|
||||||
|
applyRemovedOffset()
|
||||||
|
for (entry <- visibleEntries) {
|
||||||
|
entry.draw(g)
|
||||||
|
}
|
||||||
super.draw(g)
|
super.draw(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +143,7 @@ abstract class LogWidget extends Widget {
|
|||||||
final def absoluteBounds: Rect2D = Rect2D(position + messageList.position, size)
|
final def absoluteBounds: Rect2D = Rect2D(position + messageList.position, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class MessageEntry(message: String, override var y: Float) extends Entry {
|
private abstract class MessageEntry(val message: String, override var y: Float) extends Entry {
|
||||||
private val lines = Text.wrap(message, Some(textWidth))
|
private val lines = Text.wrap(message, Some(textWidth))
|
||||||
|
|
||||||
def minimumSize: Size2D = Size2D(
|
def minimumSize: Size2D = Size2D(
|
||||||
@ -203,12 +246,16 @@ object LogWidget {
|
|||||||
|
|
||||||
sealed trait LogEntry
|
sealed trait LogEntry
|
||||||
|
|
||||||
|
sealed trait TextLogEntry extends LogEntry {
|
||||||
|
val message: String
|
||||||
|
}
|
||||||
|
|
||||||
object LogEntry {
|
object LogEntry {
|
||||||
|
|
||||||
/** A message received from a log source. */
|
/** A message received from a log source. */
|
||||||
case class Rx(message: String) extends LogEntry
|
case class Rx(message: String) extends TextLogEntry
|
||||||
|
|
||||||
/** A message sent to a log source. */
|
/** A message sent to a log source. */
|
||||||
case class Tx(message: String) extends LogEntry
|
case class Tx(message: String) extends TextLogEntry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import ocelot.desktop.ui.event.{DoubleClickEvent, DragEvent, KeyEvent, MouseEven
|
|||||||
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
|
||||||
import ocelot.desktop.util.{DrawUtils, Register, Watcher}
|
import ocelot.desktop.util.{BaseWatcher, 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
|
||||||
|
|
||||||
@ -28,13 +28,13 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
// model
|
// model
|
||||||
private val _text: Text = new Text(initialText.codePoints().toArray)
|
private val _text: Text = new Text(initialText.codePoints().toArray)
|
||||||
private val cursor: Cursor = new Cursor()
|
private val cursor: Cursor = new Cursor()
|
||||||
private val selectionWatcher = new Watcher[Option[Selection]](None)
|
private val selectionWatcher = Watcher[Option[Selection]](None)
|
||||||
|
|
||||||
// updated after all events are processed so that event handlers can refer to the previous position.
|
// updated after all events are processed so that event handlers can refer to the previous position.
|
||||||
private val prevCursorPosition = Register.sampling(cursor.position)
|
private val prevCursorPosition = Register.sampling(cursor.position)
|
||||||
|
|
||||||
// view
|
// view
|
||||||
private var isFocused = false
|
protected var isFocused = false
|
||||||
private var scroll = 0f
|
private var scroll = 0f
|
||||||
private var blinkTimer = 0f
|
private var blinkTimer = 0f
|
||||||
private var cursorOffset = 0f
|
private var cursorOffset = 0f
|
||||||
@ -47,13 +47,13 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
selectionWatcher.value = newValue
|
selectionWatcher.value = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.onChange(position => {
|
cursor.onChange = { position =>
|
||||||
cursorOffset = charsWidth(_text.chars, 0, position)
|
cursorOffset = charsWidth(_text.chars, 0, position)
|
||||||
blinkTimer = 0
|
blinkTimer = 0
|
||||||
adjustScroll()
|
adjustScroll()
|
||||||
})
|
}
|
||||||
|
|
||||||
selectionWatcher.onChange(newValue => {
|
selectionWatcher.onChange = { newValue =>
|
||||||
selectionOffsets = newValue.map {
|
selectionOffsets = newValue.map {
|
||||||
case Selection.Ordered(start, end) =>
|
case Selection.Ordered(start, end) =>
|
||||||
(
|
(
|
||||||
@ -61,7 +61,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
charsWidth(_text.chars, 0, end),
|
charsWidth(_text.chars, 0, end),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
|
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
|
||||||
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
|
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
|
||||||
@ -81,7 +81,10 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
def text_=(value: String): Unit = {
|
def text_=(value: String): Unit = {
|
||||||
_text.chars = value.codePoints().toArray
|
_text.chars = value.codePoints().toArray
|
||||||
selection = None
|
selection = None
|
||||||
cursor.position = cursor.position max 0 min _text.chars.length
|
|
||||||
|
val desiredPosition = cursor.desiredPosition
|
||||||
|
cursor.position = desiredPosition max 0 min _text.chars.length
|
||||||
|
cursor.desiredPosition = desiredPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
private def selectedText: String = selection match {
|
private def selectedText: String = selection match {
|
||||||
@ -448,14 +451,20 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
}
|
}
|
||||||
|
|
||||||
object TextInput {
|
object TextInput {
|
||||||
class Text(initialValue: Array[Int]) extends Watcher(initialValue) {
|
class Text(initialValue: Array[Int]) extends BaseWatcher(initialValue) {
|
||||||
def chars: Array[Int] = value
|
def chars: Array[Int] = value
|
||||||
def chars_=(newValue: Array[Int]): Unit = value = newValue
|
def chars_=(newValue: Array[Int]): Unit = value = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
class Cursor(initialValue: Int = 0) extends Watcher(initialValue) {
|
class Cursor(initialValue: Int = 0) extends BaseWatcher(initialValue) {
|
||||||
|
var desiredPosition: Int = initialValue
|
||||||
|
|
||||||
def position: Int = value
|
def position: Int = value
|
||||||
def position_=(newValue: Int): Unit = value = newValue
|
|
||||||
|
def position_=(newValue: Int): Unit = {
|
||||||
|
value = newValue
|
||||||
|
desiredPosition = newValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Selection(start: Int, end: Int) {
|
case class Selection(start: Int, end: Int) {
|
||||||
|
|||||||
@ -9,29 +9,31 @@ class FPSCalculator {
|
|||||||
|
|
||||||
def fps: Float = _fps
|
def fps: Float = _fps
|
||||||
|
|
||||||
private var _skipFrame = false
|
private var _skipSecond = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Next tick will not count towards the overall statistics.
|
* Current second will not count towards the overall statistics.
|
||||||
|
* (In case the measurements were corrupted or distorted somehow.)
|
||||||
*/
|
*/
|
||||||
def skipFrame(): Unit = _skipFrame = true
|
def skipSecond(): Unit = _skipSecond = true
|
||||||
|
|
||||||
var dt: Float = 0
|
var dt: Float = 0
|
||||||
|
|
||||||
def tick(): Unit = {
|
def tick(): Unit = {
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
|
|
||||||
if (!_skipFrame) {
|
dt = (currentTime - prevFrameTime) / 1000f
|
||||||
dt = (currentTime - prevFrameTime) / 1000f
|
numFrames += 1
|
||||||
|
|
||||||
numFrames += 1
|
if (currentTime - prevTime > 1000) {
|
||||||
|
val delta = currentTime - prevTime
|
||||||
if (currentTime - prevTime > 1000) {
|
prevTime = currentTime
|
||||||
val delta = currentTime - prevTime
|
if (!_skipSecond) {
|
||||||
prevTime = currentTime
|
_fps = numFrames.toFloat / delta * 1000.0f
|
||||||
_fps = numFrames.asInstanceOf[Float] / delta * 1000f
|
} else {
|
||||||
numFrames = 0
|
_skipSecond = false
|
||||||
}
|
}
|
||||||
|
numFrames = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
prevFrameTime = currentTime
|
prevFrameTime = currentTime
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package ocelot.desktop.util
|
|||||||
|
|
||||||
import ocelot.desktop.entity.traits.OcelotInterface
|
import ocelot.desktop.entity.traits.OcelotInterface
|
||||||
import ocelot.desktop.ui.event.{BrainEvent, EventAware}
|
import ocelot.desktop.ui.event.{BrainEvent, EventAware}
|
||||||
import ocelot.desktop.ui.widget.LogWidget.LogEntry
|
import ocelot.desktop.ui.widget.LogWidget.{LogEntry, TextLogEntry}
|
||||||
import ocelot.desktop.ui.widget.window.Windowed
|
import ocelot.desktop.ui.widget.window.Windowed
|
||||||
import ocelot.desktop.util.OcelotInterfaceLogStorage._
|
import ocelot.desktop.util.OcelotInterfaceLogStorage._
|
||||||
import ocelot.desktop.windows.OcelotInterfaceWindow
|
import ocelot.desktop.windows.OcelotInterfaceWindow
|
||||||
@ -121,6 +121,16 @@ trait OcelotInterfaceLogStorage extends EventAware with Persistable with Windowe
|
|||||||
window.onMessagesRemoved(count)
|
window.onMessagesRemoved(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getEntry(index: Int): LogEntry = _entries(index)
|
||||||
|
|
||||||
|
def findFirstTextEntry(exclude: String, start: Int): Int = {
|
||||||
|
_entries.indexWhere(it => it.isInstanceOf[TextLogEntry] && it.asInstanceOf[TextLogEntry].message != exclude, start)
|
||||||
|
}
|
||||||
|
|
||||||
|
def findLastTextEntry(exclude: String, end: Int): Int = {
|
||||||
|
_entries.lastIndexWhere(it => it.isInstanceOf[TextLogEntry] && it.asInstanceOf[TextLogEntry].message != exclude, end)
|
||||||
|
}
|
||||||
|
|
||||||
private def addEntry(entry: LogEntry): Unit = {
|
private def addEntry(entry: LogEntry): Unit = {
|
||||||
ensureFreeSpace(1)
|
ensureFreeSpace(1)
|
||||||
_entries += entry
|
_entries += entry
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
package ocelot.desktop.util
|
package ocelot.desktop.util
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps a reference to an object
|
* Keeps a value and tracks its changes.
|
||||||
* and tells whether there were any changes to the value since the last check.
|
|
||||||
*/
|
*/
|
||||||
class Watcher[T](initialValue: T) {
|
abstract class BaseWatcher[T](initialValue: T) {
|
||||||
private var dirty = false
|
private var dirty = false
|
||||||
private var _callback: Option[T => Unit] = None
|
private var _callback: Option[T => Unit] = None
|
||||||
private var _value: T = initialValue
|
private var _value: T = initialValue
|
||||||
|
|
||||||
def value: T = _value
|
protected def value: T = _value
|
||||||
def value_=(newValue: T): Unit = {
|
protected def value_=(newValue: T): Unit = {
|
||||||
dirty = _value != newValue
|
dirty = _value != newValue
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
_callback.foreach(_(newValue))
|
_callback.foreach(_(newValue))
|
||||||
@ -18,7 +17,11 @@ class Watcher[T](initialValue: T) {
|
|||||||
_value = newValue
|
_value = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
def onChange(callback: T => Unit): Unit = _callback = Some(callback)
|
def onChange: Option[T => Unit] = _callback
|
||||||
|
|
||||||
|
def onChange_=(callback: T => Unit): Unit = {
|
||||||
|
_callback = Some(callback)
|
||||||
|
}
|
||||||
|
|
||||||
def changed(): Boolean = {
|
def changed(): Boolean = {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
@ -29,5 +32,10 @@ class Watcher[T](initialValue: T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Watcher {
|
object Watcher {
|
||||||
def apply[T](value: T) = new Watcher(value)
|
def apply[T](initialValue: T) = new Watcher(initialValue)
|
||||||
|
|
||||||
|
class Watcher[T](initialValue: T) extends BaseWatcher(initialValue) {
|
||||||
|
override def value: T = super.value
|
||||||
|
override def value_=(newValue: T): Unit = super.value_=(newValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
package ocelot.desktop.windows
|
package ocelot.desktop.windows
|
||||||
|
|
||||||
import ocelot.desktop.geometry.{Padding2D, Size2D}
|
import ocelot.desktop.geometry.{Padding2D, Size2D}
|
||||||
|
import ocelot.desktop.ui.event.KeyEvent
|
||||||
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
|
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
|
||||||
import ocelot.desktop.ui.widget.LogWidget.LogEntry
|
import ocelot.desktop.ui.widget.LogWidget.{LogEntry, TextLogEntry}
|
||||||
import ocelot.desktop.ui.widget.window.PanelWindow
|
import ocelot.desktop.ui.widget.window.PanelWindow
|
||||||
import ocelot.desktop.ui.widget.{Button, Checkbox, Filler, Label, LogWidget, PaddingBox, TextInput, Widget}
|
import ocelot.desktop.ui.widget.{Button, Checkbox, Filler, Label, LogWidget, PaddingBox, TextInput, Widget}
|
||||||
import ocelot.desktop.util.{OcelotInterfaceLogStorage, Orientation}
|
import ocelot.desktop.util.{OcelotInterfaceLogStorage, Orientation}
|
||||||
|
import ocelot.desktop.windows.OcelotInterfaceWindow.ScrollToEndTag
|
||||||
|
import org.lwjgl.input.Keyboard
|
||||||
|
import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||||
|
|
||||||
class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWindow {
|
class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWindow {
|
||||||
override protected def title: String = s"${storage.name} ${storage.ocelotInterface.node.address}"
|
override protected def title: String = s"${storage.name} ${storage.ocelotInterface.node.address}"
|
||||||
@ -21,10 +25,36 @@ class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWin
|
|||||||
|
|
||||||
children :+= logWidget
|
children :+= logWidget
|
||||||
|
|
||||||
|
var historyPosition = 0
|
||||||
|
var unfinishedEntryBackup: Option[String] = None
|
||||||
|
|
||||||
children :+= new TextInput() {
|
children :+= new TextInput() {
|
||||||
|
eventHandlers += {
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_UP | Keyboard.KEY_DOWN, _) if isFocused =>
|
||||||
|
val currentInput = text
|
||||||
|
val index = if (event.code == Keyboard.KEY_UP) {
|
||||||
|
storage.findLastTextEntry(currentInput, storage.entryCount - historyPosition)
|
||||||
|
} else {
|
||||||
|
storage.findFirstTextEntry(currentInput, storage.entryCount - historyPosition + 1)
|
||||||
|
}
|
||||||
|
if (index != -1) {
|
||||||
|
if (unfinishedEntryBackup.isEmpty) {
|
||||||
|
unfinishedEntryBackup = Some(currentInput)
|
||||||
|
}
|
||||||
|
historyPosition = storage.entryCount - index
|
||||||
|
text = storage.getEntry(index).asInstanceOf[TextLogEntry].message
|
||||||
|
event.consume()
|
||||||
|
} else if (event.code == Keyboard.KEY_DOWN && unfinishedEntryBackup.nonEmpty) {
|
||||||
|
text = unfinishedEntryBackup.get
|
||||||
|
unfinishedEntryBackup = None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def onConfirm(): Unit = {
|
override def onConfirm(): Unit = {
|
||||||
pushLine(text)
|
pushLine(text)
|
||||||
text = ""
|
text = ""
|
||||||
|
unfinishedEntryBackup = None
|
||||||
|
historyPosition = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +105,11 @@ class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWin
|
|||||||
|
|
||||||
children :+= new Button {
|
children :+= new Button {
|
||||||
override def text: String = "Clear"
|
override def text: String = "Clear"
|
||||||
override def onClick(): Unit = clear()
|
override def onClick(): Unit = {
|
||||||
|
unfinishedEntryBackup = None
|
||||||
|
historyPosition = 0
|
||||||
|
clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -95,4 +129,20 @@ class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWin
|
|||||||
def pushLine(line: String): Unit = {
|
def pushLine(line: String): Unit = {
|
||||||
storage.ocelotInterface.pushMessage(line)
|
storage.ocelotInterface.pushMessage(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def save(nbt: NBTTagCompound): Unit = {
|
||||||
|
super.save(nbt)
|
||||||
|
nbt.setBoolean(ScrollToEndTag, logWidget.scrollToEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def load(nbt: NBTTagCompound): Unit = {
|
||||||
|
super.load(nbt)
|
||||||
|
if (nbt.hasKey(ScrollToEndTag)) {
|
||||||
|
logWidget.scrollToEnd = nbt.getBoolean(ScrollToEndTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object OcelotInterfaceWindow {
|
||||||
|
private val ScrollToEndTag = "scrollToEnd"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user