mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Pull ScreenView out of ScreenWindow
This commit is contained in:
parent
6babdcf6d8
commit
182d42a843
@ -460,7 +460,7 @@ object UiHandler extends Logging {
|
||||
ScrollEvents.events.foreach(dispatchEvent(scrollTarget))
|
||||
|
||||
for (event <- MouseEvents.events) {
|
||||
if (event.state == MouseEvent.State.Press) {
|
||||
if (event.state == MouseEvent.State.Pressed) {
|
||||
dispatchEvent(mouseTarget)(event)
|
||||
|
||||
// TODO: this should be done in the event capturing phase in [[Window]] itself.
|
||||
|
||||
@ -2,7 +2,7 @@ package ocelot.desktop.ui.event
|
||||
|
||||
object MouseEvent {
|
||||
object State extends Enumeration {
|
||||
val Press, Release = Value
|
||||
val Pressed, Released = Value
|
||||
}
|
||||
|
||||
object Button extends Enumeration {
|
||||
|
||||
@ -25,10 +25,10 @@ trait MouseHandler extends Widget {
|
||||
protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, button) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, button) =>
|
||||
startPositions += (button -> UiHandler.mousePosition)
|
||||
|
||||
case MouseEvent(MouseEvent.State.Release, button) =>
|
||||
case MouseEvent(MouseEvent.State.Released, button) =>
|
||||
val mousePos = UiHandler.mousePosition
|
||||
|
||||
val dragStopped = receiveDragEvents && dragButtons.remove(button)
|
||||
|
||||
@ -25,12 +25,12 @@ object MouseEvents {
|
||||
|
||||
if (MouseEvent.Button.values.map(_.id).contains(buttonIdx)) {
|
||||
val button = MouseEvent.Button(buttonIdx)
|
||||
val state = if (Mouse.getEventButtonState) MouseEvent.State.Press else MouseEvent.State.Release
|
||||
val state = if (Mouse.getEventButtonState) MouseEvent.State.Pressed else MouseEvent.State.Released
|
||||
_events += MouseEvent(state, button)
|
||||
state match {
|
||||
case MouseEvent.State.Press =>
|
||||
case MouseEvent.State.Pressed =>
|
||||
_pressedButtons += button
|
||||
case MouseEvent.State.Release =>
|
||||
case MouseEvent.State.Released =>
|
||||
_pressedButtons -= button
|
||||
}
|
||||
}
|
||||
@ -49,7 +49,7 @@ object MouseEvents {
|
||||
|
||||
def releaseButtons(): Unit = {
|
||||
for (button <- pressedButtons) {
|
||||
_events += MouseEvent(MouseEvent.State.Release, button)
|
||||
_events += MouseEvent(MouseEvent.State.Released, button)
|
||||
}
|
||||
|
||||
_pressedButtons.clear()
|
||||
|
||||
@ -26,7 +26,7 @@ class Button(tooltip: Option[Tooltip] = None) extends Widget with MouseHandler w
|
||||
override protected def receiveClickEvents: Boolean = true
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) if enabled =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) if enabled =>
|
||||
clickSoundSource.press.play()
|
||||
|
||||
case ClickEvent(MouseEvent.Button.Left, _) if enabled =>
|
||||
|
||||
@ -35,7 +35,7 @@ class IconButton(
|
||||
case HoverEvent(HoverEvent.State.Enter) => onHoverEnter()
|
||||
case HoverEvent(HoverEvent.State.Leave) => onHoverLeave()
|
||||
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) =>
|
||||
mode match {
|
||||
case Mode.Regular => handlePress()
|
||||
case _ => // the other modes are triggered on click.
|
||||
@ -43,7 +43,7 @@ class IconButton(
|
||||
|
||||
clickSoundSource.press.play()
|
||||
|
||||
case MouseEvent(MouseEvent.State.Release, MouseEvent.Button.Left) => mode match {
|
||||
case MouseEvent(MouseEvent.State.Released, MouseEvent.Button.Left) => mode match {
|
||||
case Mode.Regular if model.pressed =>
|
||||
handleRelease()
|
||||
clickSoundSource.release.play()
|
||||
|
||||
@ -28,14 +28,14 @@ abstract class Knob(dyeColor: DyeColor = DyeColor.Red) extends Widget {
|
||||
private var startValue: Int = 0
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) =>
|
||||
val mousePos = UiHandler.mousePosition
|
||||
if (bounds.contains(mousePos)) {
|
||||
movePos = Some(mousePos)
|
||||
startValue = input
|
||||
}
|
||||
|
||||
case MouseEvent(MouseEvent.State.Release, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Released, MouseEvent.Button.Left) =>
|
||||
movePos = None
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ class MenuBarButton(label: String, handler: () => Unit = () => {}) extends Widge
|
||||
def onMouseLeave(): Unit = colorAnimation.goto(ColorScheme("TitleBarBackground"))
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) =>
|
||||
clickSoundSource.press.play()
|
||||
|
||||
case ClickEvent(MouseEvent.Button.Left, _) =>
|
||||
|
||||
189
src/main/scala/ocelot/desktop/ui/widget/ScreenView.scala
Normal file
189
src/main/scala/ocelot/desktop/ui/widget/ScreenView.scala
Normal file
@ -0,0 +1,189 @@
|
||||
package ocelot.desktop.ui.widget
|
||||
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.graphics.Texture.MinFilteringMode
|
||||
import ocelot.desktop.node.nodes.ScreenNode
|
||||
import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth}
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.{KeyEvent, MouseEvent, ScrollEvent}
|
||||
import ocelot.desktop.ui.layout.Layout
|
||||
import ocelot.desktop.ui.widget.ScreenView.ScaleTag
|
||||
import ocelot.desktop.util.{Keybind, Persistable, Register}
|
||||
import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings}
|
||||
import org.lwjgl.input.Keyboard
|
||||
import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
|
||||
// TODO: use an interface instead of ScreenNode.
|
||||
abstract class ScreenView(screenNode: ScreenNode) extends Widget with Persistable {
|
||||
override protected val layout: Layout = new Layout(this)
|
||||
|
||||
private var lastMousePos = Vector2D.Zero
|
||||
|
||||
val scale = Register(1f)
|
||||
def scaleX: Float = (FontWidth * scale.value).floor / FontWidth
|
||||
def scaleY: Float = (FontHeight * scale.value).floor / FontHeight
|
||||
|
||||
private def screenWidth: Int = screenNode.screenWidth
|
||||
private def screenHeight: Int = screenNode.screenHeight
|
||||
|
||||
override def minimumSize: Size2D = Size2D(
|
||||
screenWidth * FontWidth * scaleX,
|
||||
screenHeight * FontHeight * scaleY,
|
||||
)
|
||||
|
||||
override def maximumSize: Size2D = minimumSize
|
||||
|
||||
private def toBufferCoords(p: Vector2D): Vector2D = {
|
||||
// no synchronization here (see the note in ScreenNode): the method to change this property is indirect.
|
||||
if (screenNode.screen.getPrecisionMode) {
|
||||
Vector2D(
|
||||
(p.x - position.x) / FontWidth / scaleX,
|
||||
(p.y - position.y) / FontHeight / scaleY,
|
||||
)
|
||||
} else {
|
||||
Vector2D(
|
||||
math.floor((p.x - position.x) / FontWidth / scaleX),
|
||||
math.floor((p.y - position.y) / FontHeight / scaleY),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def bufferCoordsInBounds(p: Vector2D): Boolean =
|
||||
new Rect2D(0, 0, screenWidth, screenHeight).contains(p)
|
||||
|
||||
private def mouseCoordsInBounds: Boolean =
|
||||
bufferCoordsInBounds(toBufferCoords(UiHandler.mousePosition))
|
||||
|
||||
override def receiveMouseEvents: Boolean = true
|
||||
|
||||
override def receiveScrollEvents: Boolean = true
|
||||
|
||||
protected def isFocused: Boolean
|
||||
|
||||
private def shouldHandleInput: Boolean = isFocused && !root.get.modalDialogPool.isVisible
|
||||
|
||||
// OC doesn't trigger several touch events in a row; the same holds for drop events.
|
||||
// For the following inputs:
|
||||
// LMB down, RMB down, RMB up, LMB up
|
||||
// ...OC only registers LMB down and RMB up, dropping the other two events.
|
||||
private var pressedButton: Option[MouseEvent.Button.Value] = None
|
||||
|
||||
// NOTE: events are handled before update().
|
||||
// if the brain initiates a viewport size change, mouse events could be sent for coordinates outside the new bounds.
|
||||
// TODO: look into how OpenComputers deals with that, if it does at all.
|
||||
eventHandlers += {
|
||||
case event: KeyEvent if shouldHandleInput && event.code != Keyboard.KEY_ESCAPE =>
|
||||
event.state match {
|
||||
case KeyEvent.State.Press | KeyEvent.State.Repeat =>
|
||||
screenNode.screen.keyDown(event.char, event.code, OcelotDesktop.player)
|
||||
|
||||
// note: in OpenComputers, key_down signal is fired __before__ clipboard signal
|
||||
if (event.code == Settings.get.keymap(Keybind.Insert)) {
|
||||
screenNode.screen.clipboard(UiHandler.clipboard, OcelotDesktop.player)
|
||||
}
|
||||
|
||||
case KeyEvent.State.Release =>
|
||||
screenNode.screen.keyUp(event.char, event.code, OcelotDesktop.player)
|
||||
}
|
||||
|
||||
event.consume()
|
||||
|
||||
case event: MouseEvent if shouldHandleInput =>
|
||||
val pos = toBufferCoords(UiHandler.mousePosition)
|
||||
val inBounds = bufferCoordsInBounds(pos)
|
||||
|
||||
if (inBounds) {
|
||||
lastMousePos = pos
|
||||
}
|
||||
|
||||
event.state match {
|
||||
case MouseEvent.State.Pressed if inBounds && screenNode.screen.tier > Tier.One =>
|
||||
if (pressedButton.isEmpty) {
|
||||
screenNode.screen.mouseDown(pos.x, pos.y, event.button.id, OcelotDesktop.player)
|
||||
}
|
||||
|
||||
pressedButton = Some(event.button)
|
||||
event.consume()
|
||||
|
||||
case MouseEvent.State.Released =>
|
||||
if (inBounds && event.button == MouseEvent.Button.Middle) {
|
||||
screenNode.screen.clipboard(UiHandler.clipboard, OcelotDesktop.player)
|
||||
event.consume()
|
||||
}
|
||||
|
||||
if (pressedButton.nonEmpty) {
|
||||
screenNode.screen.mouseUp(lastMousePos.x, lastMousePos.y, event.button.id, OcelotDesktop.player)
|
||||
pressedButton = None
|
||||
|
||||
if (inBounds) {
|
||||
event.consume()
|
||||
}
|
||||
}
|
||||
|
||||
case _ =>
|
||||
}
|
||||
|
||||
case event: ScrollEvent if mouseCoordsInBounds && shouldHandleInput && screenNode.screen.tier > Tier.One =>
|
||||
screenNode.screen.mouseScroll(lastMousePos.x, lastMousePos.y, event.offset, OcelotDesktop.player)
|
||||
event.consume()
|
||||
}
|
||||
|
||||
override def save(nbt: NBTTagCompound): Unit = {
|
||||
nbt.setFloat(ScaleTag, scale.value)
|
||||
super.save(nbt)
|
||||
}
|
||||
|
||||
override def load(nbt: NBTTagCompound): Unit = {
|
||||
super.load(nbt)
|
||||
scale.nextValue = nbt.getFloat(ScaleTag)
|
||||
}
|
||||
|
||||
private val screenSize = Register.sampling(Size2D(screenWidth, screenHeight))
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
|
||||
// NOTE: the single bar is intentional! both operands have to be evaluated.
|
||||
if (scale.update() | screenSize.update()) {
|
||||
recalculateBoundsAndRelayout()
|
||||
}
|
||||
|
||||
val mousePos = toBufferCoords(UiHandler.mousePosition)
|
||||
|
||||
if (bufferCoordsInBounds(mousePos) && mousePos != lastMousePos) {
|
||||
lastMousePos = mousePos
|
||||
|
||||
if (isFocused && screenNode.screen.tier > Tier.One) {
|
||||
for (button <- pressedButton) {
|
||||
screenNode.screen.mouseDrag(lastMousePos.x, lastMousePos.y, button.id, OcelotDesktop.player)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
// no synchronization here (see the note in ScreenNode): the methods to turn the screen on/off are indirect.
|
||||
if (screenNode.screen.getPowerState) {
|
||||
screenNode.drawScreenData(
|
||||
g,
|
||||
position.x,
|
||||
position.y,
|
||||
scaleX,
|
||||
scaleY,
|
||||
if (Settings.get.screenWindowMipmap) {
|
||||
MinFilteringMode.LinearMipmapLinear
|
||||
} else {
|
||||
MinFilteringMode.Nearest
|
||||
},
|
||||
)
|
||||
} else {
|
||||
g.rect(bounds, ColorScheme("ScreenOff"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ScreenView {
|
||||
private val ScaleTag: String = "scale"
|
||||
}
|
||||
@ -45,7 +45,7 @@ class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandle
|
||||
}
|
||||
}
|
||||
|
||||
case event: MouseEvent if event.state == MouseEvent.State.Press =>
|
||||
case event: MouseEvent if event.state == MouseEvent.State.Pressed =>
|
||||
val pos = UiHandler.mousePosition
|
||||
mouseOldPos = pos
|
||||
|
||||
@ -59,7 +59,7 @@ class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandle
|
||||
scrollToEnd = false
|
||||
}
|
||||
|
||||
case event: MouseEvent if event.state == MouseEvent.State.Release =>
|
||||
case event: MouseEvent if event.state == MouseEvent.State.Released =>
|
||||
dragging = 0
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ class Slider(var value: Float, val text: String, val snapPoints: Int = 0)
|
||||
}
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) =>
|
||||
clickSoundSource.press.play()
|
||||
|
||||
case ClickEvent(MouseEvent.Button.Left, pos) =>
|
||||
|
||||
@ -58,7 +58,7 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
||||
private var prevEnabled = enabled
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Release, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Released, MouseEvent.Button.Left) =>
|
||||
if (isFocused && !clippedBounds.contains(UiHandler.mousePosition)) {
|
||||
unfocus()
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ class ContextMenuEntry(
|
||||
override protected def receiveClickEvents: Boolean = true
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) if !contextMenu.isOpening =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) if !contextMenu.isOpening =>
|
||||
clickSoundSource.press.play()
|
||||
case ClickEvent(MouseEvent.Button.Left, _) if !contextMenu.isOpening => clicked()
|
||||
case HoverEvent(HoverEvent.State.Enter) => enter()
|
||||
|
||||
@ -27,7 +27,7 @@ class ContextMenus extends Widget {
|
||||
case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_ESCAPE, _) =>
|
||||
closeAll()
|
||||
|
||||
case MouseEvent(MouseEvent.State.Press, _) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, _) =>
|
||||
if (!menus.map(_.bounds).exists(_.contains(UiHandler.mousePosition))) closeAll()
|
||||
}
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ class VerticalMenuButton(icon: IconSource, label: String, handler: VerticalMenuB
|
||||
def onMouseLeave(): Unit = colorAnimation.goto(ColorScheme("VerticalMenuBackground"))
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, MouseEvent.Button.Left) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) =>
|
||||
clickSoundSource.press.play()
|
||||
|
||||
case ClickEvent(MouseEvent.Button.Left, _) =>
|
||||
|
||||
@ -53,9 +53,11 @@ trait BasicWindow extends Window {
|
||||
}
|
||||
}
|
||||
|
||||
protected def borderRenderer: DrawUtils.BorderRenderer = DrawUtils.windowBorder
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
beginDraw(g)
|
||||
DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 1f, 0.5f)
|
||||
DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 1f, 0.5f, borderRenderer)
|
||||
drawChildren(g)
|
||||
endDraw(g)
|
||||
}
|
||||
|
||||
@ -14,7 +14,11 @@ trait PanelWindow extends BasicWindow {
|
||||
|
||||
protected def titleMaxLength: Int = 32
|
||||
|
||||
def setInner(inner: Widget, padding: Padding2D = Padding2D(bottom = 13, left = 12, right = 12)): Unit = {
|
||||
def setInner(
|
||||
inner: Widget,
|
||||
padding: Padding2D = Padding2D(bottom = 13, left = 12, right = 12),
|
||||
titlePadding: Padding2D = Padding2D(top = 8, left = 12, right = 12, bottom = 2),
|
||||
): Unit = {
|
||||
children = ArraySeq.empty
|
||||
|
||||
children :+= new PaddingBox(
|
||||
@ -22,7 +26,7 @@ trait PanelWindow extends BasicWindow {
|
||||
override def title: String = PanelWindow.this.title
|
||||
override def titleMaxLength: Int = PanelWindow.this.titleMaxLength
|
||||
},
|
||||
Padding2D(top = 8, left = 12, right = 12, bottom = 2),
|
||||
titlePadding,
|
||||
)
|
||||
|
||||
children :+= new PaddingBox(inner, padding)
|
||||
|
||||
@ -30,7 +30,7 @@ trait Window extends Widget with Persistable with MouseHandler {
|
||||
override protected def receiveDragEvents: Boolean = true
|
||||
|
||||
eventHandlers += {
|
||||
case MouseEvent(MouseEvent.State.Press, _) =>
|
||||
case MouseEvent(MouseEvent.State.Pressed, _) =>
|
||||
focus()
|
||||
|
||||
case ev @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, mousePos) =>
|
||||
|
||||
@ -17,24 +17,24 @@ object DrawUtils {
|
||||
h: Float,
|
||||
color: Color = RGBAColor(255, 255, 255),
|
||||
): Unit = {
|
||||
g.sprite("screen/CornerTL", x - 16, y - 20, 16, 20, color)
|
||||
g.sprite("screen/CornerTR", x + w, y - 20, 16, 20, color)
|
||||
g.sprite("screen/CornerBL", x - 16, y + h, 16, 16, color)
|
||||
g.sprite("screen/CornerBR", x + w, y + h, 16, 16, color)
|
||||
g.sprite("screen/CornerTL", x, y, 16, 20, color)
|
||||
g.sprite("screen/CornerTR", x + w - 16, y, 16, 20, color)
|
||||
g.sprite("screen/CornerBL", x, y + h - 16, 16, 16, color)
|
||||
g.sprite("screen/CornerBR", x + w - 16, y + h - 16, 16, 16, color)
|
||||
|
||||
g.sprite("screen/BorderT", x, y - 20, w, 20, color)
|
||||
g.sprite("screen/BorderB", x, y + h, w, 16, color)
|
||||
g.sprite("screen/BorderT", x + 16, y, w - 16 - 16, 20, color)
|
||||
g.sprite("screen/BorderB", x + 16, y + h - 16, w - 16 - 16, 16, color)
|
||||
|
||||
g.save()
|
||||
g.translate(x - 16, y)
|
||||
g.translate(x, y - 16)
|
||||
g.rotate(270.toRadians)
|
||||
g.sprite("screen/BorderB", -h, 0, h, 16, color)
|
||||
g.sprite("screen/BorderB", -h, 0, h - 20 - 16, 16, color)
|
||||
g.restore()
|
||||
|
||||
g.save()
|
||||
g.translate(x + w, y)
|
||||
g.translate(x + w - 16, y - 16)
|
||||
g.rotate(270.toRadians)
|
||||
g.sprite("screen/BorderB", -h, 0, h, 16, color)
|
||||
g.sprite("screen/BorderB", -h, 0, h - 20 - 16, 16, color)
|
||||
g.restore()
|
||||
}
|
||||
|
||||
@ -95,6 +95,8 @@ object DrawUtils {
|
||||
if (alpha < 1f) g.endGroupAlpha(alpha)
|
||||
}
|
||||
|
||||
type BorderRenderer = (Graphics, Float, Float, Float, Float, Color) => Unit
|
||||
|
||||
def windowWithShadow(
|
||||
g: Graphics,
|
||||
x: Float,
|
||||
@ -103,9 +105,10 @@ object DrawUtils {
|
||||
h: Float,
|
||||
backgroundAlpha: Float,
|
||||
shadowAlpha: Float,
|
||||
borderRenderer: BorderRenderer = windowBorder
|
||||
): Unit = {
|
||||
DrawUtils.shadow(g, x - 8, y - 8, w + 16, h + 20, shadowAlpha)
|
||||
DrawUtils.windowBorder(g, x, y, w, h, RGBAColorNorm(1, 1, 1, backgroundAlpha))
|
||||
borderRenderer(g, x, y, w, h, RGBAColorNorm(1, 1, 1, backgroundAlpha))
|
||||
}
|
||||
|
||||
def ring(
|
||||
|
||||
64
src/main/scala/ocelot/desktop/util/Register.scala
Normal file
64
src/main/scala/ocelot/desktop/util/Register.scala
Normal file
@ -0,0 +1,64 @@
|
||||
package ocelot.desktop.util
|
||||
|
||||
import ocelot.desktop.ui.widget.Updatable
|
||||
|
||||
/**
|
||||
* Stores a value updated by calls to [[update]].
|
||||
*/
|
||||
trait Register[T] {
|
||||
/**
|
||||
* The currently stored value.
|
||||
*/
|
||||
def value: T
|
||||
|
||||
/**
|
||||
* Updates the stored value.
|
||||
*
|
||||
* @return `true` if the value has changed.
|
||||
*/
|
||||
def update(): Boolean
|
||||
}
|
||||
|
||||
object Register {
|
||||
class Writeable[T](initialValue: T) extends Register[T] {
|
||||
private var _value: T = initialValue
|
||||
override def value: T = _value
|
||||
|
||||
/**
|
||||
* The value this register will be set to on next update.
|
||||
*/
|
||||
var nextValue: T = _value
|
||||
|
||||
override def update(): Boolean = {
|
||||
val changed = nextValue != _value
|
||||
_value = nextValue
|
||||
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
class Sampling[T](next: () => T) extends Register[T] {
|
||||
private var _value = next()
|
||||
override def value: T = _value
|
||||
|
||||
override def update(): Boolean = {
|
||||
val nextValue = next()
|
||||
val changed = nextValue != _value
|
||||
_value = nextValue
|
||||
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [[Register.Writeable writeable register]] with the given initial value.
|
||||
*/
|
||||
def apply[T](initialValue: T): Writeable[T] = new Writeable(initialValue)
|
||||
|
||||
/**
|
||||
* Creates a [[Register.Sampling register]] that reevaluates the provided expression on every update.
|
||||
*
|
||||
* The expression is evaluated to compute the initial value.
|
||||
*/
|
||||
def sampling[T](nextValue: => T): Sampling[T] = new Sampling(() => nextValue)
|
||||
}
|
||||
@ -1,122 +1,59 @@
|
||||
package ocelot.desktop.windows
|
||||
|
||||
import ocelot.desktop.audio.SoundSource
|
||||
import ocelot.desktop.color.RGBAColor
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.graphics.Texture.MinFilteringMode
|
||||
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.node.nodes.ScreenNode
|
||||
import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth}
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.sources.{KeyEvents, MouseEvents}
|
||||
import ocelot.desktop.ui.event.{DragEvent, KeyEvent, MouseEvent, ScrollEvent}
|
||||
import ocelot.desktop.ui.widget.window.BasicWindow
|
||||
import ocelot.desktop.util.{DrawUtils, Keybind, Logging}
|
||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
||||
import ocelot.desktop.ui.event.{DragEvent, MouseEvent}
|
||||
import ocelot.desktop.ui.widget.ScreenView
|
||||
import ocelot.desktop.ui.widget.window.PanelWindow
|
||||
import ocelot.desktop.util.DrawUtils.BorderRenderer
|
||||
import ocelot.desktop.util.{DrawUtils, Logging}
|
||||
import ocelot.desktop.windows.ScreenWindow._
|
||||
import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings}
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import totoro.ocelot.brain.entity.Screen
|
||||
import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
|
||||
class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
private var lastMousePos = Vector2D(0, 0)
|
||||
private var sentTouchEvent = false
|
||||
class ScreenWindow(screenNode: ScreenNode) extends PanelWindow with Logging {
|
||||
private var startingWidth = 0f
|
||||
private var scaleDragPoint: Option[Vector2D] = None
|
||||
|
||||
private var _scale = 1f
|
||||
private var scaleX: Float = 1f
|
||||
private var scaleY: Float = 1f
|
||||
override protected def title: String = screenNode.label.get
|
||||
|
||||
private def scale: Float = _scale
|
||||
|
||||
private def scale_=(value: Float): Any = {
|
||||
_scale = value
|
||||
|
||||
scaleX = (FontWidth * scale).floor / FontWidth
|
||||
scaleY = (FontHeight * scale).floor / FontHeight
|
||||
}
|
||||
|
||||
private def screen: Screen = screenNode.screen
|
||||
override protected def titleMaxLength: Int =
|
||||
((screenWidth * FontWidth * View.scaleX - 15) / FontWidth).toInt
|
||||
|
||||
private def screenWidth: Int = screenNode.screenWidth
|
||||
|
||||
private def screenHeight: Int = screenNode.screenHeight
|
||||
|
||||
override def minimumSize: Size2D = Size2D(
|
||||
screenWidth * FontWidth * scaleX + BorderHorizontal,
|
||||
screenHeight * scaleY * FontHeight + BorderVertical,
|
||||
)
|
||||
|
||||
override def receiveScrollEvents: Boolean = true
|
||||
|
||||
override def maximumSize: Size2D = minimumSize
|
||||
|
||||
private object View extends ScreenView(screenNode) {
|
||||
override protected def isFocused: Boolean = ScreenWindow.this.isFocused
|
||||
}
|
||||
|
||||
setInner(
|
||||
View,
|
||||
padding = Padding2D(
|
||||
right = BorderRight,
|
||||
bottom = BorderBottom,
|
||||
left = BorderLeft,
|
||||
),
|
||||
titlePadding = Padding2D(
|
||||
top = 2,
|
||||
right = BorderRight - 4,
|
||||
bottom = 2,
|
||||
left = BorderLeft - 4,
|
||||
),
|
||||
)
|
||||
|
||||
eventHandlers += {
|
||||
case event: KeyEvent if shouldHandleKeys =>
|
||||
event.state match {
|
||||
case KeyEvent.State.Press | KeyEvent.State.Repeat =>
|
||||
screen.keyDown(event.char, event.code, OcelotDesktop.player)
|
||||
|
||||
// note: in OpenComputers, key_down signal is fired __before__ clipboard signal
|
||||
if (event.code == Settings.get.keymap(Keybind.Insert))
|
||||
screen.clipboard(UiHandler.clipboard, OcelotDesktop.player)
|
||||
|
||||
case KeyEvent.State.Release =>
|
||||
screen.keyUp(event.char, event.code, OcelotDesktop.player)
|
||||
}
|
||||
|
||||
event.consume()
|
||||
|
||||
case event: MouseEvent if shouldHandleKeys =>
|
||||
val pos = convertMousePos(UiHandler.mousePosition)
|
||||
val inside = checkBounds(pos)
|
||||
|
||||
if (inside)
|
||||
lastMousePos = pos
|
||||
|
||||
event.state match {
|
||||
case MouseEvent.State.Press =>
|
||||
if (inside && screen.tier > Tier.One) {
|
||||
screen.mouseDown(pos.x, pos.y, event.button.id, OcelotDesktop.player)
|
||||
sentTouchEvent = true
|
||||
event.consume()
|
||||
}
|
||||
|
||||
if (
|
||||
pinButtonBounds.contains(UiHandler.mousePosition) || closeButtonBounds.contains(UiHandler.mousePosition)
|
||||
) {
|
||||
SoundSource.InterfaceClick.press.play()
|
||||
}
|
||||
|
||||
case MouseEvent.State.Release =>
|
||||
if (event.button == MouseEvent.Button.Middle && inside) {
|
||||
screen.clipboard(UiHandler.clipboard, OcelotDesktop.player)
|
||||
event.consume()
|
||||
}
|
||||
|
||||
if (sentTouchEvent) {
|
||||
screen.mouseUp(lastMousePos.x, lastMousePos.y, event.button.id, OcelotDesktop.player)
|
||||
event.consume()
|
||||
sentTouchEvent = false
|
||||
} else if (pinButtonBounds.contains(UiHandler.mousePosition)) {
|
||||
if (isPinned) unpin() else pin()
|
||||
SoundSource.InterfaceClick.release.play()
|
||||
} else if (closeButtonBounds.contains(UiHandler.mousePosition)) {
|
||||
close()
|
||||
SoundSource.InterfaceClick.release.play()
|
||||
}
|
||||
|
||||
case _ =>
|
||||
}
|
||||
|
||||
case event: ScrollEvent if shouldHandleKeys && screen.tier > Tier.One =>
|
||||
screen.mouseScroll(lastMousePos.x, lastMousePos.y, event.offset, OcelotDesktop.player)
|
||||
event.consume()
|
||||
|
||||
case event @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, _) =>
|
||||
if (scaleDragRegion.contains(event.start)) {
|
||||
scaleDragPoint = Some(event.start)
|
||||
startingWidth = screenWidth * FontWidth * scaleX
|
||||
startingWidth = screenWidth * FontWidth * View.scaleX
|
||||
}
|
||||
|
||||
case DragEvent(DragEvent.State.Drag, MouseEvent.Button.Left, mousePos) =>
|
||||
@ -125,7 +62,7 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
val sy = point.y - mousePos.y
|
||||
|
||||
val uiScale = UiHandler.scalingFactor
|
||||
var newScale = scale
|
||||
var newScale = View.scale.nextValue
|
||||
|
||||
// TODO: refactor this mess, make it consider both sizes and not have two nearby slightly off "snap points"
|
||||
|
||||
@ -134,7 +71,7 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
val maxWidth = screenWidth * FontWidth
|
||||
var midScale = (newWidth / maxWidth).max(0f)
|
||||
|
||||
if (!KeyEvents.isShiftDown && scale <= 1.001)
|
||||
if (!KeyEvents.isShiftDown && View.scale.nextValue <= 1.001)
|
||||
midScale = midScale.min(1f)
|
||||
|
||||
val lowScale = (FontWidth * midScale * uiScale).floor / FontWidth / uiScale
|
||||
@ -146,7 +83,7 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
val maxHeight = screenHeight * FontHeight
|
||||
var midScale = (newHeight / maxHeight).max(0f)
|
||||
|
||||
if (!KeyEvents.isShiftDown && scale <= 1.001)
|
||||
if (!KeyEvents.isShiftDown && View.scale.nextValue <= 1.001)
|
||||
midScale = midScale.min(1f)
|
||||
|
||||
val lowScale = (FontHeight * midScale * uiScale).floor / FontHeight / uiScale
|
||||
@ -155,28 +92,30 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
newScale = if (midScale - lowScale > highScale - midScale) highScale else lowScale
|
||||
}
|
||||
|
||||
if (newScale != scale)
|
||||
scale = newScale
|
||||
|
||||
// enforce minimal screen size
|
||||
if (scale <= 0.249f) {
|
||||
scale = 0.25f
|
||||
if (newScale <= 0.249f) {
|
||||
newScale = 0.25f
|
||||
}
|
||||
|
||||
View.scale.nextValue = newScale
|
||||
}
|
||||
|
||||
case DragEvent(DragEvent.State.Stop, MouseEvent.Button.Left, _) =>
|
||||
scaleDragPoint = None
|
||||
}
|
||||
|
||||
private def shouldHandleKeys: Boolean = isFocused && !root.get.modalDialogPool.isVisible
|
||||
|
||||
override def save(nbt: NBTTagCompound): Unit = {
|
||||
nbt.setFloat("scale", scale)
|
||||
View.save(nbt)
|
||||
super.save(nbt)
|
||||
}
|
||||
|
||||
override def load(nbt: NBTTagCompound): Unit = {
|
||||
super.load(nbt)
|
||||
View.load(nbt)
|
||||
}
|
||||
|
||||
override def fitToCenter(): Unit = {
|
||||
scale = math.min(
|
||||
View.scale.nextValue = math.min(
|
||||
((UiHandler.root.width * 0.9f) / (screenWidth * FontWidth + BorderHorizontal)).min(1f).max(0f),
|
||||
((UiHandler.root.height * 0.9f) / (screenHeight * FontHeight + BorderVertical)).min(1f).max(0f),
|
||||
)
|
||||
@ -184,29 +123,6 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
super.fitToCenter()
|
||||
}
|
||||
|
||||
override def load(nbt: NBTTagCompound): Unit = {
|
||||
scale = nbt.getFloat("scale")
|
||||
|
||||
super.load(nbt)
|
||||
}
|
||||
|
||||
private def checkBounds(p: Vector2D): Boolean = p.x >= 0 && p.y >= 0 && p.x < screenWidth && p.y < screenHeight
|
||||
|
||||
private def convertMousePos(p: Vector2D): Vector2D = {
|
||||
// no synchronization here (see the note in ScreenNode): the method to change this property is indirect.
|
||||
if (screen.getPrecisionMode) {
|
||||
Vector2D(
|
||||
(p.x - BorderLeft - position.x) / FontWidth / scaleX,
|
||||
(p.y - BorderTop - position.y) / FontHeight / scaleY,
|
||||
)
|
||||
} else {
|
||||
Vector2D(
|
||||
math.floor((p.x - BorderLeft - position.x) / FontWidth / scaleX),
|
||||
math.floor((p.y - BorderTop - position.y) / FontHeight / scaleY),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override protected def dragRegions: Iterator[Rect2D] = Iterator(
|
||||
Rect2D(position.x, position.y, size.width, BorderTop.toFloat),
|
||||
Rect2D(position.x, position.y, BorderLeft.toFloat, size.height),
|
||||
@ -230,82 +146,9 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging {
|
||||
root.get.statusBar.addMouseEntry("icons/DragLMB", "Scale screen")
|
||||
root.get.statusBar.addKeyMouseEntry("icons/DragLMB", "SHIFT", "Scale screen (magnify)")
|
||||
}
|
||||
|
||||
val currentMousePos = convertMousePos(UiHandler.mousePosition)
|
||||
if (!checkBounds(currentMousePos) || currentMousePos == lastMousePos) return
|
||||
|
||||
lastMousePos = currentMousePos
|
||||
|
||||
if (isFocused && screen.tier > Tier.One) {
|
||||
for (button <- MouseEvents.pressedButtons) {
|
||||
screen.mouseDrag(lastMousePos.x, lastMousePos.y, button.id, OcelotDesktop.player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def pinButtonBounds: Rect2D = Rect2D(
|
||||
position.x + screenWidth * FontWidth * scaleX - 13,
|
||||
position.y + 3,
|
||||
14,
|
||||
14,
|
||||
)
|
||||
|
||||
private def closeButtonBounds: Rect2D = Rect2D(
|
||||
position.x + screenWidth * FontWidth * scaleX + 2,
|
||||
position.y + 3,
|
||||
15,
|
||||
14,
|
||||
)
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
beginDraw(g)
|
||||
|
||||
val startX = position.x + BorderLeft
|
||||
val startY = position.y + BorderTop
|
||||
val windowWidth = screenWidth * FontWidth * scaleX
|
||||
val windowHeight = screenHeight * FontHeight * scaleY
|
||||
|
||||
DrawUtils.shadow(g, startX - 22, startY - 22, windowWidth + 44, windowHeight + 52, 0.5f)
|
||||
DrawUtils.screenBorder(g, startX, startY, windowWidth, windowHeight)
|
||||
|
||||
// no synchronization here (see the note in ScreenNode): the methods to turn the screen on/off are indirect.
|
||||
if (screen.getPowerState) {
|
||||
screenNode.drawScreenData(
|
||||
g,
|
||||
startX,
|
||||
startY,
|
||||
scaleX,
|
||||
scaleY,
|
||||
if (Settings.get.screenWindowMipmap) {
|
||||
MinFilteringMode.LinearMipmapLinear
|
||||
} else {
|
||||
MinFilteringMode.Nearest
|
||||
},
|
||||
)
|
||||
|
||||
} else {
|
||||
g.rect(startX, startY, windowWidth, windowHeight, ColorScheme("ScreenOff"))
|
||||
}
|
||||
|
||||
g.setSmallFont()
|
||||
g.background = RGBAColor(0, 0, 0, 0)
|
||||
g.foreground = RGBAColor(110, 110, 110)
|
||||
|
||||
val freeSpace = ((windowWidth - 15) / 8).toInt
|
||||
val label = screenNode.label.get
|
||||
val text = if (label.length <= freeSpace)
|
||||
label
|
||||
else
|
||||
StringUtils.substring(label, 0, (freeSpace - 1).max(0).min(label.length)) + "…"
|
||||
|
||||
g.text(startX - 4, startY - 14, text)
|
||||
g.setNormalFont()
|
||||
|
||||
g.sprite(if (isPinned) "icons/Unpin" else "icons/Pin", pinButtonBounds)
|
||||
g.sprite("icons/Close", closeButtonBounds)
|
||||
|
||||
endDraw(g)
|
||||
}
|
||||
override protected def borderRenderer: BorderRenderer = DrawUtils.screenBorder
|
||||
}
|
||||
|
||||
object ScreenWindow {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user