diff --git a/spritepack/spritepack.sh b/spritepack/spritepack.sh old mode 100644 new mode 100755 diff --git a/sprites/icons/SettingsKeymap.png b/sprites/icons/SettingsKeymap.png new file mode 100644 index 0000000..d4414ae Binary files /dev/null and b/sprites/icons/SettingsKeymap.png differ diff --git a/src/main/resources/ocelot/desktop/colorscheme.txt b/src/main/resources/ocelot/desktop/colorscheme.txt index 1878c16..48df2b4 100644 --- a/src/main/resources/ocelot/desktop/colorscheme.txt +++ b/src/main/resources/ocelot/desktop/colorscheme.txt @@ -23,6 +23,7 @@ Tier3 = #c354cd Label = #333333 LabelError = #aa0000 +LabelDisabled = #888888 Scrollbar = #e5e5e526 ScrollbarThumb = #cc3f72 diff --git a/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.png b/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.png index bf5252f..fc9f9e2 100644 Binary files a/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.png and b/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.png differ diff --git a/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.txt b/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.txt index 213b10d..553dd02 100644 --- a/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.txt +++ b/src/main/resources/ocelot/desktop/images/spritesheet/spritesheet.txt @@ -65,7 +65,7 @@ icons/Help 559 618 16 16 icons/Home 214 567 22 22 icons/Keyboard 576 618 16 16 icons/KeyboardOff 593 618 16 16 -icons/LMB 350 655 11 14 +icons/LMB 363 655 11 14 icons/Label 610 618 16 16 icons/LinesHorizontal 627 618 16 16 icons/Link 644 618 16 16 @@ -73,26 +73,27 @@ icons/LinkSlash 661 618 16 16 icons/Memory 678 618 16 16 icons/Microchip 695 618 16 16 icons/NA 712 618 16 16 -icons/NotificationError 374 655 11 11 -icons/NotificationInfo 386 655 11 11 -icons/NotificationWarning 398 655 11 11 +icons/NotificationError 387 655 11 11 +icons/NotificationInfo 399 655 11 11 +icons/NotificationWarning 411 655 11 11 icons/Ocelot 729 618 16 16 -icons/Pin 305 655 14 14 +icons/Pin 318 655 14 14 icons/Plus 746 618 16 16 icons/Power 763 618 16 16 -icons/RMB 362 655 11 14 +icons/RMB 375 655 11 14 icons/Restart 780 618 16 16 icons/Save 797 618 16 16 icons/SaveAs 814 618 16 16 icons/Server 831 618 16 16 -icons/SettingsSound 266 655 12 17 -icons/SettingsSystem 279 655 12 17 -icons/SettingsUI 292 655 12 17 +icons/SettingsKeymap 266 655 12 17 +icons/SettingsSound 279 655 12 17 +icons/SettingsSystem 292 655 12 17 +icons/SettingsUI 305 655 12 17 icons/Tier0 848 618 16 16 icons/Tier1 865 618 16 16 icons/Tier2 882 618 16 16 icons/Tiers 899 618 16 16 -icons/Unpin 320 655 14 14 +icons/Unpin 333 655 14 14 icons/WaveLFSR 901 707 24 10 icons/WaveNoise 926 707 24 10 icons/WaveSawtooth 951 707 24 10 @@ -185,7 +186,7 @@ light-panel/Fill 207 560 2 2 light-panel/Vent 356 434 2 38 nodes/Cable 377 445 8 8 nodes/Camera 375 674 16 16 -nodes/Chest 335 655 14 14 +nodes/Chest 348 655 14 14 nodes/HologramProjector0 392 674 16 16 nodes/HologramProjector1 409 674 16 16 nodes/IronNoteBlock 426 674 16 16 diff --git a/src/main/scala/ocelot/desktop/graphics/IconSource.scala b/src/main/scala/ocelot/desktop/graphics/IconSource.scala index ecb83c1..cc013f8 100644 --- a/src/main/scala/ocelot/desktop/graphics/IconSource.scala +++ b/src/main/scala/ocelot/desktop/graphics/IconSource.scala @@ -149,6 +149,7 @@ object IconSource { )), ) + val SettingsKeymap: IconSource = IconSource("icons/SettingsKeymap") val SettingsSystem: IconSource = IconSource("icons/SettingsSystem") val SettingsSound: IconSource = IconSource("icons/SettingsSound") val SettingsUI: IconSource = IconSource("icons/SettingsUI") diff --git a/src/main/scala/ocelot/desktop/ui/UiHandler.scala b/src/main/scala/ocelot/desktop/ui/UiHandler.scala index 5bdfe27..5a693cd 100644 --- a/src/main/scala/ocelot/desktop/ui/UiHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/UiHandler.scala @@ -13,7 +13,7 @@ import ocelot.desktop.util._ import ocelot.desktop.{OcelotDesktop, Settings} import org.apache.commons.lang3.SystemUtils import org.lwjgl.BufferUtils -import org.lwjgl.input.{Keyboard, Mouse} +import org.lwjgl.input.Mouse import org.lwjgl.opengl._ import java.awt.Toolkit @@ -29,6 +29,7 @@ import scala.collection.mutable import scala.concurrent.duration.DurationInt import scala.util.Try + object UiHandler extends Logging { var root: RootWidget = _ var graphics: Graphics = _ @@ -386,7 +387,7 @@ object UiHandler extends Logging { // we are processing screenshots here to make sure that // the current frame was fully drawn on the screen, // but the update for the next frame yet not happened (if the window, for example, is to be resized) - if (KeyEvents.isReleased(Keyboard.KEY_F12)) { + if (KeyEvents.isReleased(Settings.get.keymap(Keybind.Screenshot))) { SoundSource.InterfaceShutter.play() val image = graphics.screenshot() root.flash.bang() diff --git a/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala b/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala index 9aabde2..24b8a73 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/MenuBar.scala @@ -3,12 +3,13 @@ package ocelot.desktop.ui.widget import ocelot.desktop.audio.SoundSource import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.graphics.{Graphics, IconSource} -import ocelot.desktop.ui.event.KeyEvent import ocelot.desktop.ui.widget.contextmenu.{ContextMenuEntry, ContextMenuSubmenu} import ocelot.desktop.ui.widget.help.{AboutDialog, UpdateCheckerDialog} import ocelot.desktop.ui.widget.settings.SettingsDialog +import ocelot.desktop.util.Keybind.{QuickLoad, QuickSave} +import ocelot.desktop.util.Keymap.Press import ocelot.desktop.{ColorScheme, OcelotDesktop} -import org.lwjgl.input.Keyboard + class MenuBar extends Widget { override def receiveMouseEvents: Boolean = true @@ -70,8 +71,8 @@ class MenuBar extends Widget { }) // fill remaining space eventHandlers += { - case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_F5, _) => OcelotDesktop.save() - case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_F9, _) => OcelotDesktop.showOpenDialog() + case Press(QuickSave) => OcelotDesktop.save() + case Press(QuickLoad) => OcelotDesktop.showOpenDialog() } override def draw(g: Graphics): Unit = { diff --git a/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala index bdc79a4..521bdc9 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/RootWidget.scala @@ -4,17 +4,18 @@ import ocelot.desktop.OcelotDesktop import ocelot.desktop.color.RGBAColor import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.UiHandler -import ocelot.desktop.ui.event.KeyEvent import ocelot.desktop.ui.layout.{CopyLayout, LinearLayout} import ocelot.desktop.ui.widget.contextmenu.ContextMenus import ocelot.desktop.ui.widget.itemdrag.DraggedItemPool import ocelot.desktop.ui.widget.modal.ModalDialogPool import ocelot.desktop.ui.widget.statusbar.StatusBar import ocelot.desktop.ui.widget.tooltip.TooltipPool +import ocelot.desktop.util.Keybind.{Fullscreen, ReloadWorkspace, UIDebug} +import ocelot.desktop.util.Keymap.Release import ocelot.desktop.util.{DrawUtils, Orientation} -import org.lwjgl.input.Keyboard import totoro.ocelot.brain.nbt.NBTTagCompound + class RootWidget(setupDefaultWorkspace: Boolean = true) extends Widget { override protected val layout = new CopyLayout(this) @@ -50,10 +51,10 @@ class RootWidget(setupDefaultWorkspace: Boolean = true) extends Widget { private var isDebugViewVisible = false eventHandlers += { - case KeyEvent(KeyEvent.State.Release, Keyboard.KEY_F1, _) => + case Release(UIDebug) => isDebugViewVisible = !isDebugViewVisible - case KeyEvent(KeyEvent.State.Release, Keyboard.KEY_F3, _) => + case Release(ReloadWorkspace) => OcelotDesktop.withTickLockAcquired { val backendNBT = new NBTTagCompound val frontendNBT = new NBTTagCompound @@ -63,7 +64,7 @@ class RootWidget(setupDefaultWorkspace: Boolean = true) extends Widget { UiHandler.root.workspaceView.load(frontendNBT) } - case KeyEvent(KeyEvent.State.Release, Keyboard.KEY_F11, _) => + case Release(Fullscreen) => UiHandler.fullScreen = !UiHandler.fullScreen } diff --git a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala index e040ae7..7ed4d7f 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala @@ -12,11 +12,12 @@ import ocelot.desktop.ui.event.sources.KeyEvents import ocelot.desktop.ui.layout.{CopyLayout, Layout} import ocelot.desktop.ui.widget.WorkspaceView.NodeLoadException import ocelot.desktop.ui.widget.window.{NodeSelector, ProfilerWindow, WindowPool} +import ocelot.desktop.util.Keybind.{Center, Profiler} +import ocelot.desktop.util.Keymap.Press import ocelot.desktop.util.ReflectionUtils.findUnaryConstructor import ocelot.desktop.util.animation.ValueAnimation import ocelot.desktop.util.{DrawUtils, Logging, Persistable} -import ocelot.desktop.{ColorScheme, OcelotDesktop} -import org.lwjgl.input.Keyboard +import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings} import totoro.ocelot.brain.entity.traits.{Environment, SidedEnvironment} import totoro.ocelot.brain.entity.{Case, Screen} import totoro.ocelot.brain.event.{EventBus, InventoryEvent, NodeEvent} @@ -28,6 +29,7 @@ import scala.collection.immutable.ArraySeq import scala.collection.{immutable, mutable} import scala.jdk.CollectionConverters._ + class WorkspaceView extends Widget with Persistable with MouseHandler with HoverHandler with Logging { @volatile var nodes: immutable.Seq[Node] = immutable.ArraySeq[Node]() @@ -262,10 +264,10 @@ class WorkspaceView extends Widget with Persistable with MouseHandler with Hover nodeSelector.close() } - case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_F4, _) => + case Press(Profiler) => profilerWindow.open() - case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) => + case Press(Center) => moveCameraOffset(-cameraOffset) } @@ -608,7 +610,7 @@ class WorkspaceView extends Widget with Persistable with MouseHandler with Hover nodes.foreach(_.update()) if (isHovered) { - root.get.statusBar.addKeyEntry("HOME", "Reset camera") + root.get.statusBar.addKeyEntry(Settings.get.keymap.name(Center), "Reset camera") } if (isHovered || nodes.exists(_.isHovered)) { diff --git a/src/main/scala/ocelot/desktop/ui/widget/settings/KeymapSettingsTab.scala b/src/main/scala/ocelot/desktop/ui/widget/settings/KeymapSettingsTab.scala new file mode 100644 index 0000000..879571c --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/widget/settings/KeymapSettingsTab.scala @@ -0,0 +1,76 @@ +package ocelot.desktop.ui.widget.settings + +import ocelot.desktop.color.Color +import ocelot.desktop.{ColorScheme, Settings} +import ocelot.desktop.geometry.{Padding2D, Size2D} +import ocelot.desktop.graphics.IconSource +import ocelot.desktop.ui.event.KeyEvent +import ocelot.desktop.ui.layout.LinearLayout +import ocelot.desktop.ui.widget.{Button, Label, PaddingBox, Widget} +import ocelot.desktop.util.Keybind.Keybind +import ocelot.desktop.util.{Keybind, Orientation} +import org.lwjgl.input.Keyboard + + +class KeymapSettingsTab extends SettingsTab { + override val layout = new LinearLayout(this, orientation = Orientation.Vertical, gap = 8) + + override val icon: IconSource = IconSource.SettingsKeymap + override val label: String = "Keymap" + + private def section(title: String, keybinds: Seq[Keybind]): Widget = new PaddingBox(new Widget { + override val layout = new LinearLayout(this, orientation = Orientation.Vertical) + + override def maximumSize: Size2D = minimumSize.copy(width = 257) + + children :+= new PaddingBox(new Label(title, small = true) { + override def maximumSize: Size2D = minimumSize + override def color: Color = ColorScheme("LabelDisabled") + }, Padding2D(top = 4, bottom = 8)) + + keybinds.foreach(keybind => { + children :+= new PaddingBox(new Widget { + children :+= new PaddingBox(new Label(keybind.description), Padding2D(top = 4, right = 8)) + children :+= new Button { + private var listening: Boolean = false + + eventHandlers += { + case KeyEvent(KeyEvent.State.Release, code, _) if listening => + Settings.get.keymap.set(keybind, code) + listening = false + } + + override def text: String = if (listening) "..." else Keyboard.getKeyName(Settings.get.keymap(keybind)) + override def onClick(): Unit = { + listening = !listening + } + } + }, Padding2D(bottom = 4)) + }) + }, Padding2D(bottom = 8)) + + children :+= new PaddingBox(new Widget { + children :+= new Widget { + override val layout = new LinearLayout(this, orientation = Orientation.Vertical) + children :+= section("OpenComputers", Seq( + Keybind.Insert, + )) + children :+= section("Ocelot", Seq( + Keybind.UIDebug, + Keybind.FPSCounter, + Keybind.ReloadWorkspace, + Keybind.Profiler, + Keybind.Fullscreen, + )) + } + children :+= new Widget { + override def maximumSize: Size2D = Size2D(16, 8) + } + children :+= section("Workspace", Seq( + Keybind.Center, + Keybind.QuickSave, + Keybind.QuickLoad, + Keybind.Screenshot, + )) + }, Padding2D(bottom = 8)) +} diff --git a/src/main/scala/ocelot/desktop/ui/widget/settings/SettingsDialog.scala b/src/main/scala/ocelot/desktop/ui/widget/settings/SettingsDialog.scala index 7d71513..2e83b87 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/settings/SettingsDialog.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/settings/SettingsDialog.scala @@ -16,6 +16,7 @@ class SettingsDialog extends ModalDialog { private val tabs = Seq( new UISettingsTab, new SoundSettingsTab, + new KeymapSettingsTab, new SystemSettingsTab, ) diff --git a/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala b/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala index e1f13ca..1eeea14 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/statusbar/StatusBar.scala @@ -5,17 +5,19 @@ import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.graphics.{Graphics, IconSource} import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.event.handlers.{HoverHandler, MouseHandler} -import ocelot.desktop.ui.event.{ClickEvent, HoverEvent, KeyEvent, MouseEvent} +import ocelot.desktop.ui.event.{ClickEvent, HoverEvent, MouseEvent} import ocelot.desktop.ui.layout.{AlignItems, LinearLayout} import ocelot.desktop.ui.widget._ import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} import ocelot.desktop.ui.widget.traits.HoverAnimation +import ocelot.desktop.util.Keybind.FPSCounter +import ocelot.desktop.util.Keymap.Press import ocelot.desktop.{ColorScheme, OcelotDesktop} -import org.lwjgl.input.Keyboard import scala.collection.immutable.ArraySeq import scala.concurrent.duration.DurationInt + class StatusBar extends Widget { override protected val layout: LinearLayout = new LinearLayout(this, alignItems = AlignItems.Center) @@ -28,7 +30,7 @@ class StatusBar extends Widget { private var showFPS = false eventHandlers += { - case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_F2, _) => + case Press(FPSCounter) => showFPS = !showFPS if (showFPS) { children :+= new PaddingBox( diff --git a/src/main/scala/ocelot/desktop/util/Keybind.scala b/src/main/scala/ocelot/desktop/util/Keybind.scala index 39e5248..443bf2c 100644 --- a/src/main/scala/ocelot/desktop/util/Keybind.scala +++ b/src/main/scala/ocelot/desktop/util/Keybind.scala @@ -6,6 +6,26 @@ package ocelot.desktop.util */ object Keybind extends Enumeration { - type Keybind = Value - val Insert: Keybind = Value + type Keybind = KeybindVal + + // OpenComputers + val Insert: Keybind = KeybindVal("Insert text") + + // Workspace + val Center: Keybind = KeybindVal("Center camera") + val QuickSave: Keybind = KeybindVal("Quick save") + val QuickLoad: Keybind = KeybindVal("Quick load") + val Screenshot: Keybind = KeybindVal("Save screenshot") + + // Ocelot + val UIDebug: Keybind = KeybindVal("UI Debug Mode") + val FPSCounter: Keybind = KeybindVal("FPS counter") + val ReloadWorkspace: Keybind = KeybindVal("Reload workspace") + val Profiler: Keybind = KeybindVal("Ocelot profiler") + val Fullscreen: Keybind = KeybindVal("Toggle fullscreen mode") + + protected class KeybindVal(val description: String) extends super.Val + private object KeybindVal { + def apply(description: String): KeybindVal = new KeybindVal(description) + } } diff --git a/src/main/scala/ocelot/desktop/util/Keymap.scala b/src/main/scala/ocelot/desktop/util/Keymap.scala index 8f08556..51d91cd 100644 --- a/src/main/scala/ocelot/desktop/util/Keymap.scala +++ b/src/main/scala/ocelot/desktop/util/Keymap.scala @@ -1,18 +1,37 @@ package ocelot.desktop.util import com.typesafe.config.{Config, ConfigValue, ConfigValueFactory} +import ocelot.desktop.Settings +import ocelot.desktop.ui.event.KeyEvent +import ocelot.desktop.ui.event.KeyEvent.State import ocelot.desktop.util.Keybind.Keybind import org.lwjgl.input.Keyboard import scala.collection.mutable import scala.jdk.CollectionConverters._ + class Keymap { // default mappings - val map: mutable.Map[Keybind, Int] = mutable.Map( - Keybind.Insert -> Keyboard.KEY_INSERT + val map: mutable.Map[Keybind.Value, Int] = mutable.Map( + // OpenComputers + Keybind.Insert -> Keyboard.KEY_INSERT, + + // Workspace + Keybind.Center -> Keyboard.KEY_HOME, + Keybind.QuickSave -> Keyboard.KEY_F5, + Keybind.QuickLoad -> Keyboard.KEY_F9, + Keybind.Screenshot -> Keyboard.KEY_F12, + + // Ocelot + Keybind.UIDebug -> Keyboard.KEY_F1, + Keybind.FPSCounter -> Keyboard.KEY_F2, + Keybind.ReloadWorkspace -> Keyboard.KEY_F3, + Keybind.Profiler -> Keyboard.KEY_F4, + Keybind.Fullscreen -> Keyboard.KEY_F11, ) + /** Retrieves the LWJGL keycode which is associated with the given keybind. * Will return `Keyboard.KEY_NONE` if the binding is not found. * @@ -32,7 +51,30 @@ class Keymap { * before the `set` operation was executed, or `None` if this keybind * was not defined in the keymap before. */ - def set(keybind: Keybind, key: Int): Option[Int] = map.put(keybind, key) + def set(keybind: Keybind.Value, key: Int): Option[Int] = map.put(keybind, key) + + /** + * Retrieves the name for a keyboard key associated with the given keybind. + * @param keybind the keybind + * @return the name of the button on the keyboard, for example "HOME", or "F1" + */ + def name(keybind: Keybind): String = Keyboard.getKeyName(apply(keybind)) + + + override def clone(): Keymap = { + val clone = new Keymap + clone.map.addAll(this.map) + clone + } + + + /** + * Tries to find a keybind that corresponds to the specific LWJGL key code + * @param key the LWJGL key code + * @return Option containing the keybind (if found) + */ + private def findByCode(key: Int): Option[Keybind] = map.iterator.collectFirst { case (keybind: Keybind, `key`) => keybind } + /** * Attempts to read known keybindings from a config. @@ -55,3 +97,20 @@ class Keymap { case (key: Keybind, value: Int) => (key.toString.toLowerCase(), value) }).toMap.asJava) } + +object Keymap { + object Press { + def unapply(event: KeyEvent): Option[Keybind] = event match { + case KeyEvent(State.Press, code, _) => + Settings.get.keymap.findByCode(code) + case _ => None + } + } + object Release { + def unapply(event: KeyEvent): Option[Keybind] = event match { + case KeyEvent(State.Release, code, _) => + Settings.get.keymap.findByCode(code) + case _ => None + } + } +} diff --git a/src/main/scala/ocelot/desktop/util/SettingsData.scala b/src/main/scala/ocelot/desktop/util/SettingsData.scala index b2a1027..f7b952b 100644 --- a/src/main/scala/ocelot/desktop/util/SettingsData.scala +++ b/src/main/scala/ocelot/desktop/util/SettingsData.scala @@ -12,6 +12,7 @@ class SettingsData { def this(data: SettingsData) { this() updateWith(data) + keymap = data.keymap.clone() } var brainCustomConfigPath: Option[String] = None