Added racks, servers & mountable disk drives

This commit is contained in:
Igor Timofeev 2023-06-28 14:58:06 +00:00
parent 109e3e0c72
commit b055436b43
136 changed files with 2133 additions and 987 deletions

@ -1 +1 @@
Subproject commit 40b9b506f9590a1a2add9bb1d47508a06f2b64cf
Subproject commit 8222b1287c40965622fffcdeff4ae93f5d0418b5

View File

Before

Width:  |  Height:  |  Size: 602 B

After

Width:  |  Height:  |  Size: 602 B

View File

Before

Width:  |  Height:  |  Size: 150 B

After

Width:  |  Height:  |  Size: 150 B

View File

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 634 B

View File

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 151 B

View File

Before

Width:  |  Height:  |  Size: 151 B

After

Width:  |  Height:  |  Size: 151 B

View File

Before

Width:  |  Height:  |  Size: 150 B

After

Width:  |  Height:  |  Size: 150 B

View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

View File

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -2,8 +2,8 @@ PortDown = #8382d8
PortUp = #75bdc1
PortNorth = #c8ca5f
PortSouth = #990da3
PortEast = #db7d75
PortWest = #7ec95f
PortEast = #7ec95f
PortWest = #db7d75
PortAny = #9b9b9b
Tier0 = #ababab

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1,14 +1,13 @@
BackgroundPattern 0 0 304 304
BarSegment 53 472 16 4
ComputerMotherboard 305 129 79 70
BarSegment 69 479 16 4
Empty 426 305 16 16
EmptySlot 404 330 18 18
Knob 385 129 50 50
KnobCenter 436 129 50 50
KnobLimits 305 200 50 50
Knob 459 208 50 50
KnobCenter 406 129 50 50
KnobLimits 457 129 50 50
ShadowBorder 505 0 1 24
ShadowCorner 301 305 24 24
TabArrow 0 472 8 14
TabArrow 16 479 8 14
blocks/Generic 443 305 16 16
blocks/HologramEffect 507 18 4 4
blocks/HologramProjector1Top 460 305 16 16
@ -17,174 +16,205 @@ blocks/HologramProjectorSide 494 305 16 16
buttons/BottomDrawerClose 423 330 18 18
buttons/BottomDrawerOpen 442 330 18 18
buttons/OpenFMRadioCloseOff 502 117 7 8
buttons/OpenFMRadioCloseOn 0 487 7 8
buttons/OpenFMRadioCloseOn 131 494 7 8
buttons/OpenFMRadioRedstoneOff 502 72 8 8
buttons/OpenFMRadioRedstoneOn 502 81 8 8
buttons/OpenFMRadioStartOff 326 305 24 24
buttons/OpenFMRadioStartOn 351 305 24 24
buttons/OpenFMRadioStopOff 376 305 24 24
buttons/OpenFMRadioStopOn 401 305 24 24
buttons/OpenFMRadioVolumeDownOff 9 472 10 10
buttons/OpenFMRadioVolumeDownOn 20 472 10 10
buttons/OpenFMRadioVolumeUpOff 31 472 10 10
buttons/OpenFMRadioVolumeUpOn 42 472 10 10
buttons/OpenFMRadioVolumeDownOff 25 479 10 10
buttons/OpenFMRadioVolumeDownOn 36 479 10 10
buttons/OpenFMRadioVolumeUpOff 47 479 10 10
buttons/OpenFMRadioVolumeUpOn 58 479 10 10
buttons/PowerOff 461 330 18 18
buttons/PowerOn 480 330 18 18
icons/ButtonCheck 301 378 17 17
icons/ButtonClipboard 319 378 17 17
icons/ButtonRandomize 337 378 17 17
icons/CPU 356 200 16 16
icons/Card 373 200 16 16
icons/Close 0 439 15 14
icons/ComponentBus 390 200 16 16
icons/CPU 406 180 16 16
icons/Card 423 180 16 16
icons/Close 0 479 15 14
icons/ComponentBus 440 180 16 16
icons/DragLMB 301 363 21 14
icons/DragRMB 323 363 21 14
icons/EEPROM 407 200 16 16
icons/Floppy 424 200 16 16
icons/EEPROM 457 180 16 16
icons/Floppy 474 180 16 16
icons/Grid 335 330 22 22
icons/GridOff 358 330 22 22
icons/HDD 441 200 16 16
icons/HDD 491 180 16 16
icons/Home 381 330 22 22
icons/LMB 71 454 11 14
icons/Memory 458 200 16 16
icons/NA 475 200 16 16
icons/NotificationError 95 454 11 11
icons/NotificationInfo 107 454 11 11
icons/NotificationWarning 119 454 11 11
icons/Pin 26 454 14 14
icons/RMB 83 454 11 14
icons/RackMountable 492 200 16 16
icons/SettingsSound 0 454 12 17
icons/SettingsUI 13 454 12 17
icons/Tier0 356 217 16 16
icons/Tier1 373 217 16 16
icons/Tier2 390 217 16 16
icons/Unpin 41 454 14 14
icons/WaveLFSR 393 411 24 10
icons/WaveNoise 418 411 24 10
icons/WaveSawtooth 443 411 24 10
icons/WaveSine 468 411 24 10
icons/WaveSquare 0 428 24 10
icons/WaveTriangle 25 428 24 10
icons/LMB 71 494 11 14
icons/Memory 459 259 16 16
icons/NA 476 259 16 16
icons/NotificationError 95 494 11 11
icons/NotificationInfo 107 494 11 11
icons/NotificationWarning 119 494 11 11
icons/Pin 26 494 14 14
icons/RMB 83 494 11 14
icons/Server 493 259 16 16
icons/SettingsSound 0 494 12 17
icons/SettingsUI 13 494 12 17
icons/Tier0 379 279 16 16
icons/Tier1 396 279 16 16
icons/Tier2 413 279 16 16
icons/Unpin 41 494 14 14
icons/WaveLFSR 359 462 24 10
icons/WaveNoise 384 462 24 10
icons/WaveSawtooth 409 462 24 10
icons/WaveSine 434 462 24 10
icons/WaveSquare 459 462 24 10
icons/WaveTriangle 484 462 24 10
icons/WireArrowLeft 507 0 4 8
icons/WireArrowRight 507 9 4 8
items/APU0 233 305 16 96
items/APU1 250 305 16 96
items/APU2 267 305 16 96
items/CPU0 407 217 16 16
items/CPU1 424 217 16 16
items/CPU2 441 217 16 16
items/CardBase 458 217 16 16
items/CircuitBoard 475 217 16 16
items/ComponentBus0 492 217 16 16
items/ComponentBus1 356 234 16 16
items/ComponentBus2 373 234 16 16
items/ComponentBus3 390 234 16 16
items/CPU0 430 279 16 16
items/CPU1 447 279 16 16
items/CPU2 464 279 16 16
items/CardBase 481 279 16 16
items/CircuitBoard 0 411 16 16
items/ComponentBus0 17 411 16 16
items/ComponentBus1 34 411 16 16
items/ComponentBus2 51 411 16 16
items/ComponentBus3 68 411 16 16
items/DataCard0 434 0 16 128
items/DataCard1 451 0 16 128
items/DataCard2 468 0 16 128
items/DebugCard 407 234 16 16
items/DiskDriveMountable 424 234 16 16
items/EEPROM 441 234 16 16
items/FloppyDisk_dyeBlack 458 234 16 16
items/FloppyDisk_dyeBlue 475 234 16 16
items/FloppyDisk_dyeBrown 492 234 16 16
items/FloppyDisk_dyeCyan 305 251 16 16
items/FloppyDisk_dyeGray 322 251 16 16
items/FloppyDisk_dyeGreen 339 251 16 16
items/FloppyDisk_dyeLightBlue 356 251 16 16
items/FloppyDisk_dyeLightGray 373 251 16 16
items/FloppyDisk_dyeLime 390 251 16 16
items/FloppyDisk_dyeMagenta 407 251 16 16
items/FloppyDisk_dyeOrange 424 251 16 16
items/FloppyDisk_dyePink 441 251 16 16
items/FloppyDisk_dyePurple 458 251 16 16
items/FloppyDisk_dyeRed 475 251 16 16
items/FloppyDisk_dyeWhite 492 251 16 16
items/FloppyDisk_dyeYellow 305 268 16 16
items/GraphicsCard0 322 268 16 16
items/GraphicsCard1 339 268 16 16
items/GraphicsCard2 356 268 16 16
items/HardDiskDrive0 373 268 16 16
items/HardDiskDrive1 390 268 16 16
items/HardDiskDrive2 407 268 16 16
items/DebugCard 85 411 16 16
items/DiskDriveMountable 102 411 16 16
items/EEPROM 119 411 16 16
items/FloppyDisk_dyeBlack 136 411 16 16
items/FloppyDisk_dyeBlue 153 411 16 16
items/FloppyDisk_dyeBrown 170 411 16 16
items/FloppyDisk_dyeCyan 187 411 16 16
items/FloppyDisk_dyeGray 204 411 16 16
items/FloppyDisk_dyeGreen 221 411 16 16
items/FloppyDisk_dyeLightBlue 238 411 16 16
items/FloppyDisk_dyeLightGray 255 411 16 16
items/FloppyDisk_dyeLime 272 411 16 16
items/FloppyDisk_dyeMagenta 289 411 16 16
items/FloppyDisk_dyeOrange 306 411 16 16
items/FloppyDisk_dyePink 323 411 16 16
items/FloppyDisk_dyePurple 340 411 16 16
items/FloppyDisk_dyeRed 357 411 16 16
items/FloppyDisk_dyeWhite 374 411 16 16
items/FloppyDisk_dyeYellow 391 411 16 16
items/GraphicsCard0 408 411 16 16
items/GraphicsCard1 425 411 16 16
items/GraphicsCard2 442 411 16 16
items/HardDiskDrive0 459 411 16 16
items/HardDiskDrive1 476 411 16 16
items/HardDiskDrive2 493 411 16 16
items/InternetCard 301 330 16 32
items/LinkedCard 284 305 16 96
items/Memory0 424 268 16 16
items/Memory1 441 268 16 16
items/Memory2 458 268 16 16
items/Memory3 475 268 16 16
items/Memory4 492 268 16 16
items/Memory5 305 285 16 16
items/NetworkCard 322 285 16 16
items/RedstoneCard0 339 285 16 16
items/RedstoneCard1 356 285 16 16
items/Memory0 0 428 16 16
items/Memory1 17 428 16 16
items/Memory2 34 428 16 16
items/Memory3 51 428 16 16
items/Memory4 68 428 16 16
items/Memory5 85 428 16 16
items/NetworkCard 102 428 16 16
items/RedstoneCard0 119 428 16 16
items/RedstoneCard1 136 428 16 16
items/SelfDestructingCard 318 330 16 32
items/Server0 373 285 16 16
items/Server1 390 285 16 16
items/Server2 407 285 16 16
items/Server3 424 285 16 16
items/Server0 153 428 16 16
items/Server1 170 428 16 16
items/Server2 187 428 16 16
items/Server3 204 428 16 16
items/SoundCard 485 0 16 128
items/WirelessNetworkCard0 441 285 16 16
items/WirelessNetworkCard1 458 285 16 16
light-panel/BookmarkLeft 374 411 18 14
items/WirelessNetworkCard0 221 428 16 16
items/WirelessNetworkCard1 238 428 16 16
light-panel/BookmarkLeft 340 462 18 14
light-panel/BookmarkRight 355 378 20 14
light-panel/BorderB 8 487 4 4
light-panel/BorderL 98 487 4 2
light-panel/BorderR 13 487 4 4
light-panel/BorderT 18 487 4 4
light-panel/CornerBL 23 487 4 4
light-panel/CornerBR 28 487 4 4
light-panel/CornerTL 33 487 4 4
light-panel/CornerTR 38 487 4 4
light-panel/Fill 4 496 2 2
light-panel/BorderB 139 494 4 4
light-panel/BorderL 229 494 4 2
light-panel/BorderR 144 494 4 4
light-panel/BorderT 149 494 4 4
light-panel/CornerBL 154 494 4 4
light-panel/CornerBR 159 494 4 4
light-panel/CornerTL 164 494 4 4
light-panel/CornerTR 169 494 4 4
light-panel/Fill 88 479 2 2
light-panel/Vent 502 0 2 38
nodes/Cable 502 90 8 8
nodes/Camera 475 285 16 16
nodes/Chest 56 454 14 14
nodes/Computer 492 285 16 16
nodes/ComputerActivityOverlay 487 129 16 16
nodes/ComputerErrorOverlay 487 146 16 16
nodes/ComputerOnOverlay 487 163 16 16
nodes/DiskDrive 385 180 16 16
nodes/DiskDriveActivity 402 180 16 16
nodes/DiskDriveFloppy 419 180 16 16
nodes/HologramProjector0 436 180 16 16
nodes/HologramProjector1 453 180 16 16
nodes/IronNoteBlock 470 180 16 16
nodes/Lamp 487 180 16 16
nodes/LampFrame 0 411 16 16
nodes/Camera 255 428 16 16
nodes/Chest 56 494 14 14
nodes/HologramProjector0 272 428 16 16
nodes/HologramProjector1 289 428 16 16
nodes/IronNoteBlock 306 428 16 16
nodes/Lamp 323 428 16 16
nodes/LampFrame 340 428 16 16
nodes/LampGlow 305 0 128 128
nodes/NewNode 17 411 16 16
nodes/NoteBlock 34 411 16 16
nodes/OpenFMRadio 51 411 16 16
nodes/Relay 68 411 16 16
nodes/screen/BottomLeft 85 411 16 16
nodes/screen/BottomMiddle 102 411 16 16
nodes/screen/BottomRight 119 411 16 16
nodes/screen/ColumnBottom 136 411 16 16
nodes/screen/ColumnMiddle 153 411 16 16
nodes/screen/ColumnTop 170 411 16 16
nodes/screen/Middle 187 411 16 16
nodes/screen/MiddleLeft 204 411 16 16
nodes/screen/MiddleRight 221 411 16 16
nodes/screen/PowerOnOverlay 238 411 16 16
nodes/screen/RowLeft 255 411 16 16
nodes/screen/RowMiddle 272 411 16 16
nodes/screen/RowRight 289 411 16 16
nodes/screen/Standalone 306 411 16 16
nodes/screen/TopLeft 323 411 16 16
nodes/screen/TopMiddle 340 411 16 16
nodes/screen/TopRight 357 411 16 16
panel/BorderB 43 487 4 4
panel/BorderL 103 487 4 2
panel/BorderR 48 487 4 4
panel/BorderT 53 487 4 4
panel/CornerBL 58 487 4 4
panel/CornerBR 63 487 4 4
panel/CornerTL 68 487 4 4
panel/CornerTR 73 487 4 4
panel/Fill 7 496 2 2
nodes/NewNode 357 428 16 16
nodes/NoteBlock 374 428 16 16
nodes/OpenFMRadio 391 428 16 16
nodes/Relay 408 428 16 16
nodes/computer/Activity 425 428 16 16
nodes/computer/Default 442 428 16 16
nodes/computer/Error 459 428 16 16
nodes/computer/On 476 428 16 16
nodes/disk-drive/Activity 493 428 16 16
nodes/disk-drive/Default 0 445 16 16
nodes/disk-drive/Floppy 17 445 16 16
nodes/rack/Default 34 445 16 16
nodes/rack/Empty 51 445 16 16
nodes/rack/drive/0/Activity 68 445 16 16
nodes/rack/drive/0/Default 85 445 16 16
nodes/rack/drive/0/Floppy 102 445 16 16
nodes/rack/drive/1/Activity 119 445 16 16
nodes/rack/drive/1/Default 136 445 16 16
nodes/rack/drive/1/Floppy 153 445 16 16
nodes/rack/drive/2/Activity 170 445 16 16
nodes/rack/drive/2/Default 187 445 16 16
nodes/rack/drive/2/Floppy 204 445 16 16
nodes/rack/drive/3/Activity 221 445 16 16
nodes/rack/drive/3/Default 238 445 16 16
nodes/rack/drive/3/Floppy 255 445 16 16
nodes/rack/drive/Floppy 272 445 16 16
nodes/rack/server/0/Activity 289 445 16 16
nodes/rack/server/0/Default 306 445 16 16
nodes/rack/server/0/Error 323 445 16 16
nodes/rack/server/0/On 340 445 16 16
nodes/rack/server/1/Activity 357 445 16 16
nodes/rack/server/1/Default 374 445 16 16
nodes/rack/server/1/Error 391 445 16 16
nodes/rack/server/1/On 408 445 16 16
nodes/rack/server/2/Activity 425 445 16 16
nodes/rack/server/2/Default 442 445 16 16
nodes/rack/server/2/Error 459 445 16 16
nodes/rack/server/2/On 476 445 16 16
nodes/rack/server/3/Activity 493 445 16 16
nodes/rack/server/3/Default 0 462 16 16
nodes/rack/server/3/Error 17 462 16 16
nodes/rack/server/3/On 34 462 16 16
nodes/screen/BottomLeft 51 462 16 16
nodes/screen/BottomMiddle 68 462 16 16
nodes/screen/BottomRight 85 462 16 16
nodes/screen/ColumnBottom 102 462 16 16
nodes/screen/ColumnMiddle 119 462 16 16
nodes/screen/ColumnTop 136 462 16 16
nodes/screen/Middle 153 462 16 16
nodes/screen/MiddleLeft 170 462 16 16
nodes/screen/MiddleRight 187 462 16 16
nodes/screen/PowerOnOverlay 204 462 16 16
nodes/screen/RowLeft 221 462 16 16
nodes/screen/RowMiddle 238 462 16 16
nodes/screen/RowRight 255 462 16 16
nodes/screen/Standalone 272 462 16 16
nodes/screen/TopLeft 289 462 16 16
nodes/screen/TopMiddle 306 462 16 16
nodes/screen/TopRight 323 462 16 16
panel/BorderB 174 494 4 4
panel/BorderL 234 494 4 2
panel/BorderR 179 494 4 4
panel/BorderT 184 494 4 4
panel/CornerBL 189 494 4 4
panel/CornerBR 194 494 4 4
panel/CornerTL 199 494 4 4
panel/CornerTR 204 494 4 4
panel/Fill 91 479 2 2
particles/Note 502 61 7 10
screen/BorderB 508 25 2 8
screen/BorderT 505 25 2 10
@ -192,10 +222,30 @@ screen/CornerBL 502 99 8 8
screen/CornerBR 502 108 8 8
screen/CornerTL 502 39 8 10
screen/CornerTR 502 50 8 10
window/BorderDark 0 496 1 4
window/BorderLight 2 496 1 4
window/CornerBL 78 487 4 4
window/CornerBR 83 487 4 4
window/CornerTL 88 487 4 4
window/CornerTR 93 487 4 4
window/BorderDark 510 117 1 4
window/BorderLight 86 479 1 4
window/CornerBL 209 494 4 4
window/CornerBR 214 494 4 4
window/CornerTL 219 494 4 4
window/CornerTR 224 494 4 4
window/OpenFMRadio 0 305 232 105
window/case/Motherboard 379 208 79 70
window/rack/Lines 305 208 73 91
window/rack/Motherboard 305 129 100 78
window/rack/NetworkBack 149 499 1 2
window/rack/NetworkBottom 151 499 1 2
window/rack/NetworkConnector 153 499 1 2
window/rack/NetworkLeft 155 499 1 2
window/rack/NetworkRight 157 499 1 2
window/rack/NetworkTop 159 499 1 2
window/rack/NodeBack 239 494 5 1
window/rack/NodeBottom 245 494 5 1
window/rack/NodeLeft 251 494 5 1
window/rack/NodeRight 257 494 5 1
window/rack/NodeTop 263 494 5 1
window/rack/SideBack 510 122 1 3
window/rack/SideBottom 139 499 1 3
window/rack/SideConnector 141 499 1 3
window/rack/SideLeft 143 499 1 3
window/rack/SideRight 145 499 1 3
window/rack/SideTop 147 499 1 3

