Merge branch 'refactor/icon-source-everywhere' into develop

This commit is contained in:
Fingercomp 2025-08-17 19:51:42 +03:00
commit d769b0bde3
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
47 changed files with 570 additions and 532 deletions

View File

@ -2,7 +2,6 @@ version = 3.9.9
runner.dialect = scala213 runner.dialect = scala213
preset = default preset = default
maxColumn = 120 maxColumn = 120
indent.defnSite = 2
align = { align = {
preset = none preset = none
@ -28,3 +27,9 @@ docstrings = {
wrap = keep wrap = keep
forceBlankLineBefore = false forceBlankLineBefore = false
} }
indent {
defnSite = 2
extendSite = 2
withSiteRelativeToExtends = 2
}

View File

@ -2,7 +2,6 @@ package ocelot.desktop.graphics
import ocelot.desktop.color.{Color, RGBAColorNorm} import ocelot.desktop.color.{Color, RGBAColorNorm}
import ocelot.desktop.geometry.{Rect2D, Size2D, Transform2D, Vector2D} import ocelot.desktop.geometry.{Rect2D, Size2D, Transform2D, Vector2D}
import ocelot.desktop.graphics.IconSource.Animation
import ocelot.desktop.graphics.Texture.MinFilteringMode import ocelot.desktop.graphics.Texture.MinFilteringMode
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D} import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
import ocelot.desktop.graphics.render.InstanceRenderer import ocelot.desktop.graphics.render.InstanceRenderer
@ -280,105 +279,87 @@ class Graphics(private var width: Int, private var height: Int, private var scal
// I hate scala. Overloaded methods with default arguments are not allowed // I hate scala. Overloaded methods with default arguments are not allowed
def sprite(icon: IconSource, bounds: Rect2D): Unit = { def sprite(icon: IconSource, bounds: Rect2D): Unit = {
sprite(icon.path, bounds.x, bounds.y, bounds.w, bounds.h, Color.White, icon.animation) sprite(icon, bounds.x, bounds.y, bounds.w, bounds.h, Color.White)
} }
def sprite(icon: IconSource, bounds: Rect2D, color: Color): Unit = { def sprite(icon: IconSource, bounds: Rect2D, color: Color): Unit = {
sprite(icon.path, bounds.x, bounds.y, bounds.w, bounds.h, color, icon.animation) sprite(icon, bounds.x, bounds.y, bounds.w, bounds.h, color)
} }
def sprite(icon: IconSource, pos: Vector2D, size: Size2D): Unit = { def sprite(icon: IconSource, pos: Vector2D, size: Size2D): Unit = {
sprite(icon.path, pos.x, pos.y, size.width, size.height, Color.White, icon.animation) sprite(icon, pos.x, pos.y, size.width, size.height, Color.White)
}
def sprite(icon: IconSource, pos: Vector2D, color: Color): Unit = {
sprite(icon, pos.x, pos.y, color)
} }
def sprite(icon: IconSource, pos: Vector2D, size: Size2D, color: Color): Unit = { def sprite(icon: IconSource, pos: Vector2D, size: Size2D, color: Color): Unit = {
sprite(icon.path, pos.x, pos.y, size.width, size.height, color, icon.animation) sprite(icon, pos.x, pos.y, size.width, size.height, color)
} }
def sprite(icon: IconSource, x: Float, y: Float): Unit = { def sprite(icon: IconSource, x: Float, y: Float): Unit = {
sprite(icon.path, x, y, icon.animation) sprite(icon, x, y, Color.White)
} }
def sprite(icon: IconSource, x: Float, y: Float, width: Float, height: Float): Unit = { def sprite(icon: IconSource, x: Float, y: Float, width: Float, height: Float): Unit = {
sprite(icon.path, x, y, width, height, animation = icon.animation) sprite(icon, x, y, width, height, Color.White)
} }
def sprite(icon: IconSource, x: Float, y: Float, width: Float, height: Float, color: Color): Unit = { def sprite(icon: IconSource, x: Float, y: Float, color: Color): Unit = {
sprite(icon.path, x, y, width, height, color, icon.animation) val size = Spritesheet.spriteSize(icon.path)
sprite(icon, x, y, size.width, size.height, color)
} }
def sprite(name: String, bounds: Rect2D): Unit = { def sprite(
sprite(name, bounds.origin, bounds.size, Color.White) icon: IconSource,
} x: Float,
y: Float,
def sprite(name: String, x: Float, y: Float, color: Color): Unit = { width: Float,
sprite(name, Vector2D(x, y), Spritesheet.spriteSize(name), color) height: Float,
} color: Color,
): Unit = {
def sprite(name: String, pos: Vector2D, color: Color): Unit = { sprite = icon.path
sprite(name, pos, Spritesheet.spriteSize(name), color)
}
def sprite(name: String, pos: Vector2D, size: Size2D, color: Color): Unit = {
sprite(name, pos.x, pos.y, size.width, size.height, color)
}
def sprite(name: String, pos: Vector2D, size: Size2D): Unit = {
sprite(name, pos.x, pos.y, size.width, size.height)
}
def sprite(name: String, x: Float, y: Float): Unit = {
sprite(name, x, y, Color.White, None)
}
def sprite(name: String, x: Float, y: Float, animation: Option[Animation]): Unit = {
sprite(name, x, y, Color.White, animation)
}
def sprite(name: String, x: Float, y: Float, color: Color, animation: Option[Animation]): Unit = {
val size = Spritesheet.spriteSize(name)
sprite(name, x, y, size.width, size.height, color, animation)
}
def sprite(name: String, x: Float, y: Float, width: Float, height: Float,
color: Color = Color.White,
animation: Option[Animation] = None): Unit = {
sprite = name
foreground = color foreground = color
val spriteRect = animation match { val spriteRect = icon.animation.map { animation =>
case Some(animation) => val duration = animation.frames.map(_._2).sum
val duration = animation.frames.map(_._2).sum var timeOffset = 0f
var timeOffset = 0f var curFrame = 0
var curFrame = 0
breakable { breakable {
for ((idx, dur) <- animation.frames) { for ((idx, dur) <- animation.frames) {
timeOffset += dur timeOffset += dur
curFrame = idx curFrame = idx
if (timeOffset >= time % duration) break if (timeOffset >= time % duration) break
}
} }
}
val size = animation.frameSize match { val size = animation.frameSize match {
case Some(size) => Size2D(this.spriteRect.w, this.spriteRect.w * size.height / size.width) case Some(size) => Size2D(this.spriteRect.w, this.spriteRect.w * size.height / size.width)
case None => Size2D(this.spriteRect.w, this.spriteRect.w) case None => Size2D(this.spriteRect.w, this.spriteRect.w)
} }
Some(this.spriteRect.copy(y = this.spriteRect.y + curFrame * size.height, h = size.height))
case None => None this.spriteRect.copy(y = this.spriteRect.y + curFrame * size.height, h = size.height)
} }
_rect(x, y, width, height, fixUV = true, spriteRect) _rect(x, y, width, height, fixUV = true, spriteRect)
} }
def sprite(name: String, x: Float, y: Float, width: Float, height: Float, def sprite(
color: Color, name: String,
spriteRect: Rect2D): Unit = { x: Float,
y: Float,
width: Float,
height: Float,
color: Color,
spriteRect: Option[Rect2D],
fixUV: Boolean = true,
): Unit = {
sprite = name sprite = name
foreground = color foreground = color
_rect(x, y, width, height, fixUV = true, Some(spriteRect)) _rect(x, y, width, height, fixUV, spriteRect)
} }
def rect(r: Rect2D, color: Color): Unit = { def rect(r: Rect2D, color: Color): Unit = {
@ -386,7 +367,7 @@ class Graphics(private var width: Int, private var height: Int, private var scal
} }
def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = { def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = {
sprite("Empty", x, y, width, height, color) sprite(IconSource.Empty, x, y, width, height, color)
} }
private def checkFont(): Unit = { private def checkFont(): Unit = {

View File

@ -3,86 +3,148 @@ package ocelot.desktop.graphics
import ocelot.desktop.geometry.Size2D import ocelot.desktop.geometry.Size2D
import ocelot.desktop.ui.widget.modal.notification.NotificationType.NotificationType import ocelot.desktop.ui.widget.modal.notification.NotificationType.NotificationType
import totoro.ocelot.brain.entity.tape.Tape.{Kind => TapeKind} import totoro.ocelot.brain.entity.tape.Tape.{Kind => TapeKind}
import totoro.ocelot.brain.util.Direction.{Direction, Down, Up, North, South, West, East} import totoro.ocelot.brain.util.Direction.{Direction, Down, East, North, South, Up, West}
import totoro.ocelot.brain.util.DyeColor import totoro.ocelot.brain.util.DyeColor
import totoro.ocelot.brain.util.ExtendedTier.ExtendedTier import totoro.ocelot.brain.util.ExtendedTier.ExtendedTier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
case class IconSource( case class IconSource(path: String, animation: Option[IconSource.Animation] = None)
path: String,
animation: Option[IconSource.Animation] = None,
)
object IconSource { object IconSource {
object Items { case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D])
protected val prefix: String = "items"
object Animation {
def apply(frames: (Int, Float)*) = new Animation(frames.toArray, None)
def apply(size: Size2D)(frames: (Int, Float)*) = new Animation(frames.toArray, Some(size))
def apply(size: Option[Size2D] = None)(frames: Array[(Int, Float)]) = new Animation(frames, size)
}
class IconScope(directory: String)(implicit parent: Option[IconScope]) {
// this makes nested `IconScope` declarations use `this` as their parent scope.
implicit def scope: Option[IconScope] = Some(this)
private def prefix: String = parent match {
case Some(parent) => s"${parent.prefix}/$directory"
case None => directory
}
protected def get(name: String): IconSource = {
get(name, None)
}
protected def get(name: String, animation: Animation): IconSource = {
get(name, Some(animation))
}
protected def get(name: String, animation: Option[Animation]): IconSource = {
IconSource(s"$prefix/$name", animation)
}
}
private implicit val scope: Option[IconScope] = None
val Empty: IconSource = IconSource("Empty")
val EmptySlot: IconSource = IconSource("EmptySlot")
val ShadowCorner: IconSource = IconSource("ShadowCorner")
val ShadowBorder: IconSource = IconSource("ShadowBorder")
val TabArrow: IconSource = IconSource("TabArrow")
val BarSegment: IconSource = IconSource("BarSegment")
val BackgroundPattern: IconSource = IconSource("BackgroundPattern")
val Logo: IconSource = IconSource("Logo")
val Knob: IconSource = IconSource("Knob")
val KnobLimits: IconSource = IconSource("KnobLimits")
val KnobCenter: IconSource = IconSource("KnobCenter")
val Loading: IconSource = IconSource(
"Loading",
Some(
Animation(Size2D(48, 32))(
(0, 0.7f),
(1, 0.7f),
(2, 0.7f),
(3, 0.7f),
(4, 0.7f),
(5, 0.7f),
(6, 0.7f),
(7, 0.7f),
(8, 0.7f),
(9, 0.7f),
(10, 0.7f),
(11, 0.7f),
(12, 0.7f),
(13, 0.7f),
)
),
)
object Items extends IconScope("items") {
val Cpu: Tier => IconSource = { tier => val Cpu: Tier => IconSource = { tier =>
IconSource(s"$prefix/CPU${tier.id}") get(s"CPU${tier.id}")
} }
val Apu: Tier => IconSource = { tier => val Apu: Tier => IconSource = { tier =>
IconSource(s"$prefix/APU${tier.id}", animation = Some(Animations.Apu)) get(s"APU${tier.id}", Animations.Apu)
} }
val GraphicsCard: Tier => IconSource = { tier => val GraphicsCard: Tier => IconSource = { tier =>
IconSource(s"$prefix/GraphicsCard${tier.id}") get(s"GraphicsCard${tier.id}")
} }
val NetworkCard: IconSource = IconSource(s"$prefix/NetworkCard") val NetworkCard: IconSource = get("NetworkCard")
val WirelessNetworkCard: Tier => IconSource = { tier => val WirelessNetworkCard: Tier => IconSource = { tier =>
IconSource(s"$prefix/WirelessNetworkCard${tier.id}") get(s"WirelessNetworkCard${tier.id}")
} }
val LinkedCard: IconSource = IconSource(s"$prefix/LinkedCard", animation = Some(Animations.LinkedCard)) val LinkedCard: IconSource = get("LinkedCard", Animations.LinkedCard)
val InternetCard: IconSource = IconSource(s"$prefix/InternetCard", animation = Some(Animations.InternetCard)) val InternetCard: IconSource = get("InternetCard", Animations.InternetCard)
val RedstoneCard: Tier => IconSource = { tier => val RedstoneCard: Tier => IconSource = { tier =>
IconSource(s"$prefix/RedstoneCard${tier.id}") get(s"RedstoneCard${tier.id}")
} }
val DataCard: Tier => IconSource = { tier => val DataCard: Tier => IconSource = { tier =>
IconSource(s"$prefix/DataCard${tier.id}", animation = Some(Animations.DataCard)) get(s"DataCard${tier.id}", Animations.DataCard)
} }
val SoundCard: IconSource = IconSource(s"$prefix/SoundCard", animation = Some(Animations.DataCard)) val SoundCard: IconSource = get("SoundCard", Animations.DataCard)
val SelfDestructingCard: IconSource = val SelfDestructingCard: IconSource =
IconSource(s"$prefix/SelfDestructingCard", animation = Some(Animations.SelfDestructingCard)) get("SelfDestructingCard", Animations.SelfDestructingCard)
val OcelotCard: IconSource = IconSource(s"$prefix/OcelotCard", animation = Some(Animations.OcelotCard)) val OcelotCard: IconSource = get("OcelotCard", Animations.OcelotCard)
val HardDiskDrive: Tier => IconSource = { tier => val HardDiskDrive: Tier => IconSource = { tier =>
IconSource(s"$prefix/HardDiskDrive${tier.id}") get(s"HardDiskDrive${tier.id}")
} }
val Eeprom: IconSource = IconSource(s"$prefix/EEPROM") val Eeprom: IconSource = get("EEPROM")
val FloppyDisk: DyeColor => IconSource = { color => val FloppyDisk: DyeColor => IconSource = { color =>
IconSource(s"$prefix/FloppyDisk_${color.name}") get(s"FloppyDisk_${color.name}")
} }
val Memory: ExtendedTier => IconSource = { tier => val Memory: ExtendedTier => IconSource = { tier =>
IconSource(s"$prefix/Memory${tier.id}") get(s"Memory${tier.id}")
} }
val Server: Tier => IconSource = { tier => val Server: Tier => IconSource = { tier =>
IconSource(s"$prefix/Server${tier.id}") get(s"Server${tier.id}")
} }
val ComponentBus: Tier => IconSource = { tier => val ComponentBus: Tier => IconSource = { tier =>
IconSource(s"$prefix/ComponentBus${tier.id}") get(s"ComponentBus${tier.id}")
} }
val Tape: TapeKind => IconSource = { val Tape: TapeKind => IconSource = {
case TapeKind.Golder => Tape(TapeKind.Gold) case TapeKind.Golder => Tape(TapeKind.Gold)
case TapeKind.NetherStarrer => Tape(TapeKind.NetherStar) case TapeKind.NetherStarrer => Tape(TapeKind.NetherStar)
case kind => IconSource(s"$prefix/Tape$kind") case kind => get(s"Tape$kind")
} }
val DiskDriveMountable: IconSource = IconSource(s"$prefix/DiskDriveMountable") val DiskDriveMountable: IconSource = get("DiskDriveMountable")
// noinspection ScalaWeakerAccess // noinspection ScalaWeakerAccess
object Animations { object Animations {
@ -104,342 +166,368 @@ object IconSource {
} }
} }
case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D]) object Icons extends IconScope("icons") {
val Card: IconSource = get("Card")
object Animation { val Cpu: IconSource = get("CPU")
def apply(frames: (Int, Float)*) = new Animation(frames.toArray, None) val Hdd: IconSource = get("HDD")
def apply(size: Size2D, frames: (Int, Float)*) = new Animation(frames.toArray, Some(size)) val Eeprom: IconSource = get("EEPROM")
def apply(size: Option[Size2D] = None, frames: Array[(Int, Float)]) = new Animation(frames, size) val Floppy: IconSource = get("Floppy")
} val Memory: IconSource = get("Memory")
val Server: IconSource = get("Server")
// ----------------------- Ocelot interface icons ----------------------- val ComponentBus: IconSource = get("ComponentBus")
val Loading: IconSource = IconSource(
"Loading",
animation = Some(Animation(
Size2D(48, 32),
(0, 0.7f),
(1, 0.7f),
(2, 0.7f),
(3, 0.7f),
(4, 0.7f),
(5, 0.7f),
(6, 0.7f),
(7, 0.7f),
(8, 0.7f),
(9, 0.7f),
(10, 0.7f),
(11, 0.7f),
(12, 0.7f),
(13, 0.7f),
)),
)
object Icons {
protected val prefix: String = "icons"
val Card: IconSource = IconSource(s"$prefix/Card")
val Cpu: IconSource = IconSource(s"$prefix/CPU")
val Hdd: IconSource = IconSource(s"$prefix/HDD")
val Eeprom: IconSource = IconSource(s"$prefix/EEPROM")
val Floppy: IconSource = IconSource(s"$prefix/Floppy")
val Memory: IconSource = IconSource(s"$prefix/Memory")
val Server: IconSource = IconSource(s"$prefix/Server")
val ComponentBus: IconSource = IconSource(s"$prefix/ComponentBus")
val Tier: Tier => IconSource = { tier => val Tier: Tier => IconSource = { tier =>
IconSource(s"$prefix/Tier${tier.id}") get(s"Tier${tier.id}")
} }
val SideNone: IconSource = IconSource(s"$prefix/SideNone") val SideNone: IconSource = get("SideNone")
val SideAny: IconSource = IconSource(s"$prefix/SideAny") val SideAny: IconSource = get("SideAny")
val SideUndefined: IconSource = IconSource(s"$prefix/SideUndefined") val SideUndefined: IconSource = get("SideUndefined")
val Side: Direction => IconSource = { val Side: Direction => IconSource = {
case Down => IconSource(s"$prefix/SideDown") case Down => get("SideDown")
case Up => IconSource(s"$prefix/SideUp") case Up => get("SideUp")
case North => IconSource(s"$prefix/SideNorth") case North => get("SideNorth")
case South => IconSource(s"$prefix/SideSouth") case South => get("SideSouth")
case West => IconSource(s"$prefix/SideWest") case West => get("SideWest")
case East => IconSource(s"$prefix/SideEast") case East => get("SideEast")
case _ => SideUndefined case _ => SideUndefined
} }
val Notification: NotificationType => IconSource = { notificationType => val Notification: NotificationType => IconSource = { notificationType =>
IconSource(s"$prefix/Notification$notificationType") get(s"Notification$notificationType")
} }
val SettingsKeymap: IconSource = IconSource(s"$prefix/SettingsKeymap") val NA: IconSource = get("NA")
val SettingsSystem: IconSource = IconSource(s"$prefix/SettingsSystem") val SettingsKeymap: IconSource = get("SettingsKeymap")
val SettingsSound: IconSource = IconSource(s"$prefix/SettingsSound") val SettingsSystem: IconSource = get("SettingsSystem")
val SettingsUI: IconSource = IconSource(s"$prefix/SettingsUI") val SettingsSound: IconSource = get("SettingsSound")
val Delete: IconSource = IconSource(s"$prefix/Delete") val SettingsUI: IconSource = get("SettingsUI")
val Label: IconSource = IconSource(s"$prefix/Label") val Delete: IconSource = get("Delete")
val Copy: IconSource = IconSource(s"$prefix/Copy") val Label: IconSource = get("Label")
val AspectRatio: IconSource = IconSource(s"$prefix/AspectRatio") val Copy: IconSource = get("Copy")
val Eject: IconSource = IconSource(s"$prefix/Eject") val AspectRatio: IconSource = get("AspectRatio")
val Restart: IconSource = IconSource(s"$prefix/Restart") val Eject: IconSource = get("Eject")
val Edit: IconSource = IconSource(s"$prefix/Edit") val Restart: IconSource = get("Restart")
val Folder: IconSource = IconSource(s"$prefix/Folder") val Edit: IconSource = get("Edit")
val FolderSlash: IconSource = IconSource(s"$prefix/FolderSlash") val Folder: IconSource = get("Folder")
val Code: IconSource = IconSource(s"$prefix/Code") val FolderSlash: IconSource = get("FolderSlash")
val File: IconSource = IconSource(s"$prefix/File") val Code: IconSource = get("Code")
val Link: IconSource = IconSource(s"$prefix/Link") val File: IconSource = get("File")
val LinkSlash: IconSource = IconSource(s"$prefix/LinkSlash") val Link: IconSource = get("Link")
val Power: IconSource = IconSource(s"$prefix/Power") val LinkSlash: IconSource = get("LinkSlash")
val Save: IconSource = IconSource(s"$prefix/Save") val Power: IconSource = get("Power")
val SaveAs: IconSource = IconSource(s"$prefix/SaveAs") val Save: IconSource = get("Save")
val Plus: IconSource = IconSource(s"$prefix/Plus") val SaveAs: IconSource = get("SaveAs")
val Cross: IconSource = IconSource(s"$prefix/Cross") val Plus: IconSource = get("Plus")
val Microchip: IconSource = IconSource(s"$prefix/Microchip") val Cross: IconSource = get("Cross")
val Antenna: IconSource = IconSource(s"$prefix/Antenna") val Microchip: IconSource = get("Microchip")
val Window: IconSource = IconSource(s"$prefix/Window") val Antenna: IconSource = get("Antenna")
val Tiers: IconSource = IconSource(s"$prefix/Tiers") val Window: IconSource = get("Window")
val LinesHorizontal: IconSource = IconSource(s"$prefix/LinesHorizontal") val Tiers: IconSource = get("Tiers")
val ArrowRight: IconSource = IconSource(s"$prefix/ArrowRight") val LinesHorizontal: IconSource = get("LinesHorizontal")
val Book: IconSource = IconSource(s"$prefix/Book") val ArrowRight: IconSource = get("ArrowRight")
val Help: IconSource = IconSource(s"$prefix/Help") val Book: IconSource = get("Book")
val Ocelot: IconSource = IconSource(s"$prefix/Ocelot") val Help: IconSource = get("Help")
val Guitar: IconSource = IconSource(s"$prefix/Guitar") val Ocelot: IconSource = get("Ocelot")
val Keyboard: IconSource = IconSource(s"$prefix/Keyboard") val Guitar: IconSource = get("Guitar")
val KeyboardOff: IconSource = IconSource(s"$prefix/KeyboardOff") val Keyboard: IconSource = get("Keyboard")
val ButtonRandomize: IconSource = IconSource(s"$prefix/ButtonRandomize") val KeyboardOff: IconSource = get("KeyboardOff")
val ButtonClipboard: IconSource = IconSource(s"$prefix/ButtonClipboard") val ButtonRandomize: IconSource = get("ButtonRandomize")
val ButtonCheck: IconSource = IconSource(s"$prefix/ButtonCheck") val ButtonClipboard: IconSource = get("ButtonClipboard")
val Home: IconSource = IconSource(s"$prefix/Home") val ButtonCheck: IconSource = get("ButtonCheck")
val Pin: IconSource = IconSource(s"$prefix/Pin") val Home: IconSource = get("Home")
val Unpin: IconSource = IconSource(s"$prefix/Unpin") val Pin: IconSource = get("Pin")
val Close: IconSource = IconSource(s"$prefix/Close") val Unpin: IconSource = get("Unpin")
val Grid: IconSource = IconSource(s"$prefix/Grid") val Close: IconSource = get("Close")
val GridOff: IconSource = IconSource(s"$prefix/GridOff") val Grid: IconSource = get("Grid")
val GridOff: IconSource = get("GridOff")
val LMB: IconSource = get("LMB")
val RMB: IconSource = get("RMB")
val DragLMB: IconSource = get("DragLMB")
val DragRMB: IconSource = get("DragRMB")
val WireArrowLeft: IconSource = get("WireArrowLeft")
val WireArrowRight: IconSource = get("WireArrowRight")
val WaveSine: IconSource = get("WaveSine")
val WaveTriangle: IconSource = get("WaveTriangle")
val WaveSawtooth: IconSource = get("WaveSawtooth")
val WaveSquare: IconSource = get("WaveSquare")
val WaveNoise: IconSource = get("WaveNoise")
val WaveLFSR: IconSource = get("WaveLFSR")
} }
// ----------------------- Node icons ----------------------- object Nodes extends IconScope("nodes") {
val NewNode: IconSource = get("NewNode")
val NA: IconSource = IconSource("icons/NA") val Cable: IconSource = get("Cable")
val Camera: IconSource = get("Camera")
object Nodes { val Chest: IconSource = get("Chest")
val NewNode: IconSource = IconSource("nodes/NewNode")
val Cable: IconSource = IconSource("nodes/Cable")
val Camera: IconSource = IconSource("nodes/Camera")
val Chest: IconSource = IconSource("nodes/Chest")
val HologramProjector: Tier => IconSource = { tier => val HologramProjector: Tier => IconSource = { tier =>
IconSource(s"nodes/HologramProjector${tier.id}") get(s"HologramProjector${tier.id}")
} }
val IronNoteBlock: IconSource = IconSource("nodes/IronNoteBlock") val IronNoteBlock: IconSource = get("IronNoteBlock")
val NoteBlock: IconSource = IconSource("nodes/NoteBlock") val NoteBlock: IconSource = get("NoteBlock")
val OpenFMRadio: IconSource = IconSource("nodes/OpenFMRadio") val OpenFMRadio: IconSource = get("OpenFMRadio")
val Relay: IconSource = IconSource("nodes/Relay") val Relay: IconSource = get("Relay")
val TapeDrive: IconSource = IconSource("nodes/TapeDrive") val TapeDrive: IconSource = get("TapeDrive")
object Computer extends PowerIconSource with DiskActivityIconSource { val Lamp: IconSource = get("Lamp")
override protected def prefix: String = "nodes/computer" val LampFrame: IconSource = get("LampFrame")
val LampGlow: IconSource = get("LampGlow")
val Default: IconSource = IconSource(s"$prefix/Default") object Computer extends IconScope("computer") with PowerIconSource with DiskActivityIconSource {
val Default: IconSource = get("Default")
} }
object DiskDrive extends DiskActivityIconSource with FloppyDriveIconSource { object DiskDrive extends IconScope("disk-drive") with DiskActivityIconSource with FloppyDriveIconSource {
override protected def prefix: String = "nodes/disk-drive" val Default: IconSource = get("Default")
val Default: IconSource = IconSource(s"$prefix/Default")
} }
object Lamp extends IconSource("nodes/Lamp") { object Microcontroller extends IconScope("microcontroller") with PowerIconSource {
val Frame: IconSource = IconSource("nodes/LampFrame") val Default: IconSource = get("Default")
val Glow: IconSource = IconSource("nodes/LampGlow")
} }
object Microcontroller extends PowerIconSource { object OcelotBlock extends IconScope("ocelot-block") {
override protected def prefix: String = "nodes/microcontroller" val Default: IconSource = get(
"Default",
val Default: IconSource = IconSource(s"$prefix/Default") Some(Animation(Size2D(16, 16))((0, 30f), (1, 5f), (2, 2f), (0, 20f), (3, 3f), (4, 2f))),
}
object OcelotBlock {
val Default: IconSource = IconSource(
"nodes/ocelot-block/Default",
animation = Some(Animation(Size2D(16, 16), (0, 30f), (1, 5f), (2, 2f), (0, 20f), (3, 3f), (4, 2f))),
) )
val Rx: IconSource = IconSource("nodes/ocelot-block/Rx") val Rx: IconSource = get("Rx")
val Tx: IconSource = IconSource("nodes/ocelot-block/Tx") val Tx: IconSource = get("Tx")
} }
object Rack { object Rack extends IconScope("rack") {
protected val prefix: String = "nodes/rack" val Empty: IconSource = get("Empty")
val Default: IconSource = get("Default")
val Empty: IconSource = IconSource(s"$prefix/Empty")
val Default: IconSource = IconSource(s"$prefix/Default")
val Server: Array[Server] = Array.tabulate(4)(new Server(_)) val Server: Array[Server] = Array.tabulate(4)(new Server(_))
val Drive: Array[Drive] = Array.tabulate(4)(new Drive(_)) val Drive: Array[Drive] = Array.tabulate(4)(new Drive(_))
class Server(val slot: Int) extends PowerIconSource with DiskActivityIconSource with NetworkActivityIconSource { class Server(val slot: Int)
override protected def prefix: String = s"${Rack.prefix}/server/$slot" extends IconScope(s"server/$slot")
with PowerIconSource
with DiskActivityIconSource
with NetworkActivityIconSource {
val Default: IconSource = IconSource(s"$prefix/Default") val Default: IconSource = get("Default")
} }
class Drive(val slot: Int) extends DiskActivityIconSource with FloppyDriveIconSource { class Drive(val slot: Int)
override protected def prefix: String = s"${Rack.prefix}/drive/$slot" extends IconScope(s"drive/$slot")
with DiskActivityIconSource
with FloppyDriveIconSource {
val Default: IconSource = IconSource(s"$prefix/Default") val Default: IconSource = get("Default")
} }
} }
object Raid { object Raid extends IconScope("raid") {
protected val prefix: String = "nodes/raid" val Default: IconSource = get("Default")
val Default: IconSource = IconSource(s"$prefix/Default")
val Drive: Array[Drive] = Array.tabulate(3)(new Drive(_)) val Drive: Array[Drive] = Array.tabulate(3)(new Drive(_))
class Drive(val slot: Int) extends DiskActivityIconSource { class Drive(val slot: Int) extends IconScope(slot.toString) with DiskActivityIconSource {
override protected def prefix: String = s"${Raid.prefix}/$slot" val Error: IconSource = get("Error")
val Error: IconSource = IconSource(s"$prefix/Error")
} }
} }
object Screen { object Screen extends IconScope("screen") {
protected val prefix: String = "nodes/screen" val Standalone: IconSource = get("Standalone")
val PowerOnOverlay: IconSource = get("PowerOnOverlay")
val Standalone: IconSource = IconSource(s"$prefix/Standalone") val ColumnTop: IconSource = get("ColumnTop")
val PowerOnOverlay: IconSource = IconSource(s"$prefix/PowerOnOverlay") val ColumnMiddle: IconSource = get("ColumnMiddle")
val ColumnBottom: IconSource = get("ColumnBottom")
val ColumnTop: IconSource = IconSource(s"$prefix/ColumnTop") val RowLeft: IconSource = get("RowLeft")
val ColumnMiddle: IconSource = IconSource(s"$prefix/ColumnMiddle") val RowMiddle: IconSource = get("RowMiddle")
val ColumnBottom: IconSource = IconSource(s"$prefix/ColumnBottom") val RowRight: IconSource = get("RowRight")
val RowLeft: IconSource = IconSource(s"$prefix/RowLeft") val TopLeft: IconSource = get("TopLeft")
val RowMiddle: IconSource = IconSource(s"$prefix/RowMiddle") val TopMiddle: IconSource = get("TopMiddle")
val RowRight: IconSource = IconSource(s"$prefix/RowRight") val TopRight: IconSource = get("TopRight")
val TopLeft: IconSource = IconSource(s"$prefix/TopLeft") val MiddleLeft: IconSource = get("MiddleLeft")
val TopMiddle: IconSource = IconSource(s"$prefix/TopMiddle") val Middle: IconSource = get("Middle")
val TopRight: IconSource = IconSource(s"$prefix/TopRight") val MiddleRight: IconSource = get("MiddleRight")
val MiddleLeft: IconSource = IconSource(s"$prefix/MiddleLeft") val BottomLeft: IconSource = get("BottomLeft")
val Middle: IconSource = IconSource(s"$prefix/Middle") val BottomMiddle: IconSource = get("BottomMiddle")
val MiddleRight: IconSource = IconSource(s"$prefix/MiddleRight") val BottomRight: IconSource = get("BottomRight")
val BottomLeft: IconSource = IconSource(s"$prefix/BottomLeft")
val BottomMiddle: IconSource = IconSource(s"$prefix/BottomMiddle")
val BottomRight: IconSource = IconSource(s"$prefix/BottomRight")
} }
object Holidays { object Holidays extends IconScope("holidays") {
protected val prefix: String = "nodes/holidays" val Christmas: IconSource = get("Christmas")
val Valentines: IconSource = get("Valentines")
val Christmas: IconSource = IconSource(s"$prefix/Christmas") val Halloween: IconSource = get("Halloween")
val Valentines: IconSource = IconSource(s"$prefix/Valentines")
val Halloween: IconSource = IconSource(s"$prefix/Halloween")
} }
} }
trait PowerIconSource { trait PowerIconSource extends IconScope {
protected def prefix: String val On: IconSource = get("On")
val Error: IconSource = get("Error")
val On: IconSource = IconSource(s"$prefix/On")
val Error: IconSource = IconSource(s"$prefix/Error")
} }
trait DiskActivityIconSource { trait DiskActivityIconSource extends IconScope {
protected def prefix: String val DiskActivity: IconSource = get("DiskActivity")
val DiskActivity: IconSource = IconSource(s"$prefix/DiskActivity")
} }
trait NetworkActivityIconSource { trait NetworkActivityIconSource extends IconScope {
protected def prefix: String val NetworkActivity: IconSource = get("NetworkActivity")
val NetworkActivity: IconSource = IconSource(s"$prefix/NetworkActivity")
} }
trait FloppyDriveIconSource { trait FloppyDriveIconSource extends IconScope {
protected def prefix: String val Floppy: IconSource = get("Floppy")
val Floppy: IconSource = IconSource(s"$prefix/Floppy")
} }
object Screen { object Screen extends IconScope("screen") {
protected val prefix: String = "screen" val InnerCornerTL: IconSource = get("InnerCornerTL")
val InnerCornerTR: IconSource = get("InnerCornerTR")
val InnerCornerBL: IconSource = get("InnerCornerBL")
val InnerCornerBR: IconSource = get("InnerCornerBR")
val InnerCornerTL: IconSource = IconSource(s"$prefix/InnerCornerTL") val OuterCornerTL: IconSource = get("OuterCornerTL")
val InnerCornerTR: IconSource = IconSource(s"$prefix/InnerCornerTR") val OuterCornerTR: IconSource = get("OuterCornerTR")
val InnerCornerBL: IconSource = IconSource(s"$prefix/InnerCornerBL") val OuterCornerBL: IconSource = get("OuterCornerBL")
val InnerCornerBR: IconSource = IconSource(s"$prefix/InnerCornerBR") val OuterCornerBR: IconSource = get("OuterCornerBR")
val OuterCornerTL: IconSource = IconSource(s"$prefix/OuterCornerTL") val InnerBorderT: IconSource = get("InnerBorderT")
val OuterCornerTR: IconSource = IconSource(s"$prefix/OuterCornerTR") val OuterBorderT: IconSource = get("OuterBorderT")
val OuterCornerBL: IconSource = IconSource(s"$prefix/OuterCornerBL")
val OuterCornerBR: IconSource = IconSource(s"$prefix/OuterCornerBR")
val InnerBorderT: IconSource = IconSource(s"$prefix/InnerBorderT") val InnerBorderB: IconSource = get("InnerBorderB")
val OuterBorderT: IconSource = IconSource(s"$prefix/OuterBorderT")
val InnerBorderB: IconSource = IconSource(s"$prefix/InnerBorderB")
} }
// ----------------------- Particles ----------------------- object Particles extends IconScope("particles") {
val Note: IconSource = get("Note")
object Particles { val Smoke: IconSource = get("Smoke")
protected val prefix: String = "particles"
val Smoke: IconSource = IconSource(s"$prefix/Smoke")
} }
// ----------------------- Buttons ----------------------- object Buttons extends IconScope("buttons") {
val BottomDrawerOpen: IconSource = get("BottomDrawerOpen")
val BottomDrawerClose: IconSource = get("BottomDrawerClose")
object Buttons { val PowerOff: IconSource = get("PowerOff")
protected val prefix: String = "buttons" val PowerOn: IconSource = get("PowerOn")
val BottomDrawerOpen: IconSource = IconSource(s"$prefix/BottomDrawerOpen")
val BottomDrawerClose: IconSource = IconSource(s"$prefix/BottomDrawerClose")
val PowerOff: IconSource = IconSource(s"$prefix/PowerOff")
val PowerOn: IconSource = IconSource(s"$prefix/PowerOn")
val OpenFMRadioVolumeOff: Boolean => IconSource = { isUp => val OpenFMRadioVolumeOff: Boolean => IconSource = { isUp =>
IconSource(s"$prefix/OpenFMRadioVolume${if (isUp) "Up" else "Down"}Off") get(s"OpenFMRadioVolume${if (isUp) "Up" else "Down"}Off")
} }
val OpenFMRadioVolumeOn: Boolean => IconSource = { isUp => val OpenFMRadioVolumeOn: Boolean => IconSource = { isUp =>
IconSource(s"$prefix/OpenFMRadioVolume${if (isUp) "Up" else "Down"}On") get(s"OpenFMRadioVolume${if (isUp) "Up" else "Down"}On")
} }
val OpenFMRadioRedstoneOff: IconSource = IconSource(s"$prefix/OpenFMRadioRedstoneOff") val OpenFMRadioRedstoneOff: IconSource = get("OpenFMRadioRedstoneOff")
val OpenFMRadioRedstoneOn: IconSource = IconSource(s"$prefix/OpenFMRadioRedstoneOn") val OpenFMRadioRedstoneOn: IconSource = get("OpenFMRadioRedstoneOn")
val OpenFMRadioCloseOff: IconSource = IconSource(s"$prefix/OpenFMRadioCloseOff") val OpenFMRadioCloseOff: IconSource = get("OpenFMRadioCloseOff")
val OpenFMRadioCloseOn: IconSource = IconSource(s"$prefix/OpenFMRadioCloseOn") val OpenFMRadioCloseOn: IconSource = get("OpenFMRadioCloseOn")
val OpenFMRadioStartOff: IconSource = IconSource(s"$prefix/OpenFMRadioStartOff") val OpenFMRadioStartOff: IconSource = get("OpenFMRadioStartOff")
val OpenFMRadioStopOn: IconSource = IconSource(s"$prefix/OpenFMRadioStopOn") val OpenFMRadioStopOn: IconSource = get("OpenFMRadioStopOn")
val RackRelayOff: IconSource = IconSource(s"$prefix/RackRelayOff") val RackRelayOff: IconSource = get("RackRelayOff")
val RackRelayOn: IconSource = IconSource(s"$prefix/RackRelayOn") val RackRelayOn: IconSource = get("RackRelayOn")
} }
// ----------------------- Window icons ----------------------- object Window extends IconScope("window") {
val CornerTL: IconSource = get("CornerTL")
val CornerTR: IconSource = get("CornerTR")
val CornerBL: IconSource = get("CornerBL")
val CornerBR: IconSource = get("CornerBR")
object Window { val BorderLight: IconSource = get("BorderLight")
protected val prefix: String = "window" val BorderDark: IconSource = get("BorderDark")
object Tape {
protected val prefix: String = s"${Window.prefix}/tape"
object Tape extends IconScope("tape") {
abstract class TapeButtonIconSource private[Tape] (sprite: String) { abstract class TapeButtonIconSource private[Tape] (sprite: String) {
val Released: IconSource = IconSource(s"$prefix/$sprite") val Released: IconSource = get(sprite)
val Pressed: IconSource = IconSource(s"$prefix/${sprite}Pressed") val Pressed: IconSource = get(s"${sprite}Pressed")
} }
object Back extends TapeButtonIconSource("Back") object Back extends TapeButtonIconSource("Back")
object Play extends TapeButtonIconSource("Play") object Play extends TapeButtonIconSource("Play")
object Stop extends TapeButtonIconSource("Stop") object Stop extends TapeButtonIconSource("Stop")
object Forward extends TapeButtonIconSource("Forward") object Forward extends TapeButtonIconSource("Forward")
val Screen: IconSource = get("Screen")
} }
object Case extends IconScope("case") {
val Motherboard: IconSource = get("Motherboard")
}
object Rack extends IconScope("rack") {
val Motherboard: IconSource = get("Motherboard")
val Lines: IconSource = get("Lines")
trait DirectionIconSource {
protected def iconPrefix: String
val DirectionIcon: Direction => IconSource = { direction =>
get(s"$iconPrefix${direction.side.capitalize}")
}
}
trait ConnectorIconSource {
protected def iconPrefix: String
val Connector: IconSource = get(s"${iconPrefix}Connector")
}
object Side extends DirectionIconSource with ConnectorIconSource {
override protected def iconPrefix: String = "Side"
}
object Network extends DirectionIconSource with ConnectorIconSource {
override protected def iconPrefix: String = "Network"
}
object Node extends DirectionIconSource {
override protected def iconPrefix: String = "Node"
}
}
object Raid extends IconScope("raid") {
val Slots: IconSource = get("Slots")
}
val OpenFMRadio: IconSource = get("OpenFMRadio")
}
object Panel extends IconScope("panel") {
val CornerTL: IconSource = get("CornerTL")
val CornerTR: IconSource = get("CornerTR")
val CornerBL: IconSource = get("CornerBL")
val CornerBR: IconSource = get("CornerBR")
val BorderT: IconSource = get("BorderT")
val BorderB: IconSource = get("BorderB")
val BorderL: IconSource = get("BorderL")
val BorderR: IconSource = get("BorderR")
val Fill: IconSource = get("Fill")
}
object LightPanel extends IconScope("light-panel") {
val CornerTL: IconSource = get("CornerTL")
val CornerTR: IconSource = get("CornerTR")
val CornerBL: IconSource = get("CornerBL")
val CornerBR: IconSource = get("CornerBR")
val BorderT: IconSource = get("BorderT")
val BorderB: IconSource = get("BorderB")
val BorderL: IconSource = get("BorderL")
val BorderR: IconSource = get("BorderR")
val Fill: IconSource = get("Fill")
val Vent: IconSource = get("Vent")
val BookmarkLeft: IconSource = get("BookmarkLeft")
val BookmarkRight: IconSource = get("BookmarkRight")
} }
} }

