Add key mapping tab to the settings dialog

This commit is contained in:
UnicornFreedom 2025-07-30 00:29:03 +02:00
parent 98d352a9e6
commit 3c604b976a
No known key found for this signature in database
GPG Key ID: B4ED0DB6B940024F
16 changed files with 202 additions and 35 deletions

0
spritepack/spritepack.sh Normal file → Executable file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

View File

@ -23,6 +23,7 @@ Tier3 = #c354cd
Label = #333333
LabelError = #aa0000
LabelDisabled = #888888
Scrollbar = #e5e5e526
ScrollbarThumb = #cc3f72

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -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

View File

@ -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")

View File

@ -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()

View File

@ -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 = {

View File

@ -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
}

View File

@ -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)) {

View File

@ -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))
}

View File

@ -16,6 +16,7 @@ class SettingsDialog extends ModalDialog {
private val tabs = Seq(
new UISettingsTab,
new SoundSettingsTab,
new KeymapSettingsTab,
new SystemSettingsTab,
)

View File

@ -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(

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -12,6 +12,7 @@ class SettingsData {
def this(data: SettingsData) {
this()
updateWith(data)
keymap = data.keymap.clone()
}
var brainCustomConfigPath: Option[String] = None