View File

@ -56,6 +56,16 @@ class SoundSource(val kind: SoundSource.Kind,
}
object SoundSource {
lazy val InterfaceClick: SoundSource = SoundSource.fromBuffer(SoundBuffers.InterfaceClick, SoundCategory.Interface)
lazy val InterfaceClickLow: SoundSource = SoundSource.fromBuffer(SoundBuffers.InterfaceClick, SoundCategory.Interface, pitch = 0.8f)
lazy val InterfaceTick: SoundSource = SoundSource.fromBuffer(SoundBuffers.InterfaceTick, SoundCategory.Interface)
lazy val MinecraftClick: SoundSource = SoundSource.fromBuffer(SoundBuffers.MinecraftClick, SoundCategory.Interface)
lazy val MinecraftExplosion: SoundSource = SoundSource.fromBuffer(SoundBuffers.MinecraftExplosion, SoundCategory.Environment)
lazy val MachineFloppyInsert: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineFloppyInsert, SoundCategory.Environment)
lazy val MachineFloppyEject: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineFloppyEject, SoundCategory.Environment)
sealed trait Kind
case class KindSoundBuffer(buffer: SoundBuffer) extends Kind

View File

@ -1,11 +0,0 @@
package ocelot.desktop.audio
//noinspection TypeAnnotation
object SoundSources {
val InterfaceClick = SoundSource.fromBuffer(SoundBuffers.InterfaceClick, SoundCategory.Interface)
val InterfaceClickLow = SoundSource.fromBuffer(SoundBuffers.InterfaceClick, SoundCategory.Interface, pitch = 0.8f)
val InterfaceTick = SoundSource.fromBuffer(SoundBuffers.InterfaceTick, SoundCategory.Interface)
val MinecraftClick = SoundSource.fromBuffer(SoundBuffers.MinecraftClick, SoundCategory.Interface)
val MinecraftExplosion = SoundSource.fromBuffer(SoundBuffers.MinecraftExplosion, SoundCategory.Environment)
}