View File

@ -82,7 +82,7 @@ trait BoomCardFxHandler extends Node with PositionalSoundSourcesNode with SmokeP
} }
g.sprite( g.sprite(
IconSource.Nodes.Lamp.Glow, IconSource.Nodes.LampGlow,
position - size * glowSize, position - size * glowSize,
size * (1 + 2 * glowSize), size * (1 + 2 * glowSize),
ColorScheme("BoomCardGlowStart").lerp(ColorScheme("BoomCardGlowEnd"), boomPhase).withAlpha(alpha), ColorScheme("BoomCardGlowStart").lerp(ColorScheme("BoomCardGlowEnd"), boomPhase).withAlpha(alpha),

View File

@ -104,11 +104,11 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
super.update() super.update()
if (isHovered || isMoving) { if (isHovered || isMoving) {
root.get.statusBar.addMouseEntry("icons/RMB", "Menu") root.get.statusBar.addMouseEntry(IconSource.Icons.RMB, "Menu")
root.get.statusBar.addMouseEntry("icons/DragLMB", "Move node") root.get.statusBar.addMouseEntry(IconSource.Icons.DragLMB, "Move node")
if (ports.nonEmpty) { if (ports.nonEmpty) {
root.get.statusBar.addMouseEntry("icons/DragRMB", "Connect/Disconnect") root.get.statusBar.addMouseEntry(IconSource.Icons.DragRMB, "Connect/Disconnect")
} }
} }
} }
@ -119,7 +119,7 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
super.dispose() super.dispose()
} }
def iconSource: IconSource = IconSource.NA def icon: IconSource = IconSource.Icons.NA
def iconColor: Color = RGBAColor(255, 255, 255) def iconColor: Color = RGBAColor(255, 255, 255)
@ -263,13 +263,12 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
drawHighlight(g) drawHighlight(g)
g.sprite( g.sprite(
iconSource.path, icon,
position.x + HighlightThickness, position.x + HighlightThickness,
position.y + HighlightThickness, position.y + HighlightThickness,
size.width - HighlightThickness * 2, size.width - HighlightThickness * 2,
size.height - HighlightThickness * 2, size.height - HighlightThickness * 2,
iconColor, iconColor,
iconSource.animation,
) )
} }

