diff --git a/src/main/scala/ocelot/desktop/OcelotDesktop.scala b/src/main/scala/ocelot/desktop/OcelotDesktop.scala index d202071..baf03d3 100644 --- a/src/main/scala/ocelot/desktop/OcelotDesktop.scala +++ b/src/main/scala/ocelot/desktop/OcelotDesktop.scala @@ -3,7 +3,7 @@ package ocelot.desktop import li.flor.nativejfilechooser.NativeJFileChooser import ocelot.desktop.audio.{Audio, SoundSource} import ocelot.desktop.ui.UiHandler -import ocelot.desktop.ui.widget.{NotificationDialog, ExitConfirmationDialog, RootWidget, SettingsDialog} +import ocelot.desktop.ui.widget.{AddPlayerDialog, ExitConfirmationDialog, NotificationDialog, RootWidget, SettingsDialog} import ocelot.desktop.util.FileUtils.getOcelotConfigDirectory import ocelot.desktop.util._ import org.apache.commons.io.FileUtils @@ -11,19 +11,23 @@ import org.apache.logging.log4j.LogManager import totoro.ocelot.brain.Ocelot import totoro.ocelot.brain.event.FileSystemActivityType.Floppy import totoro.ocelot.brain.event._ -import totoro.ocelot.brain.nbt.{CompressedStreamTools, NBTTagCompound} +import totoro.ocelot.brain.nbt.ExtendedNBT.{extendNBTTagCompound, extendNBTTagList} +import totoro.ocelot.brain.nbt.{CompressedStreamTools, NBT, NBTTagCompound, NBTTagString} +import totoro.ocelot.brain.user.User import totoro.ocelot.brain.workspace.Workspace import java.io._ import java.nio.file.{Files, Path, Paths} import java.util.concurrent.locks.{Lock, ReentrantLock} import javax.swing.JFileChooser +import scala.collection.mutable.ArrayBuffer import scala.io.Source import scala.jdk.CollectionConverters._ import scala.util.Random object OcelotDesktop extends Logging { var root: RootWidget = _ + val players: ArrayBuffer[User] = ArrayBuffer(User("myself")) val tpsCounter = new FPSCalculator val ticker = new Ticker @@ -109,6 +113,7 @@ object OcelotDesktop extends Logging { val frontendNBT = new NBTTagCompound workspace.save(backendNBT) root.workspaceView.save(frontendNBT) + frontendNBT.setNewTagList("players", players.map(player => new NBTTagString(player.nickname))) nbt.setTag("back", backendNBT) nbt.setTag("front", frontendNBT) }) @@ -116,9 +121,12 @@ object OcelotDesktop extends Logging { private def loadWorld(nbt: NBTTagCompound): Unit = withTickLockAcquired(() => { val backendNBT = nbt.getCompoundTag("back") val frontendNBT = nbt.getCompoundTag("front") - workspace.load(backendNBT) root.workspaceView.load(frontendNBT) + if (frontendNBT.hasKey("players")) { + players.clear() + players.addAll(frontendNBT.getTagList("players", NBT.TAG_STRING).map((player: NBTTagString) => User(player.getString))) + } }) private var savePath: Option[Path] = None @@ -218,6 +226,29 @@ object OcelotDesktop extends Logging { }).start() } + def addPlayerDialog(): Unit = { + UiHandler.root.modalDialogPool.pushDialog(new AddPlayerDialog()) + } + + def player: User = if (players.nonEmpty) players.head else User("myself") + + def selectPlayer(name: String): Unit = { + players.indexWhere(_.nickname == name) match { + case -1 => players.prepend(User(name)) + case i => + val player = players(i) + players.remove(i) + players.prepend(player) + } + } + + def removePlayer(name: String): Unit = { + players.indexWhere(_.nickname == name) match { + case -1 => + case i => players.remove(i) + } + } + def settings(): Unit = { UiHandler.root.modalDialogPool.pushDialog(new SettingsDialog()) } diff --git a/src/main/scala/ocelot/desktop/node/SetLabelDialog.scala b/src/main/scala/ocelot/desktop/node/SetLabelDialog.scala index b2890e4..9c6ea06 100644 --- a/src/main/scala/ocelot/desktop/node/SetLabelDialog.scala +++ b/src/main/scala/ocelot/desktop/node/SetLabelDialog.scala @@ -19,6 +19,7 @@ class SetLabelDialog(node: Node) extends ModalDialog { override def onInput(text: String): Unit = { node.label = if (text.isEmpty) None else Some(text) } + override def onConfirm(): Unit = close() }, Padding2D(bottom = 8)) children :+= new Widget { diff --git a/src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala b/src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala index 80497c8..66dfffb 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ScreenWindow.scala @@ -1,6 +1,6 @@ package ocelot.desktop.node.nodes -import ocelot.desktop.ColorScheme +import ocelot.desktop.{ColorScheme, OcelotDesktop} import ocelot.desktop.color.{IntColor, RGBAColor} import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics @@ -42,13 +42,13 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging { case event: KeyEvent if shouldHandleKeys => event.state match { case KeyEvent.State.Press | KeyEvent.State.Repeat => - screen.keyDown(event.char, event.code, User("myself")) + screen.keyDown(event.char, event.code, OcelotDesktop.player) // note: in OpenComputers, key_down signal is fired __before__ clipboard signal if (event.code == Keyboard.KEY_INSERT) - screen.clipboard(UiHandler.clipboard, User("myself")) + screen.clipboard(UiHandler.clipboard, OcelotDesktop.player) case KeyEvent.State.Release => - screen.keyUp(event.char, event.code, User("myself")) + screen.keyUp(event.char, event.code, OcelotDesktop.player) } case event: MouseEvent if shouldHandleKeys => @@ -60,15 +60,15 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging { event.state match { case MouseEvent.State.Press if inside && screenNode.screen.tier > Tier.One => - screen.mouseDown(pos.x, pos.y, event.button.id, User("myself")) + screen.mouseDown(pos.x, pos.y, event.button.id, OcelotDesktop.player) sentTouchEvent = true case MouseEvent.State.Release => if (event.button == MouseEvent.Button.Middle && inside) - screen.clipboard(UiHandler.clipboard, User("myself")) + screen.clipboard(UiHandler.clipboard, OcelotDesktop.player) if (sentTouchEvent) { - screen.mouseUp(lastMousePos.x, lastMousePos.y, event.button.id, User("myself")) + screen.mouseUp(lastMousePos.x, lastMousePos.y, event.button.id, OcelotDesktop.player) sentTouchEvent = false } else if (closeButtonBounds.contains(UiHandler.mousePosition)) { hide() @@ -78,7 +78,7 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging { } case event: ScrollEvent if shouldHandleKeys && screenNode.screen.tier > Tier.One => - screen.mouseScroll(lastMousePos.x, lastMousePos.y, event.offset, User("myself")) + screen.mouseScroll(lastMousePos.x, lastMousePos.y, event.offset, OcelotDesktop.player) case ev@DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, _) => if (scaleDragRegion.contains(ev.start)) { @@ -176,7 +176,7 @@ class ScreenWindow(screenNode: ScreenNode) extends BasicWindow with Logging { if (isFocused && screenNode.screen.tier > Tier.One) for (button <- MouseEvents.pressedButtons) { - screen.mouseDrag(lastMousePos.x, lastMousePos.y, button.id, User("myself")) + screen.mouseDrag(lastMousePos.x, lastMousePos.y, button.id, OcelotDesktop.player) } } diff --git a/src/main/scala/ocelot/desktop/ui/widget/AddPlayerDialog.scala b/src/main/scala/ocelot/desktop/ui/widget/AddPlayerDialog.scala new file mode 100644 index 0000000..93f301c --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/AddPlayerDialog.scala @@ -0,0 +1,42 @@ +package ocelot.desktop.ui.widget + +import ocelot.desktop.OcelotDesktop +import ocelot.desktop.geometry.Padding2D +import ocelot.desktop.ui.layout.LinearLayout +import ocelot.desktop.ui.widget.modal.ModalDialog +import ocelot.desktop.util.Orientation + +class AddPlayerDialog() extends ModalDialog { + var name = "" + + private def confirm(): Unit = { + OcelotDesktop.selectPlayer(name) + close() + } + + children :+= new PaddingBox(new Widget { + override val layout = new LinearLayout(this, orientation = Orientation.Vertical) + + children :+= new PaddingBox(new Label { + override def text = "Add new player" + }, Padding2D(bottom = 16)) + + children :+= new PaddingBox(new TextInput("") { + focus() + override def onInput(text: String): Unit = name = text + override def onConfirm(): Unit = confirm() + }, Padding2D(bottom = 8)) + + children :+= new Widget { + children :+= new Filler + children :+= new Button { + override def text: String = "Cancel" + override def onClick(): Unit = close() + } + children :+= new PaddingBox(new Button { + override def text: String = "Ok" + override def onClick(): Unit = confirm() + }, Padding2D(left = 8)) + } + }, Padding2D.equal(16)) +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala b/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala index 716eb53..662d07a 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/ChangeSimulationSpeedDialog.scala @@ -9,6 +9,12 @@ import ocelot.desktop.util.Orientation class ChangeSimulationSpeedDialog() extends ModalDialog { private var tickInterval: Option[Long] = Some(OcelotDesktop.ticker.tickInterval) + private def confirm(): Unit = { + if (tickInterval.isDefined) + OcelotDesktop.ticker.tickInterval = tickInterval.get + close() + } + children :+= new PaddingBox(new Widget { override val layout = new LinearLayout(this, orientation = Orientation.Vertical) @@ -42,6 +48,8 @@ class ChangeSimulationSpeedDialog() extends ModalDialog { case _: NumberFormatException => false } } + + override def onConfirm(): Unit = confirm() } inputTPS = new TextInput(formatTPS(OcelotDesktop.ticker.tickInterval)) { @@ -63,6 +71,8 @@ class ChangeSimulationSpeedDialog() extends ModalDialog { case _: NumberFormatException => false } } + + override def onConfirm(): Unit = confirm() } children :+= new Label { @@ -84,11 +94,7 @@ class ChangeSimulationSpeedDialog() extends ModalDialog { children :+= new Button { override def text: String = "Apply" - override def onClick(): Unit = { - if (tickInterval.isDefined) - OcelotDesktop.ticker.tickInterval = tickInterval.get - close() - } + override def onClick(): Unit = confirm() } } }, Padding2D.equal(16)) diff --git a/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala b/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala index 1de80bb..90b5ecb 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala @@ -2,7 +2,7 @@ package ocelot.desktop.ui.widget import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.graphics.Graphics -import ocelot.desktop.ui.widget.contextmenu.ContextMenuEntry +import ocelot.desktop.ui.widget.contextmenu.{ContextMenuEntry, ContextMenuSubmenu} import ocelot.desktop.{ColorScheme, OcelotDesktop} class MenuBar extends Widget { @@ -23,6 +23,19 @@ class MenuBar extends Widget { menu.addEntry(new ContextMenuEntry("Exit", () => OcelotDesktop.exit())) })) + addEntry(new MenuBarSubmenu("Player", menu => { + menu.addEntry(new ContextMenuEntry("Add...", () => OcelotDesktop.addPlayerDialog())) + menu.addSeparator() + OcelotDesktop.players.foreach(player => { + menu.addEntry(new ContextMenuSubmenu( + (if (player == OcelotDesktop.players.head) "● " else " ") + player.nickname, + () => OcelotDesktop.selectPlayer(player.nickname) + ) { + addEntry(new ContextMenuEntry("Remove", () => OcelotDesktop.removePlayer(player.nickname))) + }) + }) + })) + addEntry(new MenuBarButton("Settings", () => OcelotDesktop.settings())) addEntry(new Widget { diff --git a/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala b/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala index 6d8e9b9..f12f8b3 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/TextInput.scala @@ -15,6 +15,7 @@ import scala.collection.mutable.ArrayBuffer class TextInput(val initialText: String = "") extends Widget with ClickHandler { def onInput(text: String): Unit = {} + def onConfirm(): Unit = {} def isInputValid(text: String): Boolean = true var isFocused = false @@ -65,6 +66,9 @@ class TextInput(val initialText: String = "") extends Widget with ClickHandler { else events += new WriteChar(ch) + case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused => + onConfirm() + case KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, _, char) if isFocused => if (!char.isControl) events += new WriteChar(char) diff --git a/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenuSubmenu.scala b/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenuSubmenu.scala index 6a3a3bf..3ac623c 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenuSubmenu.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenuSubmenu.scala @@ -8,8 +8,9 @@ import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.event.HoverEvent class ContextMenuSubmenu(label: String, + onClick: () => Unit = null, icon: Option[IconDef] = None) - extends ContextMenuEntry(label + " ", icon = icon) + extends ContextMenuEntry(label + " ", if (onClick != null) onClick else () => {}, icon) { private val parentEntry = this private val submenu = new ContextMenu { @@ -28,7 +29,9 @@ class ContextMenuSubmenu(label: String, submenu.addSeparator() } - override def clicked(): Unit = {} + override def clicked(): Unit = { + if (onClick != null) super.clicked() + } override def leave(): Unit = { if (submenu.isClosed) super.leave() diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala index 6d2ac03..e7bb27c 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/slot/CPUSlot.scala @@ -41,7 +41,7 @@ class CPUSlot(owner: Inventory#Slot, node: ComputerNode, val tier: Int) extends if (tier < Tier.Two) return - menu.addEntry(new ContextMenuSubmenu("APU (CPU + GPU)", Some(new IconDef("items/GraphicsCard1"))) { + menu.addEntry(new ContextMenuSubmenu("APU (CPU + GPU)", icon = Some(new IconDef("items/GraphicsCard1"))) { addEntry(new ContextMenuEntry("Tier 2", () => item = new APU(Tier.One), icon = Some(new IconDef("items/APU0", animation = APUAnimation)))) diff --git a/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala b/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala index 2060a0e..74931a7 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/slot/CardSlot.scala @@ -39,7 +39,7 @@ class CardSlot(owner: Inventory#Slot, val tier: Int) extends InventorySlot[Entit if (nextEntry.isDefined && nextEntry.get.name == entry.name) { val groupName = entry.name if (tier >= entry.tier) { - menu.addEntry(new ContextMenuSubmenu(groupName, Some(nextEntry.get.icon)) { + menu.addEntry(new ContextMenuSubmenu(groupName, icon = Some(nextEntry.get.icon)) { entries.view.slice(i, entries.length).takeWhile(_.name == groupName).foreach(entry => { val name = if (entry.tier == Tier.Four) "Creative" else "Tier " + (entry.tier + 1) if (tier >= entry.tier) {