View File

@ -4,7 +4,7 @@ import ocelot.desktop.util.Persistable
import totoro.ocelot.brain.nbt.NBTTagCompound
object Vector2D {
val Zero = Vector2D(0, 0)
val Zero: Vector2D = Vector2D(0, 0)
def apply(x: Double, y: Double): Vector2D = Vector2D(x.toFloat, y.toFloat)
}

View File

@ -14,6 +14,8 @@ object Icons {
val EepromIcon: IconDef = IconDef("icons/EEPROM")
val FloppyIcon: IconDef = IconDef("icons/Floppy")
val MemoryIcon: IconDef = IconDef("icons/Memory")
val ServerIcon: IconDef = IconDef("icons/Server")
val ComponentBusIcon: IconDef = IconDef("icons/ComponentBus")
val TierIcon: Tier => IconDef = { tier =>
IconDef(s"icons/Tier${tier.id}")
@ -75,6 +77,16 @@ object Icons {
IconDef(s"items/Memory${tier.id}")
}
val Server: Tier => IconDef = { tier =>
IconDef(s"items/Server${tier.id}")
}
val ComponentBus: Tier => IconDef = { tier =>
IconDef(s"items/ComponentBus${tier.id}")
}
val DiskDriveMountable: IconDef = IconDef("items/DiskDriveMountable")
//noinspection ScalaWeakerAccess
object Animations {
val Apu: Animation = Array(

View File

@ -3,15 +3,18 @@ package ocelot.desktop.inventory
import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color
import ocelot.desktop.graphics.IconDef
import ocelot.desktop.ui.event.EventAware
import ocelot.desktop.ui.widget.Updatable
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import ocelot.desktop.ui.widget.tooltip.ItemTooltip
import ocelot.desktop.util.Disposable
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier
/**
* Something that can be stored in an [[Inventory]].
*/
trait Item {
trait Item extends EventAware with Updatable with Disposable {
private var _slot: Option[Inventory#Slot] = None
/**
@ -24,7 +27,8 @@ trait Item {
if (slot.nonEmpty) {
onInserted()
} else {
}
else {
onRemoved()
}
}
@ -102,6 +106,12 @@ trait Item {
* Keep this rather cheap.
*/
def factory: ItemFactory
/**
* Override this in subclasses to implement some specific item behavior
* during UI update
*/
def update(): Unit = {}
}
object Item {

View File

@ -139,4 +139,8 @@ object Items extends Logging {
}
registerSingleton(SoundCardItem.Factory)
registerSingleton(SelfDestructingCardItem.Factory)
registerTiered("Server", Tier.One to Tier.Creative)(new ServerItem.Factory(_))
registerTiered("Component bus", Tier.One to Tier.Creative)(new ComponentBusItem.Factory(_))
registerSingleton(DiskDriveMountableItem.Factory)
}

View File

@ -3,8 +3,8 @@ package ocelot.desktop.inventory
import ocelot.desktop.OcelotDesktop
import ocelot.desktop.inventory.PersistedInventory._
import ocelot.desktop.inventory.traits.{ComponentItem, PersistableItem}
import ocelot.desktop.util.{Logging, Persistable}
import ocelot.desktop.util.ReflectionUtils.findUnaryConstructor
import ocelot.desktop.util.{Logging, Persistable}
import totoro.ocelot.brain.nbt.ExtendedNBT.{extendNBTTagCompound, extendNBTTagList}
import totoro.ocelot.brain.nbt.persistence.NBTPersistence
import totoro.ocelot.brain.nbt.{NBT, NBTTagCompound}

View File

@ -4,7 +4,7 @@ import ocelot.desktop.OcelotDesktop
import ocelot.desktop.inventory.PersistedInventory.ItemLoadException
import ocelot.desktop.inventory.SyncedInventory.SlotStatus.SlotStatus
import ocelot.desktop.inventory.SyncedInventory.SyncDirection.SyncDirection
import ocelot.desktop.inventory.SyncedInventory._
import ocelot.desktop.inventory.SyncedInventory.{SyncDirection, _}
import ocelot.desktop.inventory.traits.ComponentItem
import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.ui.widget.EventHandlers
@ -54,12 +54,7 @@ trait SyncedInventory extends PersistedInventory with Logging {
isLoading = false
}
val occupiedSlots = inventoryIterator.map(_.index).toSet
.union(brainInventory.inventory.iterator.map(_.index).toSet)
for (slotIndex <- occupiedSlots) {
sync(slotIndex, SyncDirection.Reconcile)
}
syncSlots()
}
@throws[ItemLoadException]("if the item could not be loaded")
@ -116,15 +111,23 @@ trait SyncedInventory extends PersistedInventory with Logging {
}
}
def syncSlots(direction: SyncDirection = SyncDirection.Reconcile): Unit = {
val occupiedSlots = inventoryIterator.map(_.index).toSet
.union(brainInventory.inventory.iterator.map(_.index).toSet)
for (slotIndex <- occupiedSlots)
sync(slotIndex, direction)
}
private def sync(slotIndex: Int, direction: SyncDirection): Unit = {
val initialSync = syncFuel == InitialSyncFuel
try {
doSync(slotIndex, direction)
} finally {
if (initialSync) {
refuel()
}
finally {
if (initialSync)
refuel()
}
}
@ -138,7 +141,8 @@ trait SyncedInventory extends PersistedInventory with Logging {
if (syncFuel < 0) {
// ignore: the limit has already been reached
} else if (syncFuel == 0) {
}
else if (syncFuel == 0) {
logger.error(
s"Got trapped in an infinite loop while trying to synchronize the slot $slotIndex " +
s"in $this (class ${this.getClass.getName})!",
@ -158,7 +162,8 @@ trait SyncedInventory extends PersistedInventory with Logging {
logger.error("Breaking the loop forcefully by removing the items.")
Slot(slotIndex).remove()
brainInventory.inventory(slotIndex).remove()
} else {
}
else {
direction match {
case _ if checkSlotStatus(slotIndex) == SlotStatus.Synchronized =>

View File

@ -0,0 +1,34 @@
package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.{IconDef, Icons}
import ocelot.desktop.inventory.traits.ComponentItem
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
import totoro.ocelot.brain.entity.ComponentBus
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
import totoro.ocelot.brain.util.Tier.Tier
class ComponentBusItem(val componentBus: ComponentBus)
extends Item
with ComponentItem
{
override def component: Entity with Environment = componentBus
override def factory: ItemFactory = new ComponentBusItem.Factory(componentBus.tier)
}
object ComponentBusItem {
class Factory(_tier: Tier) extends ItemFactory {
override type I = ComponentBusItem
override def itemClass: Class[I] = classOf
override def name: String = s"ComponentBus (${_tier.label})"
override def tier: Option[Tier] = Some(_tier)
override def icon: IconDef = Icons.ComponentBus(_tier)
override def build(): ComponentBusItem = new ComponentBusItem(new ComponentBus(_tier))
override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new ComponentBusItem(_)))
}
}

View File

@ -0,0 +1,36 @@
package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.{IconDef, Icons}
import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer}
import ocelot.desktop.util.DiskDriveAware
import totoro.ocelot.brain.entity.traits.{Entity, RackMountable}
import totoro.ocelot.brain.entity.{DiskDriveMountable, FloppyDiskDrive}
import totoro.ocelot.brain.util.Tier.Tier
class DiskDriveMountableItem(val diskDriveMountable: DiskDriveMountable)
extends RackMountableItem
with DiskDriveAware
{
override def floppyDiskDrive: FloppyDiskDrive = diskDriveMountable
override def component: DiskDriveMountable = diskDriveMountable
override def factory: ItemFactory = DiskDriveMountableItem.Factory
}
object DiskDriveMountableItem {
object Factory extends ItemFactory {
override type I = DiskDriveMountableItem
override def itemClass: Class[I] = classOf
override def tier: Option[Tier] = None
override def name: String = "Disk drive"
override def icon: IconDef = Icons.DiskDriveMountable
override def build(): DiskDriveMountableItem = new DiskDriveMountableItem(new DiskDriveMountable())
override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new DiskDriveMountableItem(_)))
}
}

View File

@ -4,12 +4,12 @@ import ocelot.desktop.graphics.{IconDef, Icons}
import ocelot.desktop.inventory.traits.{CardItem, ComponentItem, PersistableItem}
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
import totoro.ocelot.brain.entity.NetworkCard
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
import totoro.ocelot.brain.entity.traits.Entity
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier
class NetworkCardItem(val networkCard: NetworkCard) extends Item with ComponentItem with PersistableItem with CardItem {
override def component: Entity with Environment = networkCard
class NetworkCardItem(val card: NetworkCard) extends Item with ComponentItem with PersistableItem with CardItem {
override def component: Entity with NetworkCard = card
override def factory: ItemFactory = NetworkCardItem.Factory
}

View File

@ -0,0 +1,23 @@
package ocelot.desktop.inventory.item
import ocelot.desktop.inventory.Item
import ocelot.desktop.inventory.traits.ComponentItem
import ocelot.desktop.node.nodes.RackNode
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import totoro.ocelot.brain.entity.{Rack, result}
import totoro.ocelot.brain.entity.traits.{Entity, RackMountable}
trait RackMountableItem
extends Item
with ComponentItem
{
override def component: Entity with RackMountable
def isInRack: Boolean = slot.exists(_.inventory.isInstanceOf[RackNode])
override def fillRmbMenu(menu: ContextMenu): Unit = {
RackNode.addContextMenuEntriesOfMountable(menu, Some(this))
super.fillRmbMenu(menu)
}
}

View File

@ -0,0 +1,127 @@
package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.{IconDef, Icons}
import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer}
import ocelot.desktop.ui.widget.slot._
import ocelot.desktop.util.ComputerAware
import totoro.ocelot.brain.entity.Server
import totoro.ocelot.brain.entity.traits.Inventory
import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier
class ServerItem(val server: Server)
extends RackMountableItem
with ComputerAware
{
override def component: Server = server
override def factory: ItemFactory = new ServerItem.Factory(server.tier)
override def update(): Unit = {
updateRunningSound()
}
override def onRemoved(): Unit = {
turnOff()
window.get.close()
super.onRemoved()
}
override def load(nbt: NBTTagCompound): Unit = {
super.load(nbt)
}
// ---------------------------- ComputerAware ----------------------------
override def computer: Server = server
override def brainInventory: Inventory = server.inventory.owner
override def addSlotsBasedOnTier(): Unit = {
floppySlot = None
server.tier match {
case Tier.One =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two))
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Two))
componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Two))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two))
eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case Tier.Two =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two))
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three))
componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Three), new ComponentBusSlotWidget(_, Tier.Three))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three))
eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case _ =>
cardSlots =
if (server.tier == Tier.Three) {
addSlotWidgets(
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Two),
new CardSlotWidget(_, Tier.Two)
)
}
else {
addSlotWidgets(
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
)
}
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three))
componentBusSlots = addSlotWidgets(
new ComponentBusSlotWidget(_, Tier.Three),
new ComponentBusSlotWidget(_, Tier.Three),
new ComponentBusSlotWidget(_, Tier.Three),
)
memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three)
)
diskSlots = addSlotWidgets(
new HddSlotWidget(_, Tier.Three),
new HddSlotWidget(_, Tier.Three),
new HddSlotWidget(_, Tier.Three),
new HddSlotWidget(_, Tier.Three),
)
eepromSlot = addSlotWidget(new EepromSlotWidget(_))
}
}
}
object ServerItem {
class Factory(_tier: Tier) extends ItemFactory {
override type I = ServerItem
override def itemClass: Class[I] = classOf
override def name: String = s"Server (${_tier.label})"
override def tier: Option[Tier] = Some(_tier)
override def icon: IconDef = Icons.Server(_tier)
override def build(): ServerItem = {
val item = new ServerItem(new Server(_tier))
item.fillSlotsWithDefaultItems()
item.syncSlots()
item
}
override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new ServerItem(_)))
}
}