View File

@ -2,7 +2,7 @@ package ocelot.desktop.node
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Size2D import ocelot.desktop.geometry.Size2D
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.Node.Size import ocelot.desktop.node.Node.Size
import ocelot.desktop.ui.event.handlers.{HoverHandler, MouseHandler} import ocelot.desktop.ui.event.handlers.{HoverHandler, MouseHandler}
import ocelot.desktop.ui.event.{ClickEvent, HoverEvent, MouseEvent} import ocelot.desktop.ui.event.{ClickEvent, HoverEvent, MouseEvent}
@ -39,20 +39,19 @@ class NodeTypeWidget(val nodeType: NodeType) extends Widget with MouseHandler wi
val size = Spritesheet.spriteSize(nodeType.icon) * 4 val size = Spritesheet.spriteSize(nodeType.icon) * 4
g.sprite( g.sprite(
nodeType.icon.path, nodeType.icon,
position.x + Size / 2 - size.width / 2, position.x + Size / 2 - size.width / 2,
position.y + Size / 2 - size.height / 2, position.y + Size / 2 - size.height / 2,
size.width, size.width,
size.height, size.height,
nodeType.tier.map(TierColor.get).getOrElse(Color.White), nodeType.tier.map(TierColor.get).getOrElse(Color.White),
nodeType.icon.animation,
) )
} }
override def update(): Unit = { override def update(): Unit = {
super.update() super.update()
if (isHovered) { if (isHovered) {
root.get.statusBar.addMouseEntry("icons/LMB", "Add node") root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Add node")
} }
} }
} }

View File

@ -1,5 +1,6 @@
package ocelot.desktop.node package ocelot.desktop.node
import ocelot.desktop.graphics.IconSource
import ocelot.desktop.ui.event.sources.KeyEvents import ocelot.desktop.ui.event.sources.KeyEvents
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent} import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
@ -22,6 +23,6 @@ trait ShiftClickNode extends Node {
super.update() super.update()
if (isHovered || isMoving) if (isHovered || isMoving)
root.get.statusBar.addKeyMouseEntry("icons/LMB", "SHIFT", hoveredShiftStatusBarText) root.get.statusBar.addKeyMouseEntry(IconSource.Icons.LMB, "SHIFT", hoveredShiftStatusBarText)
} }
} }

View File

@ -43,10 +43,10 @@ trait SmokeParticleNode extends Node {
SmokeParticleSize.width, SmokeParticleSize.width,
SmokeParticleSize.height, SmokeParticleSize.height,
color, color,
spriteRect.copy( Some(spriteRect.copy(
y = spriteRect.y + animationFrame * spriteRect.w, y = spriteRect.y + animationFrame * spriteRect.w,
h = spriteRect.w, h = spriteRect.w,
), )),
) )
} }
} }

View File

@ -1,9 +1,9 @@
package ocelot.desktop.node package ocelot.desktop.node
import ocelot.desktop.graphics.IconSource
import ocelot.desktop.ui.event.sources.KeyEvents import ocelot.desktop.ui.event.sources.KeyEvents
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent} import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
import ocelot.desktop.ui.widget.window.{Window, Windowed} import ocelot.desktop.ui.widget.window.{Window, Windowed}
import org.lwjgl.input.Keyboard
trait WindowedNode[T <: Window] extends Node with Windowed[T] { trait WindowedNode[T <: Window] extends Node with Windowed[T] {
override def dispose(): Unit = { override def dispose(): Unit = {
@ -14,7 +14,7 @@ trait WindowedNode[T <: Window] extends Node with Windowed[T] {
override def update(): Unit = { override def update(): Unit = {
if (isHovered || isMoving) if (isHovered || isMoving)
root.get.statusBar.addMouseEntry("icons/LMB", if (windowCreated && window.isOpen) "Close" else "Open") root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, if (windowCreated && window.isOpen) "Close" else "Open")
super.update() super.update()
} }

View File

@ -6,7 +6,7 @@ import ocelot.desktop.node.EntityNode
import totoro.ocelot.brain.entity.Cable import totoro.ocelot.brain.entity.Cable
class CableNode(val cable: Cable) extends EntityNode(cable) { class CableNode(val cable: Cable) extends EntityNode(cable) {
override def iconSource: IconSource = IconSource.Nodes.Cable override def icon: IconSource = IconSource.Nodes.Cable
override def rotatable: Boolean = false override def rotatable: Boolean = false

View File

@ -6,7 +6,7 @@ import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode}
import ocelot.desktop.windows.CameraWindow import ocelot.desktop.windows.CameraWindow
class CameraNode(val camera: Camera) extends EntityNode(camera) with LabeledEntityNode with WindowedNode[CameraWindow] { class CameraNode(val camera: Camera) extends EntityNode(camera) with LabeledEntityNode with WindowedNode[CameraWindow] {
override def iconSource: IconSource = IconSource.Nodes.Camera override def icon: IconSource = IconSource.Nodes.Camera
override def rotatable: Boolean = true override def rotatable: Boolean = true

View File

@ -10,7 +10,7 @@ import ocelot.desktop.windows.ChestWindow
class ChestNode extends LabeledNode with WindowedNode[ChestWindow] with PersistedInventory { class ChestNode extends LabeledNode with WindowedNode[ChestWindow] with PersistedInventory {
override type I = Item with PersistableItem override type I = Item with PersistableItem
override def iconSource: IconSource = IconSource.Nodes.Chest override def icon: IconSource = IconSource.Nodes.Chest
override def rotatable: Boolean = false override def rotatable: Boolean = false

View File

@ -24,12 +24,12 @@ class ColorfulLampNode(val lamp: ColorfulLamp) extends EntityNode(lamp) with Lab
) )
g.rect(position.x + 2, position.y + 2, size.width - 4, size.height - 4, lastColor) g.rect(position.x + 2, position.y + 2, size.width - 4, size.height - 4, lastColor)
g.sprite(IconSource.Nodes.Lamp.Frame, position.x + 2, position.y + 2, size.width - 4, size.height - 4) g.sprite(IconSource.Nodes.LampFrame, position.x + 2, position.y + 2, size.width - 4, size.height - 4)
} }
override def drawLight(g: Graphics): Unit = { override def drawLight(g: Graphics): Unit = {
super.drawLight(g) super.drawLight(g)
g.sprite(IconSource.Nodes.Lamp.Glow, position - size / 2, size * 2, lastColor) g.sprite(IconSource.Nodes.LampGlow, position - size / 2, size * 2, lastColor)
} }
eventHandlers += { eventHandlers += {

View File

@ -20,7 +20,7 @@ class ComputerNode(val computerCase: Case)
with AudibleComputerAware with AudibleComputerAware
with WindowedNode[ComputerWindow] { with WindowedNode[ComputerWindow] {
override val iconSource: IconSource = IconSource.Nodes.Computer.Default override val icon: IconSource = IconSource.Nodes.Computer.Default
override def iconColor: Color = TierColor.get(computerCase.tier) override def iconColor: Color = TierColor.get(computerCase.tier)
override def rotatable: Boolean = true override def rotatable: Boolean = true

View File

@ -21,7 +21,7 @@ class DiskDriveNode(entity: FloppyDiskDrive)
with ShiftClickNode with ShiftClickNode
with WindowedNode[DiskDriveWindow] { with WindowedNode[DiskDriveWindow] {
override def iconSource: IconSource = IconSource.Nodes.DiskDrive.Default override def icon: IconSource = IconSource.Nodes.DiskDrive.Default
override def rotatable: Boolean = true override def rotatable: Boolean = true

View File

@ -8,7 +8,7 @@ import totoro.ocelot.brain.entity.HologramProjector
class HologramProjectorNode(val hologramProjector: HologramProjector) class HologramProjectorNode(val hologramProjector: HologramProjector)
extends EntityNode(hologramProjector) with LabeledEntityNode with WindowedNode[HologramProjectorWindow] { extends EntityNode(hologramProjector) with LabeledEntityNode with WindowedNode[HologramProjectorWindow] {
override def iconSource: IconSource = IconSource.Nodes.HologramProjector(hologramProjector.tier) override def icon: IconSource = IconSource.Nodes.HologramProjector(hologramProjector.tier)
override def rotatable: Boolean = false override def rotatable: Boolean = false

View File

@ -9,7 +9,7 @@ import totoro.ocelot.brain.event.{EventBus, NoteBlockTriggerEvent}
import scala.util.Random import scala.util.Random
class IronNoteBlockNode(val ironNoteBlock: IronNoteBlock) extends NoteBlockNodeBase(ironNoteBlock) { class IronNoteBlockNode(val ironNoteBlock: IronNoteBlock) extends NoteBlockNodeBase(ironNoteBlock) {
override def iconSource: IconSource = IconSource.Nodes.IronNoteBlock override def icon: IconSource = IconSource.Nodes.IronNoteBlock
override def rotatable: Boolean = false override def rotatable: Boolean = false

View File

@ -21,7 +21,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
with DefaultSlotItemsFillable with DefaultSlotItemsFillable
with WindowedNode[ComputerWindow] { with WindowedNode[ComputerWindow] {
override val iconSource: IconSource = IconSource.Nodes.Microcontroller.Default override val icon: IconSource = IconSource.Nodes.Microcontroller.Default
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = { override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
addPowerContextMenuEntries(menu) addPowerContextMenuEntries(menu)

View File

@ -7,7 +7,7 @@ import totoro.ocelot.brain.entity.NoteBlock
import totoro.ocelot.brain.event.{EventBus, NoteBlockTriggerEvent} import totoro.ocelot.brain.event.{EventBus, NoteBlockTriggerEvent}
class NoteBlockNode(val noteBlock: NoteBlock) extends NoteBlockNodeBase(noteBlock) { class NoteBlockNode(val noteBlock: NoteBlock) extends NoteBlockNodeBase(noteBlock) {
override def iconSource: IconSource = IconSource.Nodes.NoteBlock override def icon: IconSource = IconSource.Nodes.NoteBlock
override def rotatable: Boolean = false override def rotatable: Boolean = false

View File

@ -3,7 +3,7 @@ package ocelot.desktop.node.nodes
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.audio.{SoundBuffers, SoundCategory, SoundSource} import ocelot.desktop.audio.{SoundBuffers, SoundCategory, SoundSource}
import ocelot.desktop.geometry.{Size2D, Vector2D} import ocelot.desktop.geometry.{Size2D, Vector2D}
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.{EntityNode, LabeledEntityNode} import ocelot.desktop.node.{EntityNode, LabeledEntityNode}
import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.BrainEvent import ocelot.desktop.ui.event.BrainEvent
@ -27,15 +27,19 @@ abstract class NoteBlockNodeBase(entity: Entity with Environment) extends Entity
override def update(): Unit = { override def update(): Unit = {
super.update() super.update()
if (isHovered || isMoving) { if (isHovered || isMoving) {
root.get.statusBar.addMouseEntry("icons/LMB", "Play sample") root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Play sample")
} }
} }
private class NoteParticle(pitch: Int) extends Particle(speed = 1.2f) { private class NoteParticle(pitch: Int) extends Particle(speed = 1.2f) {
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
val col = ColorScheme("Note" + pitch.min(24).max(0)).withAlpha(1 - (2 * time - 1).min(1).max(0)) val col = ColorScheme("Note" + pitch.min(24).max(0)).withAlpha(1 - (2 * time - 1).min(1).max(0))
g.sprite("particles/Note", position + Vector2D(pitch / 24f * 40f + 5, height / 2 - 10 - 100 * time), g.sprite(
Size2D(14, 20), col) IconSource.Particles.Note,
position + Vector2D(pitch / 24f * 40f + 5, height / 2 - 10 - 100 * time),
Size2D(14, 20),
col,
)
} }
} }
} }

View File

@ -22,7 +22,7 @@ class OcelotBlockNode(val ocelot: OcelotBlock)
override def name: String = "Ocelot Block" override def name: String = "Ocelot Block"
override def iconSource: IconSource = IconSource.Nodes.OcelotBlock.Default override def icon: IconSource = IconSource.Nodes.OcelotBlock.Default
override def rotatable: Boolean = false override def rotatable: Boolean = false
@ -68,13 +68,12 @@ class OcelotBlockNode(val ocelot: OcelotBlock)
if (alpha > 0) { if (alpha > 0) {
g.sprite( g.sprite(
icon.path, icon,
position.x + HighlightThickness, position.x + HighlightThickness,
position.y + HighlightThickness, position.y + HighlightThickness,
size.width - HighlightThickness * 2, size.width - HighlightThickness * 2,
size.height - HighlightThickness * 2, size.height - HighlightThickness * 2,
RGBAColorNorm(1f, 1f, 1f, alpha), RGBAColorNorm(1f, 1f, 1f, alpha),
icon.animation,
) )
} }
} }

View File

@ -9,7 +9,7 @@ import ocelot.desktop.windows.OpenFMRadioWindow
class OpenFMRadioNode(val openFMRadio: OpenFMRadio) class OpenFMRadioNode(val openFMRadio: OpenFMRadio)
extends EntityNode(openFMRadio) with LabeledEntityNode with WindowedNode[OpenFMRadioWindow] { extends EntityNode(openFMRadio) with LabeledEntityNode with WindowedNode[OpenFMRadioWindow] {
override def iconSource: IconSource = IconSource.Nodes.OpenFMRadio override def icon: IconSource = IconSource.Nodes.OpenFMRadio
override def rotatable: Boolean = true override def rotatable: Boolean = true

View File

@ -18,7 +18,7 @@ import totoro.ocelot.brain.network
import totoro.ocelot.brain.util.Direction import totoro.ocelot.brain.util.Direction
class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode[RackWindow] { class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode[RackWindow] {
override val iconSource: IconSource = IconSource.Nodes.Rack.Empty override val icon: IconSource = IconSource.Nodes.Rack.Empty
override def exposeAddress = false override def exposeAddress = false
override def ports: Array[NodePort] = Array( override def ports: Array[NodePort] = Array(

View File

@ -27,7 +27,7 @@ class RaidNode(val raid: Raid)
with WindowedNode[RaidWindow] { with WindowedNode[RaidWindow] {
var diskSlots: Array[HddSlotWidget] = Array.tabulate(3)(index => new HddSlotWidget(Slot(index), Tier.Three)) var diskSlots: Array[HddSlotWidget] = Array.tabulate(3)(index => new HddSlotWidget(Slot(index), Tier.Three))
override val iconSource: IconSource = IconSource.Nodes.Raid.Default override val icon: IconSource = IconSource.Nodes.Raid.Default
override def rotatable: Boolean = true override def rotatable: Boolean = true

View File

@ -11,7 +11,7 @@ import totoro.ocelot.brain.util.Direction
class RelayNode(val relay: Relay) class RelayNode(val relay: Relay)
extends EntityNode(relay) with SyncedInventory with LabeledNode with WindowedNode[RelayWindow] { extends EntityNode(relay) with SyncedInventory with LabeledNode with WindowedNode[RelayWindow] {
override val iconSource: IconSource = IconSource.Nodes.Relay override val icon: IconSource = IconSource.Nodes.Relay
override def ports: Array[NodePort] = Array( override def ports: Array[NodePort] = Array(
NodePort(Some(Direction.North)), NodePort(Some(Direction.North)),

View File

@ -91,7 +91,7 @@ class ScreenNode(val screen: Screen)
} }
} }
override def iconSource: IconSource = IconSource.Nodes.Screen.Standalone override def icon: IconSource = IconSource.Nodes.Screen.Standalone
override def iconColor: Color = TierColor.get(screen.tier) override def iconColor: Color = TierColor.get(screen.tier)

View File

@ -18,7 +18,7 @@ class TapeDriveNode(val tapeDrive: TapeDrive)
with PositionalSoundSourcesNode with PositionalSoundSourcesNode
with WindowedNode[TapeDriveWindow] { with WindowedNode[TapeDriveWindow] {
override def iconSource: IconSource = IconSource.Nodes.TapeDrive override def icon: IconSource = IconSource.Nodes.TapeDrive
override def rotatable: Boolean = true override def rotatable: Boolean = true

View File

@ -2,7 +2,7 @@ package ocelot.desktop.ui.widget
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
@ -13,8 +13,8 @@ class Histogram extends Widget {
private def drawBars(g: Graphics): Unit = { private def drawBars(g: Graphics): Unit = {
def drawBarSegment(i: Int, color: Color): Unit = { def drawBarSegment(i: Int, color: Color): Unit = {
g.sprite("BarSegment", position.x, position.y + i * 6, 16, 4, color) g.sprite(IconSource.BarSegment, position.x, position.y + i * 6, 16, 4, color)
g.sprite("BarSegment", position.x + 18, position.y + i * 6, 16, 4, color) g.sprite(IconSource.BarSegment, position.x + 18, position.y + i * 6, 16, 4, color)
} }
val ratio = history.last val ratio = history.last

View File

@ -17,16 +17,13 @@ class Icon(icon: IconSource, size: Size2D = null, private val color: Color = Col
def iconColor: Color = color def iconColor: Color = color
def iconSize: Size2D = { def iconSize: Size2D = {
if (size != null) size Option(size).getOrElse(Spritesheet.spriteSize(icon))
else if (icon.animation.isDefined && icon.animation.get.frameSize.isDefined)
icon.animation.get.frameSize.get
else Spritesheet.spriteSize(icon.path)
} }
override def minimumSize: Size2D = iconSize override def minimumSize: Size2D = iconSize
override def maximumSize: Size2D = minimumSize override def maximumSize: Size2D = minimumSize
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
g.sprite(icon.path, bounds.x, bounds.y, bounds.w, bounds.h, iconColor, icon.animation) g.sprite(icon, bounds, iconColor)
} }
} }

View File

@ -2,7 +2,7 @@ package ocelot.desktop.ui.widget
import ocelot.desktop.color.{Color, IntColor} import ocelot.desktop.color.{Color, IntColor}
import ocelot.desktop.geometry.{Size2D, Vector2D} import ocelot.desktop.geometry.{Size2D, Vector2D}
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.MouseEvent import ocelot.desktop.ui.event.MouseEvent
import totoro.ocelot.brain.util.DyeColor import totoro.ocelot.brain.util.DyeColor
@ -47,14 +47,14 @@ abstract class Knob(dyeColor: DyeColor = DyeColor.Red) extends Widget {
} }
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
g.sprite("KnobLimits", position.x, position.y, 25, 25) g.sprite(IconSource.KnobLimits, position.x, position.y, 25, 25)
g.save() g.save()
g.translate(position.x + 12.5f, position.y + 12.5f) g.translate(position.x + 12.5f, position.y + 12.5f)
g.rotate(input.toFloat / 15f * 4.71239f - 0.785398f) g.rotate(input.toFloat / 15f * 4.71239f - 0.785398f)
g.sprite("Knob", -12.5f, -12.5f, 25, 25, color) g.sprite(IconSource.Knob, -12.5f, -12.5f, 25, 25, color)
g.restore() g.restore()
g.sprite("KnobCenter", position.x, position.y, 25, 25) g.sprite(IconSource.KnobCenter, position.x, position.y, 25, 25)
val centerColor = color.toRGBANorm.copy(a = output / 15f) val centerColor = color.toRGBANorm.copy(a = output / 15f)
g.sprite("KnobCenter", position.x - 1, position.y - 1, 27, 27, centerColor) g.sprite(IconSource.KnobCenter, position.x - 1, position.y - 1, 27, 27, centerColor)
} }
} }

View File

@ -150,8 +150,8 @@ abstract class Viewport3DWidget extends Widget with MouseHandler with HoverHandl
scene = createScene() scene = createScene()
if (isHovered) { if (isHovered) {
root.get.statusBar.addMouseEntry("icons/LMB", "Rotate view") root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Rotate view")
root.get.statusBar.addKeyMouseEntry("icons/LMB", "Shift", "Pan view") root.get.statusBar.addKeyMouseEntry(IconSource.Icons.LMB, "SHIFT", "Pan view")
} }
} }

View File

@ -574,7 +574,7 @@ class WorkspaceView extends Widget with Persistable with MouseHandler with Hover
for (x <- 0 to numRepeatsX) { for (x <- 0 to numRepeatsX) {
for (y <- 0 to numRepeatsY) { for (y <- 0 to numRepeatsY) {
g.sprite("BackgroundPattern", x.toFloat * 304, y.toFloat * 304, 304, 304) g.sprite(IconSource.BackgroundPattern, x.toFloat * 304, y.toFloat * 304, 304, 304)
} }
} }
@ -660,11 +660,11 @@ class WorkspaceView extends Widget with Persistable with MouseHandler with Hover
} }
if (isHovered && newConnection.isEmpty) { if (isHovered && newConnection.isEmpty) {
if (nodeSelector.isClosed) root.get.statusBar.addMouseEntry(
root.get.statusBar.addMouseEntry("icons/LMB", "Add node") IconSource.Icons.LMB,
else if (nodeSelector.isClosed) "Add node" else "Close selector",
root.get.statusBar.addMouseEntry("icons/LMB", "Close selector") )
root.get.statusBar.addMouseEntry("icons/DragLMB", "Move camera") root.get.statusBar.addMouseEntry(IconSource.Icons.DragLMB, "Move camera")
} }
} }
} }

View File

@ -3,7 +3,7 @@ package ocelot.desktop.ui.widget.card
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
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.layout.{Layout, LinearLayout}
import ocelot.desktop.ui.widget.card.SoundCardWindow._ import ocelot.desktop.ui.widget.card.SoundCardWindow._
import ocelot.desktop.ui.widget.window.{PanelWindow, Window} import ocelot.desktop.ui.widget.window.{PanelWindow, Window}
@ -167,9 +167,9 @@ class SoundCardWindow(card: SoundCard) extends PanelWindow {
g.line(b, Vector2D(mx, b.y), 2, col) g.line(b, Vector2D(mx, b.y), 2, col)
if (b.x > px) { if (b.x > px) {
g.sprite("icons/WireArrowRight", b.x - 4, b.y - 4, col) g.sprite(IconSource.Icons.WireArrowRight, b.x - 4, b.y - 4, col)
} else { } else {
g.sprite("icons/WireArrowLeft", b.x, b.y - 4, col) g.sprite(IconSource.Icons.WireArrowLeft, b.x, b.y - 4, col)
} }
existingConnectors += Connector(column, colorIdx, from, to) existingConnectors += Connector(column, colorIdx, from, to)
@ -275,12 +275,12 @@ object SoundCardWindow {
} }
private val waves = Array( private val waves = Array(
("icons/WaveSine", SignalGenerator.Sine.getClass), (IconSource.Icons.WaveSine, SignalGenerator.Sine.getClass),
("icons/WaveTriangle", SignalGenerator.Triangle.getClass), (IconSource.Icons.WaveTriangle, SignalGenerator.Triangle.getClass),
("icons/WaveSawtooth", SignalGenerator.Sawtooth.getClass), (IconSource.Icons.WaveSawtooth, SignalGenerator.Sawtooth.getClass),
("icons/WaveSquare", SignalGenerator.Square.getClass), (IconSource.Icons.WaveSquare, SignalGenerator.Square.getClass),
("icons/WaveNoise", classOf[SignalGenerator.Noise]), (IconSource.Icons.WaveNoise, classOf[SignalGenerator.Noise]),
("icons/WaveLFSR", classOf[SignalGenerator.LFSR]), (IconSource.Icons.WaveLFSR, classOf[SignalGenerator.LFSR]),
) )
private def drawEnvelope(g: Graphics, env: ADSREnvelope, bounds: Rect2D, elapsedMs: Float): Unit = { private def drawEnvelope(g: Graphics, env: ADSREnvelope, bounds: Rect2D, elapsedMs: Float): Unit = {
@ -445,21 +445,21 @@ object SoundCardWindow {
val sh = (if (isFirst) 10 else 0) + (if (isLast) 50 else 0) val sh = (if (isFirst) 10 else 0) + (if (isLast) 50 else 0)
h -= sh h -= sh
g.sprite("light-panel/BorderL", x, y, 4, h) g.sprite(IconSource.LightPanel.BorderL, x, y, 4, h)
g.sprite("light-panel/BorderR", x + w - 4, y, 4, h) g.sprite(IconSource.LightPanel.BorderR, x + w - 4, y, 4, h)
g.sprite("light-panel/Fill", x + 4, y, w - 8, h) g.sprite(IconSource.LightPanel.Fill, x + 4, y, w - 8, h)
if (isFirst) { if (isFirst) {
g.sprite("light-panel/CornerTL", x, y - 4, 4, 4) g.sprite(IconSource.LightPanel.CornerTL, x, y - 4, 4, 4)
g.sprite("light-panel/CornerTR", x + w - 4, y - 4, 4, 4) g.sprite(IconSource.LightPanel.CornerTR, x + w - 4, y - 4, 4, 4)
g.sprite("light-panel/BorderT", x + 4, y - 4, w - 8, 4) g.sprite(IconSource.LightPanel.BorderT, x + 4, y - 4, w - 8, 4)
} }
if (isLast) { if (isLast) {
g.sprite("light-panel/CornerBL", x, y + h, 4, 4) g.sprite(IconSource.LightPanel.CornerBL, x, y + h, 4, 4)
g.sprite("light-panel/CornerBR", x + w - 4, y + h, 4, 4) g.sprite(IconSource.LightPanel.CornerBR, x + w - 4, y + h, 4, 4)
g.sprite("light-panel/BorderB", x + 4, y + h, w - 8, 4) g.sprite(IconSource.LightPanel.BorderB, x + 4, y + h, w - 8, 4)
g.sprite("light-panel/Vent", x, y + h + 8, w, 38) g.sprite(IconSource.LightPanel.Vent, x, y + h + 8, w, 38)
} }
for (i <- 0 until 6) { for (i <- 0 until 6) {
@ -469,13 +469,13 @@ object SoundCardWindow {
y -= sy y -= sy
h += sh h += sh
g.sprite("light-panel/BookmarkLeft", x, y + 16, 18, 14) g.sprite(IconSource.LightPanel.BookmarkLeft, x, y + 16, 18, 14)
g.sprite("light-panel/BookmarkLeft", x, y + 32, 18, 14) g.sprite(IconSource.LightPanel.BookmarkLeft, x, y + 32, 18, 14)
g.sprite("light-panel/BookmarkLeft", x, y + 48, 18, 14) g.sprite(IconSource.LightPanel.BookmarkLeft, x, y + 48, 18, 14)
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 16, 20, 14) g.sprite(IconSource.LightPanel.BookmarkRight, x + w - 20, y + 16, 20, 14)
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 32, 20, 14) g.sprite(IconSource.LightPanel.BookmarkRight, x + w - 20, y + 32, 20, 14)
g.sprite("light-panel/BookmarkRight", x + w - 20, y + 48, 20, 14) g.sprite(IconSource.LightPanel.BookmarkRight, x + w - 20, y + 48, 20, 14)
g.setSmallFont() g.setSmallFont()
g.background = Color.Transparent g.background = Color.Transparent

View File

@ -4,7 +4,7 @@ import buildinfo.BuildInfo
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.ui.layout.LinearLayout import ocelot.desktop.ui.layout.LinearLayout
import ocelot.desktop.ui.widget.{Button, Filler, Label, PaddingBox, Widget} import ocelot.desktop.ui.widget.{Button, Filler, Label, PaddingBox, Widget}
import ocelot.desktop.ui.widget.modal.ModalDialog import ocelot.desktop.ui.widget.modal.ModalDialog
@ -25,7 +25,7 @@ class AboutDialog extends ModalDialog {
override def minimumSize: Size2D = Spritesheet.spriteSize("Logo") + 40 override def minimumSize: Size2D = Spritesheet.spriteSize("Logo") + 40
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
g.sprite("Logo", bounds.x + 20, bounds.y + 80, ColorScheme("AboutLogo")) g.sprite(IconSource.Logo, bounds.x + 20, bounds.y + 80, ColorScheme("AboutLogo"))
drawChildren(g) drawChildren(g)
} }
} }

View File

@ -230,7 +230,7 @@ class SlotWidget[I <: Item](private val slot: Inventory#Slot)(implicit slotItemT
} }
override final def draw(g: Graphics): Unit = { override final def draw(g: Graphics): Unit = {
g.sprite("EmptySlot", bounds) g.sprite(IconSource.EmptySlot, bounds)
val iconBounds = bounds.inflated(-2) val iconBounds = bounds.inflated(-2)

View File

@ -3,11 +3,11 @@ package ocelot.desktop.ui.widget.statusbar
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Size2D import ocelot.desktop.geometry.Size2D
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.ui.widget.Widget import ocelot.desktop.ui.widget.Widget
import ocelot.desktop.util.{DrawUtils, Spritesheet} import ocelot.desktop.util.{DrawUtils, Spritesheet}
class KeyMouseEntry(val icon: String, val key: String, val text: String) extends Widget { class KeyMouseEntry(val icon: IconSource, val key: String, val text: String) extends Widget {
private val iconSize = Spritesheet.spriteSize(icon) private val iconSize = Spritesheet.spriteSize(icon)
override def minimumSize: Size2D = Size2D(iconSize.width + key.length * 8 + 40 + text.length * 8, 16) override def minimumSize: Size2D = Size2D(iconSize.width + key.length * 8 + 40 + text.length * 8, 16)

View File

@ -3,11 +3,11 @@ package ocelot.desktop.ui.widget.statusbar
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Size2D import ocelot.desktop.geometry.Size2D
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.ui.widget.Widget import ocelot.desktop.ui.widget.Widget
import ocelot.desktop.util.Spritesheet import ocelot.desktop.util.Spritesheet
class MouseEntry(val icon: String, val text: String) extends Widget { class MouseEntry(val icon: IconSource, val text: String) extends Widget {
private val iconSize = Spritesheet.spriteSize(icon) private val iconSize = Spritesheet.spriteSize(icon)
override def minimumSize: Size2D = Size2D(iconSize.width + 24 + text.length * 8, 16) override def minimumSize: Size2D = Size2D(iconSize.width + 24 + text.length * 8, 16)

View File

@ -102,7 +102,7 @@ class StatusBar extends Widget {
super.update() super.update()
if (isHovered) { if (isHovered) {
root.get.statusBar.addMouseEntry("icons/RMB", "Change simulation speed") root.get.statusBar.addMouseEntry(IconSource.Icons.RMB, "Change simulation speed")
} }
} }
@ -135,22 +135,16 @@ class StatusBar extends Widget {
Padding2D(left = 8), Padding2D(left = 8),
) )
def addMouseEntry(icon: String, text: String): Unit = { def addMouseEntry(icon: IconSource, text: String): Unit = {
if ( if (!keyMouseEntries.children.collect({ case e: MouseEntry => e.icon }).contains(icon)) {
!keyMouseEntries.children.filter(_.isInstanceOf[MouseEntry]).map(_.asInstanceOf[MouseEntry]).exists(
_.icon == icon
)
)
keyMouseEntries.children :+= new MouseEntry(icon, text) keyMouseEntries.children :+= new MouseEntry(icon, text)
}
} }
def addKeyMouseEntry(icon: String, key: String, text: String): Unit = { def addKeyMouseEntry(icon: IconSource, key: String, text: String): Unit = {
if ( if (!keyMouseEntries.children.collect({ case e: KeyMouseEntry => e }).exists(e => e.icon == icon && e.key == key)) {
!keyMouseEntries.children.filter(_.isInstanceOf[KeyMouseEntry]).map(_.asInstanceOf[KeyMouseEntry]).exists(v =>
v.icon == icon && v.key == key
)
)
keyMouseEntries.children :+= new KeyMouseEntry(icon, key, text) keyMouseEntries.children :+= new KeyMouseEntry(icon, key, text)
}
} }
def addKeyEntry(key: String, text: String): Unit = { def addKeyEntry(key: String, text: String): Unit = {

View File

@ -64,7 +64,7 @@ class VerticalMenuButton(icon: IconSource, label: String, handler: VerticalMenuB
colorAnimation.update() colorAnimation.update()
g.rect(bounds, colorAnimation.color) g.rect(bounds, colorAnimation.color)
g.rect(bounds.x + bounds.w - 2f, bounds.y, 2f, bounds.h, ColorScheme("VerticalMenuBorder")) g.rect(bounds.x + bounds.w - 2f, bounds.y, 2f, bounds.h, ColorScheme("VerticalMenuBorder"))
if (selected) g.sprite("TabArrow", bounds.x + bounds.w - 8f, bounds.y + bounds.h / 2f - 7f, 8, 14) if (selected) g.sprite(IconSource.TabArrow, bounds.x + bounds.w - 8f, bounds.y + bounds.h / 2f - 7f, 8, 14)
drawChildren(g) drawChildren(g)
} }

View File

@ -92,17 +92,17 @@ object DrawUtils {
} }
def panel(g: Graphics, x: Float, y: Float, w: Float, h: Float): Unit = { def panel(g: Graphics, x: Float, y: Float, w: Float, h: Float): Unit = {
g.sprite("panel/CornerTL", x, y, 4, 4) g.sprite(IconSource.Panel.CornerTL, x, y, 4, 4)
g.sprite("panel/CornerTR", x + w - 4, y, 4, 4) g.sprite(IconSource.Panel.CornerTR, x + w - 4, y, 4, 4)
g.sprite("panel/CornerBL", x, y + h - 4, 4, 4) g.sprite(IconSource.Panel.CornerBL, x, y + h - 4, 4, 4)
g.sprite("panel/CornerBR", x + w - 4, y + h - 4, 4, 4) g.sprite(IconSource.Panel.CornerBR, x + w - 4, y + h - 4, 4, 4)
g.sprite("panel/BorderT", x + 4, y, w - 8, 4) g.sprite(IconSource.Panel.BorderT, x + 4, y, w - 8, 4)
g.sprite("panel/BorderB", x + 4, y + h - 4, w - 8, 4) g.sprite(IconSource.Panel.BorderB, x + 4, y + h - 4, w - 8, 4)
g.sprite("panel/BorderL", x, y + 4, 4, h - 8) g.sprite(IconSource.Panel.BorderL, x, y + 4, 4, h - 8)
g.sprite("panel/BorderR", x + w - 4, y + 4, 4, h - 8) g.sprite(IconSource.Panel.BorderR, x + w - 4, y + 4, 4, h - 8)
g.sprite("panel/Fill", x + 4, y + 4, w - 8, h - 8) g.sprite(IconSource.Panel.Fill, x + 4, y + 4, w - 8, h - 8)
} }
def isValidPolyline(points: Array[Vector2D]): Boolean = { def isValidPolyline(points: Array[Vector2D]): Boolean = {
@ -186,19 +186,19 @@ object DrawUtils {
h: Float, h: Float,
color: Color = RGBAColor(255, 255, 255), color: Color = RGBAColor(255, 255, 255),
): Unit = { ): Unit = {
g.sprite("window/CornerTL", x, y, 8, 8, color) g.sprite(IconSource.Window.CornerTL, x, y, 8, 8, color)
g.sprite("window/CornerTR", x + w - 8, y, 8, 8, color) g.sprite(IconSource.Window.CornerTR, x + w - 8, y, 8, 8, color)
g.sprite("window/CornerBL", x, y + h - 8, 8, 8, color) g.sprite(IconSource.Window.CornerBL, x, y + h - 8, 8, 8, color)
g.sprite("window/CornerBR", x + w - 8, y + h - 8, 8, 8, color) g.sprite(IconSource.Window.CornerBR, x + w - 8, y + h - 8, 8, 8, color)
g.sprite("window/BorderLight", x + 8, y, w - 16, 8, color) g.sprite(IconSource.Window.BorderLight, x + 8, y, w - 16, 8, color)
g.sprite("window/BorderDark", x + 8, y + h - 8, w - 16, 8, color) g.sprite(IconSource.Window.BorderDark, x + 8, y + h - 8, w - 16, 8, color)
g.save() g.save()
g.translate(x, y + 8) g.translate(x, y + 8)
g.rotate(270.toRadians) g.rotate(270.toRadians)
g.sprite("window/BorderLight", 0, 0, -h + 16, 8, color) g.sprite(IconSource.Window.BorderLight, 0, 0, -h + 16, 8, color)
g.sprite("window/BorderDark", 0, w - 8, -h + 16, 8, color) g.sprite(IconSource.Window.BorderDark, 0, w - 8, -h + 16, 8, color)
g.restore() g.restore()
g.rect(x + 8, y + 8, w - 16, h - 16, RGBAColor(198, 198, 198, color.toRGBA.a)) g.rect(x + 8, y + 8, w - 16, h - 16, RGBAColor(198, 198, 198, color.toRGBA.a))
@ -207,24 +207,24 @@ object DrawUtils {
def shadow(g: Graphics, x: Float, y: Float, w: Float, h: Float, a: Float = 0.8f): Unit = { def shadow(g: Graphics, x: Float, y: Float, w: Float, h: Float, a: Float = 0.8f): Unit = {
val col = RGBAColorNorm(1, 1, 1, a) val col = RGBAColorNorm(1, 1, 1, a)
rotSprite(g, "ShadowCorner", x, y, 24, 24, 180.toRadians, col) rotSprite(g, IconSource.ShadowCorner, x, y, 24, 24, 180.toRadians, col)
rotSprite(g, "ShadowCorner", x + w - 24, y, 24, 24, 270.toRadians, col) rotSprite(g, IconSource.ShadowCorner, x + w - 24, y, 24, 24, 270.toRadians, col)
rotSprite(g, "ShadowCorner", x, y + h - 24, 24, 24, 90.toRadians, col) rotSprite(g, IconSource.ShadowCorner, x, y + h - 24, 24, 24, 90.toRadians, col)
g.sprite("ShadowCorner", x + w - 24, y + h - 24, 24, 24, col) g.sprite(IconSource.ShadowCorner, x + w - 24, y + h - 24, 24, 24, col)
g.sprite("ShadowBorder", x + 24, y + 24, w - 48, -24, col) g.sprite(IconSource.ShadowBorder, x + 24, y + 24, w - 48, -24, col)
g.sprite("ShadowBorder", x + 24, y + h - 24, w - 48, 24, col) g.sprite(IconSource.ShadowBorder, x + 24, y + h - 24, w - 48, 24, col)
g.save() g.save()
g.translate(x + 24, y + 24) g.translate(x + 24, y + 24)
g.rotate(90.toRadians) g.rotate(90.toRadians)
g.sprite("ShadowBorder", 0, 0, h - 48, 24, col) g.sprite(IconSource.ShadowBorder, 0, 0, h - 48, 24, col)
g.restore() g.restore()
g.save() g.save()
g.translate(x + w - 24, y + h - 24) g.translate(x + w - 24, y + h - 24)
g.rotate(270.toRadians) g.rotate(270.toRadians)
g.sprite("ShadowBorder", 0, 0, h - 48, 24, col) g.sprite(IconSource.ShadowBorder, 0, 0, h - 48, 24, col)
g.restore() g.restore()
g.rect(x + 24, y + 24, w - 48, h - 48, RGBAColorNorm(0, 0, 0, a)) g.rect(x + 24, y + 24, w - 48, h - 48, RGBAColorNorm(0, 0, 0, a))
@ -232,7 +232,7 @@ object DrawUtils {
private def rotSprite( private def rotSprite(
g: Graphics, g: Graphics,
sprite: String, icon: IconSource,
x: Float, x: Float,
y: Float, y: Float,
w: Float, w: Float,
@ -243,7 +243,7 @@ object DrawUtils {
g.save() g.save()
g.translate(x + w / 2f, y + h / 2f) g.translate(x + w / 2f, y + h / 2f)
g.rotate(angle) g.rotate(angle)
g.sprite(sprite, -w / 2f, -h / 2f, w, h, col) g.sprite(icon, -w / 2f, -h / 2f, w, h, col)
g.restore() g.restore()
} }

View File

@ -1,6 +1,6 @@
package ocelot.desktop.util package ocelot.desktop.util
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.geometry.{Rect2D, Size2D}
import ocelot.desktop.graphics.{IconSource, Texture} import ocelot.desktop.graphics.{IconSource, Texture}
import javax.imageio.ImageIO import javax.imageio.ImageIO
@ -15,18 +15,18 @@ object Spritesheet extends Resource with Logging {
def spriteSize(sprite: String): Size2D = sprites(sprite).size * resolution def spriteSize(sprite: String): Size2D = sprites(sprite).size * resolution
def spriteSize(iconSource: IconSource): Size2D = iconSource.animation match { def spriteSize(icon: IconSource): Size2D = icon.animation match {
case Some(animation) => case Some(animation) =>
animation.frameSize match { animation.frameSize match {
case Some(size) => size case Some(size) => size
case None => case None =>
val size = spriteSize(iconSource.path) val size = spriteSize(icon.path)
Size2D(size.width, size.width) Size2D(size.width, size.width)
} }
case None => spriteSize(iconSource.path) case None => spriteSize(icon.path)
} }
def load(): Unit = { def load(): Unit = {

View File

@ -277,7 +277,11 @@ class ComputerWindow(computerAware: ComputerAware) extends BasicWindow {
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
// Background image // Background image
g.sprite( g.sprite(
s"window/${if (isServerMachineType) "rack" else "case"}/Motherboard", if (isServerMachineType) {
IconSource.Window.Rack.Motherboard
} else {
IconSource.Window.Case.Motherboard
},
position.x, position.x,
position.y, position.y,
width, width,

View File

@ -186,7 +186,7 @@ class OpenFMRadioWindow(radioNode: OpenFMRadioNode) extends BasicWindow {
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
beginDraw(g) beginDraw(g)
// I hate this // I hate this
g.sprite("window/OpenFMRadio", position.x, position.y, size.width, size.height) g.sprite(IconSource.Window.OpenFMRadio, position.x, position.y, size.width, size.height)
drawChildren(g) drawChildren(g)
endDraw(g) endDraw(g)
} }

View File

@ -25,10 +25,6 @@ class RackWindow(rackNode: RackNode) extends PanelWindow {
private val nodeButtonsGap = 12 private val nodeButtonsGap = 12
private val nodeButtonsWidth = 10 private val nodeButtonsWidth = 10
private def directionToSpriteName(prefix: String, direction: Direction): String = {
s"$prefix${direction.side.capitalize}"
}
private def shouldConnectionBeVisible(slotWidget: RackMountableSlotWidget, connectableIndex: Int) = { private def shouldConnectionBeVisible(slotWidget: RackMountableSlotWidget, connectableIndex: Int) = {
connectableIndex == 0 || connectableIndex == 0 ||
( (
@ -77,23 +73,11 @@ class RackWindow(rackNode: RackNode) extends PanelWindow {
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
// Relay mode line // Relay mode line
if (rackNode.rack.isRelayEnabled) { if (rackNode.rack.isRelayEnabled) {
g.sprite( g.sprite(IconSource.Window.Rack.Network.Connector, position.x + 12, position.y + 172, 102, 4)
s"window/rack/NetworkConnector",
position.x + 12,
position.y + 172,
102,
4,
)
} }
// Lines background // Lines background
g.sprite( g.sprite(IconSource.Window.Rack.Lines, position.x + linesMarginLeft, position.y, size.width, size.height)
"window/rack/Lines",
position.x + linesMarginLeft,
position.y,
size.width,
size.height,
)
super.draw(g) super.draw(g)
} }
@ -120,31 +104,28 @@ class RackWindow(rackNode: RackNode) extends PanelWindow {
var y = position.y var y = position.y
var connectionHeight: Float = 0 var connectionHeight: Float = 0
var prefix: String = null
for (connectableIndex <- 0 until 4) { for (connectableIndex <- 0 until 4) {
connectionHeight = if (connectableIndex == 0) lineSideHeight else lineNetworkHeight connectionHeight = if (connectableIndex == 0) lineSideHeight else lineNetworkHeight
if (shouldConnectionBeVisible(mountableSlotWidget, connectableIndex)) { if (shouldConnectionBeVisible(mountableSlotWidget, connectableIndex)) {
val connection = rackNode.rack.nodeMapping(mountableIndex)(connectableIndex) val connection = rackNode.rack.nodeMapping(mountableIndex)(connectableIndex)
prefix = s"window/rack/${if (connectableIndex == 0) "Side" else "Network"}" val source = if (connectableIndex == 0) {
IconSource.Window.Rack.Side
} else {
IconSource.Window.Rack.Network
}
// Connector // Connector
g.sprite( g.sprite(source.Connector, position.x, y, 2, connectionHeight)
s"${prefix}Connector",
position.x,
y,
2,
connectionHeight,
)
// Line // Line
if (connection.isDefined) { for (connection <- connection) {
g.sprite( g.sprite(
directionToSpriteName(prefix, connection.get), source.DirectionIcon(connection),
position.x + 2, position.x + 2,
y, y,
connection.get match { connection match {
case Direction.Bottom => nodeButtonsGap case Direction.Bottom => nodeButtonsGap
case Direction.Top => nodeButtonsGap * 2 + nodeButtonsWidth case Direction.Top => nodeButtonsGap * 2 + nodeButtonsWidth
case Direction.Back => nodeButtonsGap * 3 + nodeButtonsWidth * 2 case Direction.Back => nodeButtonsGap * 3 + nodeButtonsWidth * 2
@ -195,21 +176,13 @@ class RackWindow(rackNode: RackNode) extends PanelWindow {
rackNode.rack.connect( rackNode.rack.connect(
mountableIndex, mountableIndex,
connectableIndex - 1, connectableIndex - 1,
// Connection already exists, removing it Option.unless(oldConnection.contains(direction))(direction),
if (oldConnection.isDefined && oldConnection.get == direction)
None
// Connecting normally
else
Some(direction),
) )
} }
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
if (enabled) { if (enabled) {
g.sprite( g.sprite(IconSource.Window.Rack.Node.DirectionIcon(direction), bounds)
directionToSpriteName("window/rack/Node", direction),
bounds,
)
drawHighlight(g) drawHighlight(g)
} }
} }

View File

@ -3,7 +3,7 @@ package ocelot.desktop.windows
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.nodes.RaidNode import ocelot.desktop.node.nodes.RaidNode
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout} import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
import ocelot.desktop.ui.widget._ import ocelot.desktop.ui.widget._
@ -37,13 +37,7 @@ class RaidWindow(raidNode: RaidNode) extends PanelWindow {
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
// Background border // Background border
g.sprite( g.sprite(IconSource.Window.Raid.Slots, position, size)
"window/raid/Slots",
position.x,
position.y,
size.width,
size.height,
)
super.draw(g) super.draw(g)
} }

View File

@ -2,7 +2,7 @@ package ocelot.desktop.windows
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.node.nodes.ScreenNode import ocelot.desktop.node.nodes.ScreenNode
import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth} import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth}
import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.UiHandler
@ -147,8 +147,8 @@ class ScreenWindow(screenNode: ScreenNode) extends PanelWindow with Logging {
super.update() super.update()
if (scaleDragPoint.isDefined || scaleDragRegion.contains(UiHandler.mousePosition)) { if (scaleDragPoint.isDefined || scaleDragRegion.contains(UiHandler.mousePosition)) {
root.get.statusBar.addMouseEntry("icons/DragLMB", "Scale screen") root.get.statusBar.addMouseEntry(IconSource.Icons.DragLMB, "Scale screen")
root.get.statusBar.addKeyMouseEntry("icons/DragLMB", "SHIFT", "Scale screen (magnify)") root.get.statusBar.addKeyMouseEntry(IconSource.Icons.DragLMB, "SHIFT", "Scale screen (magnify)")
} }
} }

View File

@ -39,12 +39,12 @@ class TapeDriveWindow(val tapeDriveNode: TapeDriveNode) extends PanelWindow {
override def draw(g: Graphics): Unit = { override def draw(g: Graphics): Unit = {
// Screen background // Screen background
g.sprite( g.sprite(
"window/tape/Screen", IconSource.Window.Tape.Screen,
bounds, bounds,
) )
// A barely noticeable overlay showing the playback progress // A barely noticeable overlay showing the playback progress
// Btw Computronix doesn't have this feature, so I won't ruin the canon and make it too annoying // Btw Computronics doesn't have this feature, so I won't ruin the canon and make it too annoying
val playedPart = tapeDriveNode.tapeDrive.position.toFloat / tapeDriveNode.tapeDrive.size.toFloat val playedPart = tapeDriveNode.tapeDrive.position.toFloat / tapeDriveNode.tapeDrive.size.toFloat
if (playedPart > 0) { if (playedPart > 0) {