View File

@ -1,20 +1,13 @@
package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.{IconDef, Icons}
import ocelot.desktop.inventory.traits.{CardItem, ComponentItem, PersistableItem}
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer}
import totoro.ocelot.brain.entity.WirelessNetworkCard
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier
abstract class WirelessNetworkCardItem(val card: WirelessNetworkCard)
extends Item
with ComponentItem
with PersistableItem
with CardItem {
override def component: Entity with Environment = card
abstract class WirelessNetworkCardItem(override val card: WirelessNetworkCard)
extends NetworkCardItem(card) {
}
object WirelessNetworkCardItem {

View File

@ -1,6 +1,5 @@
package ocelot.desktop.inventory.traits
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.window.Window

View File

@ -0,0 +1,88 @@
package ocelot.desktop.node
import ocelot.desktop.OcelotDesktop
import ocelot.desktop.audio._
import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.ComputerAwareNode.{ErrorMessageMoveSpeed, MaxErrorMessageDistance}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
import ocelot.desktop.ui.widget.ComputerErrorMessageLabel
import ocelot.desktop.util.{Logging, Messages}
import totoro.ocelot.brain.Settings
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
import totoro.ocelot.brain.event._
import scala.collection.mutable
abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
extends SyncedInventoryEntityNode(entity)
with DiskActivityHandler
with ShiftClickNode
{
private val messages = mutable.ArrayBuffer[(Float, ComputerErrorMessageLabel)]()
protected def addErrorMessage(message: ComputerErrorMessageLabel): Unit = synchronized {
messages += ((0f, message))
}
// TODO: Scala has lazy vals. Use them.
private var soundCardStream: SoundStream = _
private var soundCardSource: SoundSource = _
eventHandlers += {
case BrainEvent(event: MachineCrashEvent) =>
val message = Messages.lift(event.message) match {
case Some(message) =>
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message code ${event.message}: $message")
message
case None =>
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message: ${event.message}")
event.message
}
addErrorMessage(new ComputerErrorMessageLabel(this, message))
case BrainEvent(event: BeepEvent) if !Audio.isDisabled =>
BeepGenerator.newBeep(".", event.frequency, event.duration).play()
case BrainEvent(event: BeepPatternEvent) if !Audio.isDisabled =>
BeepGenerator.newBeep(event.pattern, 1000, 200).play()
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
if (soundCardStream == null) {
val (stream, source) = Audio.newStream(SoundCategory.Beep)
soundCardStream = stream
soundCardSource = source
}
soundCardStream.enqueue(samples)
soundCardSource.volume = event.volume
case BrainEvent(_: SelfDestructingCardBoomEvent) =>
OcelotDesktop.workspace.runLater(() => {
SoundSource.MinecraftExplosion.play()
destroy()
})
}
override protected val canOpen = true
override def drawParticles(g: Graphics): Unit = synchronized {
for ((time, message) <- messages.reverseIterator) {
message.position = message.initialPosition + Vector2D(0, -MaxErrorMessageDistance * time)
message.alpha = 1 - time
message.draw(g)
}
messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) }
messages.filterInPlace(_._1 <= 1f)
}
}
object ComputerAwareNode {
private val MaxErrorMessageDistance: Float = 50
private val ErrorMessageMoveSpeed: Float = 0.5f
}

View File

@ -2,6 +2,7 @@ package ocelot.desktop.node
import ocelot.desktop.OcelotDesktop
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import totoro.ocelot.brain.entity.traits.{Entity, Environment, SidedEnvironment}
import totoro.ocelot.brain.nbt.NBTTagCompound
@ -30,7 +31,7 @@ abstract class EntityNode(val entity: Entity with Environment) extends Node {
}
}
override def setupContextMenu(menu: ContextMenu): Unit = {
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
if (exposeAddress && entity.node != null && entity.node.address != null) {
menu.addEntry(
ContextMenuEntry("Copy address") {
@ -39,14 +40,15 @@ abstract class EntityNode(val entity: Entity with Environment) extends Node {
)
}
super.setupContextMenu(menu)
super.setupContextMenu(menu, event)
}
override def ports: Array[NodePort] = Array(NodePort())
override def getNodeByPort(port: NodePort): network.Node = entity.node
override def shouldReceiveEventsFor(address: String): Boolean = address == entity.node.address
override def shouldReceiveEventsFor(address: String): Boolean =
entity.node != null && address == entity.node.address
override def dispose(): Unit = {
super.dispose()

View File

@ -2,6 +2,7 @@ package ocelot.desktop.node
import ocelot.desktop.color.RGBAColor
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.InputDialog
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.util.DrawUtils
@ -24,9 +25,8 @@ trait LabeledNode extends Node {
nbt.setString("label", _label.getOrElse(""))
}
override def setupContextMenu(menu: ContextMenu): Unit = {
menu.addEntry(
ContextMenuEntry("Set label") {
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
menu.addEntry(ContextMenuEntry("Set label") {
new InputDialog(
"Set label",
text => {
@ -34,10 +34,9 @@ trait LabeledNode extends Node {
},
_label.getOrElse(""),
).show()
}
)
})
super.setupContextMenu(menu)
super.setupContextMenu(menu, event)
}
override def drawLabel(g: Graphics): Unit = {

View File

@ -3,13 +3,13 @@ package ocelot.desktop.node
import ocelot.desktop.color.{Color, RGBAColor}
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.Node.{HighlightSize, HoveredHighlight, MovingHighlight, NoHighlight, Size}
import ocelot.desktop.node.Node._
import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler}
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, HoverEvent, MouseEvent}
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.window.Window
import ocelot.desktop.ui.widget.window.Windowed
import ocelot.desktop.ui.widget.{Widget, WorkspaceView}
import ocelot.desktop.util.Persistable
import ocelot.desktop.util.Disposable
import ocelot.desktop.util.animation.ColorAnimation
import org.lwjgl.input.Keyboard
import totoro.ocelot.brain.nbt.NBTTagCompound
@ -17,7 +17,7 @@ import totoro.ocelot.brain.network
import scala.collection.mutable.ArrayBuffer
abstract class Node extends Widget with DragHandler with ClickHandler with HoverHandler with Persistable {
abstract class Node extends Widget with DragHandler with ClickHandler with HoverHandler with Windowed {
var workspaceView: WorkspaceView = _
protected val highlight = new ColorAnimation(RGBAColor(0, 0, 0, 0))
@ -31,23 +31,17 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
size = minimumSize
override def load(nbt: NBTTagCompound): Unit = {
super.load(nbt)
position = new Vector2D(nbt.getCompoundTag("pos"))
window.foreach(window => {
val tag = nbt.getCompoundTag("window")
window.load(tag)
})
}
override def save(nbt: NBTTagCompound): Unit = {
val posTag = new NBTTagCompound
position.save(posTag)
nbt.setTag("pos", posTag)
super.save(nbt)
window.foreach(window => {
val tag = new NBTTagCompound
window.save(tag)
nbt.setTag("window", tag)
})
position.save(tag)
nbt.setTag("pos", tag)
}
override def receiveMouseEvents = true
@ -92,7 +86,7 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
highlight.goto(NoHighlight)
}
def setupContextMenu(menu: ContextMenu): Unit = {
def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
if (ports.nonEmpty) {
menu.addEntry(
ContextMenuEntry("Disconnect") {
@ -182,9 +176,10 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
window.foreach(_.open())
else
window.foreach(_.close())
case ClickEvent(MouseEvent.Button.Right, _) =>
val menu = new ContextMenu
setupContextMenu(menu)
setupContextMenu(menu, event)
root.get.contextMenus.open(menu)
case _ =>
}
@ -272,10 +267,10 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
g.sprite(
icon,
position. x + HighlightSize,
position.y + HighlightSize,
size.width - HighlightSize * 2,
size.height - HighlightSize * 2,
position. x + HighlightThickness,
position.y + HighlightThickness,
size.width - HighlightThickness * 2,
size.height - HighlightThickness * 2,
iconColor
)
}
@ -294,9 +289,9 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
}
}
def window: Option[Window] = None
override def dispose(): Unit = {
super.dispose()
def dispose(): Unit = {
disconnectFromAll()
window.foreach(_.closeAndDispose())
}
@ -307,7 +302,10 @@ object Node {
protected val HoveredHighlight: RGBAColor = RGBAColor(160, 160, 160)
protected val NoHighlight: RGBAColor = RGBAColor(160, 160, 160, 0)
val Size = 68f
val HighlightSize = 2f
val SizeWithoutHighlight: Float = Size - HighlightSize * 2
val TexelCount = 16f
val Scale = 4f
val HighlightThickness = 2f
val NoHighlightSize: Float = TexelCount * Scale
val Size: Float = NoHighlightSize + HighlightThickness * 2f
}

View File

@ -12,7 +12,7 @@ case class NodePort(direction: Option[Direction.Value] = None) extends Ordered[N
case Some(Direction.South) => ColorScheme("PortSouth")
case Some(Direction.East) => ColorScheme("PortEast")
case Some(Direction.West) => ColorScheme("PortWest")
case None => ColorScheme("PortAny")
case _ => ColorScheme("PortAny")
}
override def compare(that: NodePort): Int = this.direction.compare(that.direction)

View File

@ -2,7 +2,7 @@ package ocelot.desktop.node
import ocelot.desktop.entity.{Camera, OpenFMRadio}
import ocelot.desktop.node.nodes._
import totoro.ocelot.brain.entity.{Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, NoteBlock, Relay, Screen}
import totoro.ocelot.brain.entity.{Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, NoteBlock, Rack, Relay, Screen}
import totoro.ocelot.brain.util.Tier
import scala.collection.mutable
@ -27,20 +27,30 @@ object NodeRegistry {
for (tier <- Tier.One to Tier.Three) {
addType(NodeType(s"Screen (${tier.label})", "nodes/screen/Standalone", tier) {
new ScreenNode(new Screen(tier)).setup()
val node = new ScreenNode(new Screen(tier))
node.attachKeyboard()
node
})
}
addType(NodeType("Disk Drive", "nodes/DiskDrive", None) {
new DiskDriveNode(new FloppyDiskDrive(), initDisk = true)
addType(NodeType("Disk Drive", "nodes/disk-drive/Default", None) {
val node = new DiskDriveNode(new FloppyDiskDrive())
node.fillSlotsWithDefaultItems()
node
})
for (tier <- Tier.One to Tier.Creative) {
addType(NodeType(s"Computer Case (${tier.label})", "nodes/Computer", tier) {
new ComputerNode(new Case(tier)).setup()
addType(NodeType(s"Computer Case (${tier.label})", "nodes/computer/Default", tier) {
val node = new ComputerNode(new Case(tier))
node.fillSlotsWithDefaultItems()
node
})
}
addType(NodeType("Rack", "nodes/rack/Default", None) {
new RackNode(new Rack)
})
addType(NodeType("Relay", "nodes/Relay", None) {
new RelayNode(new Relay)
})

View File

@ -0,0 +1,28 @@
package ocelot.desktop.node
import ocelot.desktop.ui.event.sources.KeyEvents
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
import org.lwjgl.input.Keyboard
trait ShiftClickNode extends Node {
protected def onShiftClick(event: ClickEvent): Unit
protected def hoveredShiftStatusBarText: String
override def onClick(event: ClickEvent): Unit = {
event match {
case ClickEvent(MouseEvent.Button.Left, _) if KeyEvents.isDown(Keyboard.KEY_LSHIFT) =>
onShiftClick(event)
return
case _ =>
}
super.onClick(event)
}
override def update(): Unit = {
super.update()
if (isHovered || isMoving)
root.get.statusBar.addKeyMouseEntry("icons/LMB", "SHIFT", hoveredShiftStatusBarText)
}
}

View File

@ -0,0 +1,28 @@
package ocelot.desktop.node
import ocelot.desktop.inventory.{Item, SyncedInventory}
import ocelot.desktop.ui.event.Event
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
abstract class SyncedInventoryEntityNode(entity: Entity with Environment)
extends EntityNode(entity)
with SyncedInventory
{
override def shouldReceiveEventsFor(address: String): Boolean =
super.shouldReceiveEventsFor(address) ||
brainInventory.inventory.entities.exists {
case env: Environment => env.node.address == address
}
override def handleEvent(event: Event): Unit = {
super.handleEvent(event)
inventoryIterator.foreach(slot =>
slot.get match {
case Some(item: Item) =>
item.handleEvent(event)
case _ =>
}
)
}
}

View File

@ -1,396 +1,121 @@
package ocelot.desktop.node.nodes
import ocelot.desktop.ColorScheme
import ocelot.desktop.audio._
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.inventory.item._
import ocelot.desktop.inventory.traits.ComponentItem
import ocelot.desktop.inventory.{Item, SyncedInventory}
import ocelot.desktop.node.{EntityNode, LabeledEntityNode}
import ocelot.desktop.node.nodes.ComputerNode.{ErrorMessageMoveSpeed, MaxErrorMessageDistance}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.sources.KeyEvents
import ocelot.desktop.ui.event.{BrainEvent, ClickEvent, MouseEvent}
import ocelot.desktop.ui.widget.Label
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu}
import ocelot.desktop.inventory.SyncedInventory
import ocelot.desktop.node.ComputerAwareNode
import ocelot.desktop.node.Node.HighlightThickness
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import ocelot.desktop.ui.widget.slot._
import ocelot.desktop.util.{Logging, Messages, TierColor}
import ocelot.desktop.util.{ComputerAware, DrawUtils, TierColor}
import ocelot.desktop.windows.ComputerWindow
import org.lwjgl.input.Keyboard
import totoro.ocelot.brain.Settings
import totoro.ocelot.brain.entity.Case
import totoro.ocelot.brain.entity.traits.{Environment, Inventory}
import totoro.ocelot.brain.event.FileSystemActivityType.Floppy
import totoro.ocelot.brain.event._
import totoro.ocelot.brain.loot.Loot
import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.entity.traits.{Computer, Inventory, TieredPersistable}
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier
import scala.collection.mutable
import scala.math.Ordering.Implicits.infixOrderingOps
import scala.reflect.ClassTag
import scala.util.Random
class ComputerNode(val computerCase: Case)
extends ComputerAwareNode(computerCase)
with ComputerAware
{
override val icon: String = "nodes/computer/Default"
override def iconColor: Color = TierColor.get(computerCase.tier)
class ComputerNode(val computer: Case)
extends EntityNode(computer)
with LabeledEntityNode
with Logging
with SyncedInventory {
node =>
override type I = Item with ComponentItem
var eepromSlot: EepromSlotWidget = _
var cpuSlot: CpuSlotWidget = _
var memorySlots: Array[MemorySlotWidget] = Array.empty
var cardSlots: Array[CardSlotWidget] = Array.empty
var diskSlots: Array[HddSlotWidget] = Array.empty
var floppySlot: Option[FloppySlotWidget] = None
override def brainInventory: Inventory = computer.inventory.owner
private def slots: IterableOnce[SlotWidget[I]] = (
// slots may be null during initialization
Option(eepromSlot).iterator ++
Option(cpuSlot).iterator ++
memorySlots.iterator ++
cardSlots.iterator ++
diskSlots.iterator ++
floppySlot.iterator
).map(_.asInstanceOf[SlotWidget[I]])
// NOTE: `soundComputerRunning` must be lazy so that it doesn't get loaded before the audio subsystem is initialized
private lazy val soundComputerRunning = SoundSource.fromBuffer(
SoundBuffers.MachineComputerRunning,
SoundCategory.Environment,
looping = true,
)
// TODO: Scala has lazy vals. Use them.
private var soundCardStream: SoundStream = _
private var soundCardSource: SoundSource = _
setupSlots()
private class ErrorMessageLabel(override val text: String) extends Label {
override def isSmall: Boolean = true
var alpha: Float = 1f
override def color: Color = ColorScheme("ErrorMessage").toRGBANorm.mapA(_ * alpha)
val initialPosition: Vector2D = {
val position = node.position
val size = node.size
position + Vector2D(size.width / 2 - minimumSize.width / 2, -minimumSize.height)
}
position = initialPosition
}
eventHandlers += {
case BrainEvent(event: MachineCrashEvent) =>
val message = Messages.lift(event.message) match {
case Some(message) =>
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message code ${event.message}: $message")
message
case None =>
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message: ${event.message}")
event.message
}
addErrorMessage(new ErrorMessageLabel(message))
case BrainEvent(event: BeepEvent) if !Audio.isDisabled =>
BeepGenerator.newBeep(".", event.frequency, event.duration).play()
case BrainEvent(event: BeepPatternEvent) if !Audio.isDisabled =>
BeepGenerator.newBeep(event.pattern, 1000, 200).play()
case BrainEvent(event: FileSystemActivityEvent) if !Audio.isDisabled =>
val soundFloppyAccess = SoundBuffers.MachineFloppyAccess
.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
val soundHDDAccess = SoundBuffers.MachineHDDAccess
.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
val sound = if (event.activityType == Floppy) soundFloppyAccess else soundHDDAccess
sound(Random.between(0, sound.length)).play()
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
if (soundCardStream == null) {
val (stream, source) = Audio.newStream(SoundCategory.Beep)
soundCardStream = stream
soundCardSource = source
}
soundCardStream.enqueue(samples)
soundCardSource.volume = event.volume
case BrainEvent(_: SelfDestructingCardBoomEvent) =>
computer.workspace.runLater(
() => {
SoundSources.MinecraftExplosion.play()
destroy()
},
)
}
override def shouldReceiveEventsFor(address: String): Boolean = super.shouldReceiveEventsFor(address) ||
computer.inventory.entities.exists { case env: Environment => env.node.address == address }
def setup(): ComputerNode = {
cpuSlot.item = new CpuItem.Factory(computer.tier min Tier.Three).build()
memorySlots(0).item = new MemoryItem.Factory((computer.tier min Tier.Three).toExtended(true)).build()
memorySlots(1).item = new MemoryItem.Factory((computer.tier min Tier.Three).toExtended(true)).build()
cardSlots(0).item = new GraphicsCardItem.Factory(computer.tier min Tier.Two).build()
for (floppySlot <- floppySlot) {
floppySlot.item = new FloppyItem.Factory.Loot(Loot.OpenOsFloppy).build()
}
eepromSlot.item = new EepromItem.Factory.Loot(Loot.LuaBiosEEPROM).build()
this
}
override val icon: String = "nodes/Computer"
override def iconColor: Color = TierColor.get(computer.tier)
override protected val canOpen = true
def turnOn(): Unit = {
computer.turnOn()
soundComputerRunning.play()
}
def turnOff(): Unit = {
computer.turnOff()
soundComputerRunning.stop()
}
def isRunning: Boolean = computer.machine.isRunning
override def setupContextMenu(menu: ContextMenu): Unit = {
if (isRunning) {
menu.addEntry(
ContextMenuEntry("Turn off") {
turnOff()
},
)
menu.addEntry(
ContextMenuEntry("Reboot") {
computer.turnOff()
computer.turnOn()
},
)
} else {
menu.addEntry(
ContextMenuEntry("Turn on") {
turnOn()
},
)
}
menu.addEntry(
new ContextMenuSubmenu("Set tier") {
for (tier <- Tier.One to Tier.Creative) {
addEntry(
ContextMenuEntry(tier.label) {
changeTier(tier)
},
)
}
},
)
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
addPowerContextMenuEntries(menu)
addTierContextMenuEntries(menu)
menu.addSeparator()
super.setupContextMenu(menu)
}
override def onClick(event: ClickEvent): Unit = {
event match {
case ClickEvent(MouseEvent.Button.Left, _) =>
if (KeyEvents.isDown(Keyboard.KEY_LSHIFT)) {
if (isRunning) {
turnOff()
} else {
turnOn()
}
} else {
super.onClick(event)
}
case event => super.onClick(event)
}
}
private def changeTier(tier: Tier): Unit = {
computer.tier = tier
val items = slots.iterator.flatMap(_.item).toArray
clearInventory()
setupSlots()
insertItems(items)
if (currentWindow != null) {
currentWindow.reloadWindow()
}
}
private def insertItems(items: IterableOnce[I]): Unit = {
def findBestSlot[A <: I](item: A, candidates: IterableOnce[SlotWidget[A]]): Option[SlotWidget[A]] = {
candidates.iterator
.filter(_.item.isEmpty)
.filter(_.isItemAccepted(item.factory))
.minByOption(_.slotTier)
}
for (item <- items; newSlot <- findBestSlot(item, slots)) {
newSlot.item = item
}
}
private def setupSlots(): Unit = {
var slotIndex = 0
def nextSlot(): Slot = {
val result = Slot(slotIndex)
slotIndex += 1
result
}
def addSlot[T <: SlotWidget[_]](factory: Slot => T): T = {
val slot = nextSlot()
val widget = factory(slot)
widget
}
def addSlots[T <: SlotWidget[_] : ClassTag](factories: (Slot => T)*): Array[T] = {
val array = Array.newBuilder[T]
for (factory <- factories) {
array += addSlot(factory)
}
array.result()
}
for (slot <- slots) {
slot.dispose()
}
computer.tier match {
case Tier.One =>
cardSlots = addSlots(new CardSlotWidget(_, Tier.One), new CardSlotWidget(_, Tier.One))
memorySlots = addSlots(new MemorySlotWidget(_, Tier.One))
diskSlots = addSlots(new HddSlotWidget(_, Tier.One))
floppySlot = None
cpuSlot = addSlot(new CpuSlotWidget(_, this, Tier.One))
// no idea why on earth the memory slots are split in two here
memorySlots :+= addSlot(new MemorySlotWidget(_, Tier.One))
eepromSlot = addSlot(new EepromSlotWidget(_))
case Tier.Two =>
cardSlots = addSlots(new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.One))
memorySlots = addSlots(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two))
diskSlots = addSlots(new HddSlotWidget(_, Tier.Two), new HddSlotWidget(_, Tier.One))
floppySlot = None
cpuSlot = addSlot(new CpuSlotWidget(_, this, Tier.Two))
eepromSlot = addSlot(new EepromSlotWidget(_))
case _ =>
cardSlots = if (computer.tier == Tier.Three) {
addSlots(new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two))
} else {
addSlots(
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
)
}
memorySlots = addSlots(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three))
diskSlots = if (computer.tier == Tier.Three) {
addSlots(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Two))
} else {
addSlots(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three))
}
floppySlot = Some(addSlot(new FloppySlotWidget(_)))
cpuSlot = addSlot(new CpuSlotWidget(_, this, Tier.Three))
eepromSlot = addSlot(new EepromSlotWidget(_))
}
}
override def draw(g: Graphics): Unit = {
super.draw(g)
val hasErred = computer.machine.lastError != null
if (isRunning && !hasErred)
g.sprite("nodes/ComputerOnOverlay", position.x + 2, position.y + 2, size.width - 4, size.height - 4)
if (!isRunning && hasErred)
g.sprite("nodes/ComputerErrorOverlay", position.x + 2, position.y + 2, size.width - 4, size.height - 4)
if (isRunning && System.currentTimeMillis() - computer.machine.lastDiskAccess < 400 && Math.random() > 0.1)
g.sprite("nodes/ComputerActivityOverlay", position.x + 2, position.y + 2, size.width - 4, size.height - 4)
super.setupContextMenu(menu, event)
}
override def update(): Unit = {
super.update()
if (!isRunning && soundComputerRunning.isPlaying) {
soundComputerRunning.stop()
} else if (isRunning && !soundComputerRunning.isPlaying && !Audio.isDisabled) {
soundComputerRunning.play()
}
if (isHovered || isMoving) {
root.get.statusBar.addKeyMouseEntry("icons/LMB", "SHIFT", if (isRunning) "Turn Off" else "Turn On")
}
updateRunningSound()
}
override def dispose(): Unit = {
super.dispose()
soundComputerRunning.stop()
}
private var currentWindow: ComputerWindow = _
override def draw(g: Graphics): Unit = {
super.draw(g)
override def window: Option[ComputerWindow] = {
if (currentWindow == null) {
currentWindow = new ComputerWindow(this)
Some(currentWindow)
} else Some(currentWindow)
DrawUtils.drawComputerNodeActivity(
g,
position.x + HighlightThickness,
position.y + HighlightThickness,
computerCase.machine,
"nodes/computer/"
)
}
private val messages = mutable.ArrayBuffer[(Float, ErrorMessageLabel)]()
// ---------------------------- ComputerAware ----------------------------
private def addErrorMessage(message: ErrorMessageLabel): Unit = synchronized {
messages += ((0f, message))
override def computer: Case = computerCase
override def brainInventory: Inventory = computerCase.inventory.owner
override def addSlotsBasedOnTier(): Unit = {
computerCase.tier match {
case Tier.One =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.One), new CardSlotWidget(_, Tier.One))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.One))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.One))
floppySlot = None
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.One))
// no idea why on earth the memory slots are split in two here
memorySlots :+= addSlotWidget(new MemorySlotWidget(_, Tier.One))
eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case Tier.Two =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.One))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Two), new MemorySlotWidget(_, Tier.Two))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Two), new HddSlotWidget(_, Tier.One))
floppySlot = None
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Two))
eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case _ =>
cardSlots =
if (computerCase.tier == Tier.Three) {
addSlotWidgets(
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Two),
new CardSlotWidget(_, Tier.Two)
)
}
else {
addSlotWidgets(
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three),
)
}
override def drawParticles(g: Graphics): Unit = synchronized {
for ((time, message) <- messages.reverseIterator) {
message.position = message.initialPosition + Vector2D(0, -MaxErrorMessageDistance * time)
message.alpha = 1 - time
message.draw(g)
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three))
diskSlots =
if (computerCase.tier == Tier.Three) {
addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Two))
}
else {
addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three))
}
messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) }
messages.filterInPlace(_._1 <= 1f)
floppySlot = Some(addSlotWidget(new FloppySlotWidget(_)))
cpuSlot = addSlotWidget(new CpuSlotWidget(_, this, Tier.Three))
eepromSlot = addSlotWidget(new EepromSlotWidget(_))
}
}
object ComputerNode {
private val MaxErrorMessageDistance: Float = 50
private val ErrorMessageMoveSpeed: Float = 0.5f
// ---------------------------- ShiftClickNode ----------------------------
override protected def onShiftClick(event: ClickEvent): Unit = toggleIsTurnedOn()
override protected def hoveredShiftStatusBarText: String = if (computer.machine.isRunning) "Turn off" else "Turn on"
}

View File

@ -1,77 +1,58 @@
package ocelot.desktop.node.nodes
import ocelot.desktop.color.IntColor
import ocelot.desktop.geometry.Rect2D
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.inventory.SyncedInventory
import ocelot.desktop.inventory.item.FloppyItem
import ocelot.desktop.node.{EntityNode, LabeledEntityNode}
import ocelot.desktop.ui.widget.slot.FloppySlotWidget
import ocelot.desktop.windows.DiskDriveWindow
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize}
import ocelot.desktop.node.{LabeledEntityNode, ShiftClickNode, SyncedInventoryEntityNode}
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import ocelot.desktop.util.DiskDriveAware
import totoro.ocelot.brain.entity.FloppyDiskDrive
import totoro.ocelot.brain.entity.traits.Inventory
import totoro.ocelot.brain.loot.Loot
import totoro.ocelot.brain.util.DyeColor
class DiskDriveNode(val diskDrive: FloppyDiskDrive, initDisk: Boolean)
extends EntityNode(diskDrive)
class DiskDriveNode(entity: FloppyDiskDrive)
extends SyncedInventoryEntityNode(entity)
with LabeledEntityNode
with SyncedInventory {
def this(diskDrive: FloppyDiskDrive) = {
this(diskDrive, false)
}
override type I = FloppyItem
override def brainInventory: Inventory = diskDrive.inventory.owner
val slot: FloppySlotWidget = new FloppySlotWidget(Slot(0))
if (initDisk) {
slot.item = new FloppyItem.Factory.Loot(Loot.OpenOsFloppy).build()
}
override def icon: String = "nodes/DiskDrive"
with DiskDriveAware
with DiskActivityHandler
with ShiftClickNode
{
override def icon: String = "nodes/disk-drive/Default"
override protected val canOpen = true
private val colorMap: Map[DyeColor, Int] = Map(
DyeColor.Black -> 0x444444, // 0x1E1B1B
DyeColor.Red -> 0xB3312C,
DyeColor.Green -> 0x339911, // 0x3B511A
DyeColor.Brown -> 0x51301A,
DyeColor.Blue -> 0x6666FF, // 0x253192
DyeColor.Purple -> 0x7B2FBE,
DyeColor.Cyan -> 0x66FFFF, // 0x287697
DyeColor.Silver -> 0xABABAB,
DyeColor.Gray -> 0x666666, // 0x434343
DyeColor.Pink -> 0xD88198,
DyeColor.Lime -> 0x66FF66, // 0x41CD34
DyeColor.Yellow -> 0xFFFF66, // 0xDECF2A
DyeColor.LightBlue -> 0xAAAAFF, // 0x6689D3
DyeColor.Magenta -> 0xC354CD,
DyeColor.Orange -> 0xEB8844,
DyeColor.White -> 0xF0F0F0
)
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
addDiskDriveMenuEntries(menu)
menu.addSeparator()
super.setupContextMenu(menu, event)
}
override def draw(g: Graphics): Unit = {
super.draw(g)
if (System.currentTimeMillis() - diskDrive.lastDiskAccess < 400 && Math.random() > 0.1) {
g.sprite("nodes/DiskDriveActivity", position.x + 2, position.y + 2, size.width - 4, size.height - 4)
}
for (item <- slot.item) {
g.sprite(
"nodes/DiskDriveFloppy",
position.x + 2,
position.y + 2,
size.width - 4,
size.height - 4,
IntColor(colorMap(item.color.value)),
drawActivityAndFloppy(
g,
Rect2D(
position.x + HighlightThickness,
position.y + HighlightThickness,
NoHighlightSize,
NoHighlightSize
),
"nodes/disk-drive/"
)
}
// ---------------------------- DiskDriveAware ----------------------------
override def floppyDiskDrive: FloppyDiskDrive = entity
// ---------------------------- ShiftClickNode ----------------------------
override protected def onShiftClick(event: ClickEvent): Unit = {
if (isFloppyItemPresent)
eject()
}
override val window: Option[DiskDriveWindow] = Some(new DiskDriveWindow(this))
override protected def hoveredShiftStatusBarText: String = "Eject floppy"
}

View File

@ -1,12 +1,13 @@
package ocelot.desktop.node.nodes
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu}
import totoro.ocelot.brain.entity.NoteBlock
class NoteBlockNode(val noteBlock: NoteBlock) extends NoteBlockNodeBase(noteBlock) {
override def icon: String = "nodes/NoteBlock"
override def setupContextMenu(menu: ContextMenu): Unit = {
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
menu.addEntry(new ContextMenuSubmenu("Instrument") {
{
val maxLen = NoteBlockNode.Instruments.map(_._2.length).max
@ -20,7 +21,8 @@ class NoteBlockNode(val noteBlock: NoteBlock) extends NoteBlockNodeBase(noteBloc
})
menu.addSeparator()
super.setupContextMenu(menu)
super.setupContextMenu(menu, event)
}
}

View File

@ -0,0 +1,200 @@
package ocelot.desktop.node.nodes
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.{Graphics, Icons}
import ocelot.desktop.inventory.item.{DiskDriveMountableItem, RackMountableItem, ServerItem}
import ocelot.desktop.inventory.{Item, SyncedInventory}
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size, TexelCount}
import ocelot.desktop.node.{ComputerAwareNode, NodePort}
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.window.Window
import ocelot.desktop.util.DrawUtils
import ocelot.desktop.windows.RackWindow
import totoro.ocelot.brain.entity.Rack
import totoro.ocelot.brain.entity.traits.{ComponentInventory, Environment, Inventory, RackMountable, WorkspaceAware}
import totoro.ocelot.brain.network
import totoro.ocelot.brain.util.Direction
class RackNode(val rack: Rack)
extends ComputerAwareNode(rack)
{
override val icon: String = "nodes/rack/Empty"
override def exposeAddress = false
override def ports: Array[NodePort] = Array(
NodePort(Some(Direction.Bottom)),
NodePort(Some(Direction.Top)),
NodePort(Some(Direction.Back)),
NodePort(Some(Direction.Right)),
NodePort(Some(Direction.Left))
)
override def getNodeByPort(port: NodePort): network.Node = rack.sidedNode(port.direction.get)
override def shouldReceiveEventsFor(address: String): Boolean =
super.shouldReceiveEventsFor(address) ||
rack.inventory.entities.exists {
case mountable: RackMountable if mountable.node.address == address => true
case mountable: RackMountable with ComponentInventory => mountable.inventory.entities.exists {
case environment: Environment => environment.node.address == address
}
case _ => false
}
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
RackNode.addContextMenuEntriesOfMountable(menu, getMountableByClick(event))
super.setupContextMenu(menu, event)
}
override def draw(g: Graphics): Unit = {
super.draw(g)
val x = position.x + HighlightThickness
val y = position.y + HighlightThickness
var prefix: String = null
for (i <- 0 until 4) {
Slot(i).get match {
case Some(serverItem: ServerItem) =>
prefix = s"nodes/rack/server/$i/"
// Background
g.sprite(
s"${prefix}Default",
x,
y,
NoHighlightSize,
NoHighlightSize
)
// Activity overlay
DrawUtils.drawComputerNodeActivity(
g,
x,
y,
serverItem.server.machine,
prefix
)
case Some(diskDriveMountableItem: DiskDriveMountableItem) =>
prefix = s"nodes/rack/drive/$i/"
// Background
g.sprite(
s"${prefix}Default",
x,
y,
NoHighlightSize,
NoHighlightSize
)
diskDriveMountableItem.drawActivityAndFloppy(
g,
Rect2D(
x,
y,
NoHighlightSize,
NoHighlightSize
),
prefix
)
case _ =>
}
}
}
override def dispose(): Unit = {
for (i <- 0 until 4) {
Slot(i).get match {
case Some(serverItem: ServerItem) => serverItem.dispose()
case _=>
}
}
super.dispose()
}
// -------------------------------- Inventory --------------------------------
override type I = Item with RackMountableItem
override def brainInventory: Inventory = rack.inventory.owner
// -------------------------------- Window --------------------------------
private lazy val currentWindow = new RackWindow(this)
override def window: Option[Window] = Some(currentWindow)
// ---------------------------- ShiftClickNode ----------------------------
private def getMountableByClick(event: ClickEvent): Option[RackMountableItem] = {
val horizontalMargin = HighlightThickness + (1 / TexelCount) * Size
val verticalMargin = HighlightThickness + (2 / TexelCount) * Size
val localPosition = Vector2D(
event.mousePos.x - position.x - horizontalMargin,
event.mousePos.y - position.y - verticalMargin
)
val m = Size2D(
this.width - horizontalMargin * 2,
this.height - verticalMargin * 2
)
// Checking if click was inside mountables area
if (localPosition.x < 0 || localPosition.y < 0 || localPosition.x > m.width || localPosition.y > m.height)
return None
val mountableIndex = (localPosition.y / m.height * 4).toInt
Slot(mountableIndex).get.collect {
case item: RackMountableItem => item
}
}
override protected def onShiftClick(event: ClickEvent): Unit = {
getMountableByClick(event) match {
case Some(serverItem: ServerItem) =>
serverItem.toggleIsTurnedOn()
case Some(diskDriveMountableItem: DiskDriveMountableItem) =>
if (diskDriveMountableItem.isFloppyItemPresent)
diskDriveMountableItem.eject()
case _ =>
}
}
override protected def hoveredShiftStatusBarText: String = "Turn server on/off"
}
object RackNode {
def addContextMenuEntriesOfMountable(menu: ContextMenu, item: Option[RackMountableItem]): Unit = {
item match {
case Some(serverItem: ServerItem) =>
if (serverItem.isInRack) {
serverItem.addPowerContextMenuEntries(menu)
menu.addSeparator()
}
serverItem.addTierContextMenuEntries(menu)
menu.addEntry(ContextMenuEntry("Configure server") {
serverItem.window.get.open()
})
menu.addSeparator()
case Some(diskDriveMountableItem: DiskDriveMountableItem) =>
if (diskDriveMountableItem.isFloppyItemPresent) {
diskDriveMountableItem.addDiskDriveMenuEntries(menu)
menu.addSeparator()
}
case _ =>
}
}
}

View File

@ -1,16 +1,17 @@
package ocelot.desktop.node.nodes
import ocelot.desktop.{OcelotDesktop, Settings}
import ocelot.desktop.color.{Color, IntColor}
import ocelot.desktop.geometry.{Rect2D, Size2D}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.Node.{HighlightSize, Size, SizeWithoutHighlight}
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size}
import ocelot.desktop.node.nodes.ScreenNode.{BorderSize, FontHeight, FontWidth}
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, Node}
import ocelot.desktop.node.{EntityNode, LabeledEntityNode}
import ocelot.desktop.ui.event.ClickEvent
import ocelot.desktop.ui.widget.ScreenAspectRatioDialog
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu}
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.util.TierColor
import ocelot.desktop.windows.ScreenWindow
import ocelot.desktop.{OcelotDesktop, Settings}
import totoro.ocelot.brain.entity.{Keyboard, Screen}
import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.util.PackedColor
@ -50,12 +51,11 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
}
}
def setup(): ScreenNode = {
def attachKeyboard(): Unit = {
val kbd = new Keyboard
OcelotDesktop.workspace.add(kbd)
screen.connect(kbd)
keyboard = Some(kbd)
this
}
override def icon: String = "nodes/screen/Standalone"
@ -64,7 +64,7 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
override protected val canOpen = true
override def setupContextMenu(menu: ContextMenu): Unit = {
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
if (screen.getPowerState)
menu.addEntry(ContextMenuEntry("Turn off") { screen.setPowerState(false) })
else
@ -76,7 +76,7 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
menu.addSeparator()
super.setupContextMenu(menu)
super.setupContextMenu(menu, event)
}
def drawScreenData(g: Graphics, startX: Float, startY: Float, scaleX: Float, scaleY: Float): Unit = {
@ -131,10 +131,10 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
if (aspectRatioHeight == 1) {
drawScreenPart(
"nodes/screen/Standalone",
position.x + HighlightSize,
position.y + HighlightSize,
SizeWithoutHighlight,
SizeWithoutHighlight,
position.x + HighlightThickness,
position.y + HighlightThickness,
NoHighlightSize,
NoHighlightSize,
)
}
// 1 x n
@ -142,19 +142,19 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Top
drawScreenPart(
"nodes/screen/ColumnTop",
position.x + HighlightSize,
position.y + HighlightSize,
SizeWithoutHighlight,
Size - HighlightSize,
position.x + HighlightThickness,
position.y + HighlightThickness,
NoHighlightSize,
Size - HighlightThickness,
)
// Middle
for (y <- 1 until aspectRatioHeight - 1) {
drawScreenPart(
"nodes/screen/ColumnMiddle",
position.x + HighlightSize,
position.x + HighlightThickness,
position.y + y * Size,
SizeWithoutHighlight,
NoHighlightSize,
Size
)
}
@ -162,10 +162,10 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Bottom
drawScreenPart(
"nodes/screen/ColumnBottom",
position.x + HighlightSize,
position.x + HighlightThickness,
position.y + (aspectRatioHeight - 1) * Size,
SizeWithoutHighlight,
SizeWithoutHighlight,
NoHighlightSize,
NoHighlightSize,
)
}
}
@ -176,10 +176,10 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Left
drawScreenPart(
"nodes/screen/RowLeft",
position.x + HighlightSize,
position.y + HighlightSize,
Size - HighlightSize,
SizeWithoutHighlight,
position.x + HighlightThickness,
position.y + HighlightThickness,
Size - HighlightThickness,
NoHighlightSize,
)
// Middle
@ -187,18 +187,18 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
drawScreenPart(
"nodes/screen/RowMiddle",
position.x + x * Size,
position.y + HighlightSize,
position.y + HighlightThickness,
Size,
SizeWithoutHighlight
NoHighlightSize
)
// Right
drawScreenPart(
"nodes/screen/RowRight",
position.x + (aspectRatioWidth - 1) * Size,
position.y + HighlightSize,
Size - HighlightSize,
SizeWithoutHighlight
position.y + HighlightThickness,
Size - HighlightThickness,
NoHighlightSize
)
}
// n x n
@ -207,7 +207,7 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Left
drawScreenPart(
leftName,
position.x + HighlightSize,
position.x + HighlightThickness,
y,
Size,
height
@ -226,7 +226,7 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Right
drawScreenPart(
rightName,
position.x + (aspectRatioWidth - 1) * Size - HighlightSize,
position.x + (aspectRatioWidth - 1) * Size - HighlightThickness,
y,
Size,
height
@ -235,8 +235,8 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Top
drawLine(
position.y + HighlightSize,
Size - HighlightSize,
position.y + HighlightThickness,
Size - HighlightThickness,
"nodes/screen/TopLeft",
"nodes/screen/TopMiddle",
"nodes/screen/TopRight"
@ -255,7 +255,7 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
// Bottom
drawLine(
position.y + (aspectRatioHeight - 1) * Size,
Size - HighlightSize,
Size - HighlightThickness,
"nodes/screen/BottomLeft",
"nodes/screen/BottomMiddle",
"nodes/screen/BottomRight"
@ -289,11 +289,10 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
screen.getHeight * FontHeight
)
var scale = virtualScreenBounds.w / pixelDataSize.width
val scaleY = virtualScreenBounds.h / pixelDataSize.height
if (scaleY < scale)
scale = scaleY
val scale = Math.min(
virtualScreenBounds.w / pixelDataSize.width,
virtualScreenBounds.h / pixelDataSize.height
)
// Drawing pixel data
drawScreenData(
@ -308,10 +307,10 @@ class ScreenNode(val screen: Screen) extends EntityNode(screen) with LabeledEnti
else {
g.sprite(
"nodes/screen/PowerOnOverlay",
position.x + HighlightSize,
position.y + HighlightSize,
SizeWithoutHighlight,
SizeWithoutHighlight
position.x + HighlightThickness,
position.y + HighlightThickness,
NoHighlightSize,
NoHighlightSize
)
}
}

View File

@ -183,7 +183,7 @@ object UiHandler extends Logging {
def init(): Unit = {
scalingFactor = Settings.get.scaleFactor
fullScreen = Settings.get.windowFullscreen
windowTitle = "Ocelot Desktop v" + BuildInfo.version
windowTitle = "Ocelot Desktop"
loadIcons()
@ -375,13 +375,13 @@ object UiHandler extends Logging {
hierarchy.reverseIterator.foreach(_.handleEvent(event))
for (event <- MouseEvents.events)
hierarchy.reverseIterator.filter(_.receiveAllMouseEvents).foreach(_.handleEvent(event))
hierarchy.reverseIterator.filter(w => w.enabled && w.receiveAllMouseEvents).foreach(_.handleEvent(event))
val scrollTarget = hierarchy.reverseIterator
.find(w => w.receiveScrollEvents && w.clippedBounds.contains(mousePos))
val mouseTarget = hierarchy.reverseIterator
.find(w => w.receiveMouseEvents && w.clippedBounds.contains(mousePos))
.find(w => w.enabled && w.receiveMouseEvents && w.clippedBounds.contains(mousePos))
for (event <- ScrollEvents.events)
scrollTarget.foreach(_.handleEvent(event))

View File

@ -0,0 +1,11 @@
package ocelot.desktop.ui.event
import ocelot.desktop.ui.widget.EventHandlers
trait EventAware {
protected val eventHandlers = new EventHandlers
def handleEvent(event: Event): Unit = {
eventHandlers(event)
}
}

View File

@ -0,0 +1,23 @@
package ocelot.desktop.ui.event.handlers
import ocelot.desktop.audio.{Audio, SoundBuffers, SoundCategory, SoundSource}
import ocelot.desktop.ui.event.{BrainEvent, EventAware}
import totoro.ocelot.brain.event.FileSystemActivityEvent
import totoro.ocelot.brain.event.FileSystemActivityType.Floppy
import scala.util.Random
trait DiskActivityHandler extends EventAware {
eventHandlers += {
case BrainEvent(event: FileSystemActivityEvent) if !Audio.isDisabled =>
val sound =
if (event.activityType == Floppy)
SoundBuffers.MachineFloppyAccess
.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
else
SoundBuffers.MachineHDDAccess
.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
sound(Random.between(0, sound.length)).play()
}
}

View File

@ -1,7 +1,7 @@
package ocelot.desktop.ui.widget
import ocelot.desktop.ColorScheme
import ocelot.desktop.audio.{SoundSource, SoundSources}
import ocelot.desktop.audio.SoundSource
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Size2D
import ocelot.desktop.graphics.Graphics
@ -14,8 +14,6 @@ class Button extends Widget with ClickHandler with ClickSoundSource {
def onClick(): Unit = {}
def enabled: Boolean = true
override def receiveMouseEvents: Boolean = true
eventHandlers += {
@ -49,5 +47,5 @@ class Button extends Widget with ClickHandler with ClickSoundSource {
g.text(position.x + ((width - textWidth) / 2).round, position.y + 4, text)
}
override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClick
override protected def clickSoundSource: SoundSource = SoundSource.InterfaceClick
}

View File

@ -1,7 +1,7 @@
package ocelot.desktop.ui.widget
import ocelot.desktop.OcelotDesktop
import ocelot.desktop.audio.{SoundSource, SoundSources}
import ocelot.desktop.audio.SoundSource
import ocelot.desktop.geometry.Padding2D
import ocelot.desktop.ui.layout.LinearLayout
import ocelot.desktop.ui.widget.ChangeSimulationSpeedDialog.validateIntervalUs
@ -87,7 +87,7 @@ class ChangeSimulationSpeedDialog() extends ModalDialog {
children :+= new PaddingBox(new Button {
override def text: String = "Cancel"
override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClickLow
override protected def clickSoundSource: SoundSource = SoundSource.InterfaceClickLow
override def onClick(): Unit = close()
}, Padding2D(right = 8))

View File

@ -1,6 +1,6 @@
package ocelot.desktop.ui.widget
import ocelot.desktop.audio.{SoundSource, SoundSources}
import ocelot.desktop.audio.SoundSource
import ocelot.desktop.geometry.Padding2D
import ocelot.desktop.ui.layout.LinearLayout
import ocelot.desktop.ui.widget.modal.ModalDialog
@ -19,7 +19,7 @@ class CloseConfirmationDialog extends ModalDialog {
children :+= new Widget {
children :+= new PaddingBox(new Button {
override def text: String = "Cancel"
override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClickLow
override protected def clickSoundSource: SoundSource = SoundSource.InterfaceClickLow
override def onClick(): Unit = close()
}, Padding2D(left = 8))

View File

@ -0,0 +1,23 @@
package ocelot.desktop.ui.widget
import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.node.Node
class ComputerErrorMessageLabel(node: Node, override val text: String) extends Label {
override def isSmall: Boolean = true
var alpha: Float = 1f
override def color: Color = ColorScheme("ErrorMessage").toRGBANorm.mapA(_ * alpha)
val initialPosition: Vector2D = {
val position = node.position
val size = node.size
position + Vector2D(size.width / 2 - minimumSize.width / 2, -minimumSize.height)
}
position = initialPosition
}

View File

@ -2,26 +2,14 @@ package ocelot.desktop.ui.widget
import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Size2D
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.ui.event.HoverEvent
import ocelot.desktop.ui.event.handlers.HoverHandler
import ocelot.desktop.ui.widget.tooltip.Tooltip
class Histogram extends Widget with HoverHandler {
override def minimumSize: Size2D = Size2D(274, 70)
override def maximumSize: Size2D = minimumSize
import scala.collection.mutable.ArrayBuffer
var text = "N/A"
var history: Array[Float] = Seq.fill(21)(0.0f).toArray
override def receiveMouseEvents = true
eventHandlers += {
case HoverEvent(state) =>
if (state == HoverEvent.State.Enter) onHoverEnter()
else onHoverLeave()
}
class Histogram extends Widget {
var value = "N/A"
var title = "Hello world"
var history: ArrayBuffer[Float] = ArrayBuffer(50)
private def drawBars(g: Graphics): Unit = {
def drawBarSegment(i: Int, color: Color): Unit = {
@ -33,52 +21,90 @@ class Histogram extends Widget with HoverHandler {
val fillBars = (ratio * 10).round
val emptyBars = (9 - fillBars).max(0)
for (i <- 0 until emptyBars) {
for (i <- 0 until emptyBars)
drawBarSegment(i, ColorScheme("HistogramBarEmpty"))
}
for (i <- emptyBars + 1 until 10) {
for (i <- emptyBars + 1 until 10)
drawBarSegment(i, ColorScheme("HistogramBarFill"))
}
drawBarSegment(emptyBars, ColorScheme("HistogramBarTop"))
drawText(g, position.x + 17, value)
}
private def drawText(g: Graphics): Unit = {
private def drawText(g: Graphics, x: Float, text: String): Unit = {
g.setSmallFont()
g.text(position.x + 17 - text.length * 4, position.y + 62, text)
g.text(x - text.length * 4, position.y + 62, text)
g.setNormalFont()
}
private def drawHistogram(g: Graphics): Unit = {
for (i <- 0 until 22) {
g.rect(position.x + 41 + i * 11, position.y, 2, 57, ColorScheme("HistogramGrid"))
}
for (i <- 0 until 6) {
g.rect(position.x + 41, position.y + i * 11, 233, 2, ColorScheme("HistogramGrid"))
val marginLeft = 41
val marginBottom = 12
var x = position.x + marginLeft
val cellThickness = 2f
val cellSize = 11f
val horizontalLineCount = ((height - cellThickness - marginBottom) / cellSize).toInt
val verticalLineCount = ((width - cellThickness - marginLeft) / cellSize).toInt
val gridWidth = verticalLineCount * cellSize
val gridHeight = horizontalLineCount * cellSize
// Text
drawText(g, x + gridWidth / 2, title)
// Horizontal lines
for (i <- 0 until horizontalLineCount + 1)
g.rect(x, position.y + i * cellSize, gridWidth, cellThickness, ColorScheme("HistogramGrid"))
// Additional line closes grid from right
for (i <- 0 until verticalLineCount + 1) {
val lesserThenPreLast = i < verticalLineCount - 1
// Vertical line
g.rect(
x,
position.y,
cellThickness,
if (lesserThenPreLast) gridHeight else gridHeight + cellThickness,
ColorScheme("HistogramGrid")
)
// History
val historyIndex = history.length - verticalLineCount + i - 1
val historyValueWidth = if (i < verticalLineCount) cellSize else cellThickness
var historyValueHeight: Float = cellThickness
// Value fill
if (historyIndex > 0) {
val historyValue = history(historyIndex)
historyValueHeight = (historyValue * (gridHeight + cellThickness)).max(cellThickness)
g.rect(
x,
position.y + gridHeight + cellThickness - historyValueHeight,
historyValueWidth,
historyValueHeight,
ColorScheme("HistogramFill")
)
}
for ((entry, i) <- history.zipWithIndex) {
val width = if (i == 20) 13 else 11
val height = (entry * 57).max(2)
g.rect(position.x + 41 + i * 11, position.y + 57 - height, width, 2, ColorScheme("HistogramEdge"))
g.rect(position.x + 41 + i * 11, position.y + 59 - height, width, height - 2, ColorScheme("HistogramFill"))
}
}
// Value line
g.rect(
x,
position.y + gridHeight + cellThickness - historyValueHeight,
historyValueWidth,
cellThickness,
ColorScheme("HistogramEdge")
)
protected val tooltip: Option[Tooltip] = None
def onHoverEnter(): Unit = {
if (tooltip.isDefined)
root.get.tooltipPool.addTooltip(tooltip.get)
x += cellSize
}
def onHoverLeave(): Unit = {
if (tooltip.isDefined)
root.get.tooltipPool.closeTooltip(tooltip.get)
}
override def draw(g: Graphics): Unit = {
drawBars(g)
drawText(g)
drawHistogram(g)
}
}

View File

@ -1,7 +1,7 @@
package ocelot.desktop.ui.widget
import ocelot.desktop.ColorScheme
import ocelot.desktop.audio.{SoundSource, SoundSources}
import ocelot.desktop.audio.SoundSource
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Size2D
import ocelot.desktop.graphics.Graphics
@ -143,7 +143,7 @@ class IconButton(
alphaAnimation.update()
}
override protected def clickSoundSource: SoundSource = SoundSources.InterfaceClick
override protected def clickSoundSource: SoundSource = SoundSource.InterfaceClick
}
object IconButton {

Some files were not shown because too many files have changed in this diff Show More