mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Merge branch 'feature/better-text-edit' into 'develop'
Better text edit See merge request cc-ru/ocelot/ocelot-desktop!120
This commit is contained in:
commit
6db5ff3f37
BIN
sprites/icons/Cut.png
Normal file
BIN
sprites/icons/Cut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 239 B |
BIN
sprites/icons/Paste.png
Normal file
BIN
sprites/icons/Paste.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 B |
@ -68,7 +68,9 @@ TextInputBorderDisabled = #666666
|
|||||||
TextInputBorderFocused = #336666
|
TextInputBorderFocused = #336666
|
||||||
TextInputBackground = #aaaaaa
|
TextInputBackground = #aaaaaa
|
||||||
TextInputBackgroundActive = #bbbbbb
|
TextInputBackgroundActive = #bbbbbb
|
||||||
|
TextInputBackgroundSelected = #336666
|
||||||
TextInputForeground = #333333
|
TextInputForeground = #333333
|
||||||
|
TextInputForegroundSelected = #aaaaaa
|
||||||
TextInputForegroundDisabled = #888888
|
TextInputForegroundDisabled = #888888
|
||||||
TextInputBorderError = #aa8888
|
TextInputBorderError = #aa8888
|
||||||
TextInputBorderErrorDisabled = #aa8888
|
TextInputBorderErrorDisabled = #aa8888
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 150 KiB |
@ -47,46 +47,48 @@ icons/Code 321 632 16 16
|
|||||||
icons/ComponentBus 338 632 16 16
|
icons/ComponentBus 338 632 16 16
|
||||||
icons/Copy 355 632 16 16
|
icons/Copy 355 632 16 16
|
||||||
icons/Cross 372 632 16 16
|
icons/Cross 372 632 16 16
|
||||||
icons/Delete 389 632 16 16
|
icons/Cut 389 632 16 16
|
||||||
|
icons/Delete 406 632 16 16
|
||||||
icons/DragLMB 509 567 21 14
|
icons/DragLMB 509 567 21 14
|
||||||
icons/DragRMB 531 567 21 14
|
icons/DragRMB 531 567 21 14
|
||||||
icons/EEPROM 406 632 16 16
|
icons/EEPROM 423 632 16 16
|
||||||
icons/Edit 423 632 16 16
|
icons/Edit 440 632 16 16
|
||||||
icons/Eject 440 632 16 16
|
icons/Eject 457 632 16 16
|
||||||
icons/File 457 632 16 16
|
icons/File 474 632 16 16
|
||||||
icons/Floppy 474 632 16 16
|
icons/Floppy 491 632 16 16
|
||||||
icons/Folder 491 632 16 16
|
icons/Folder 508 632 16 16
|
||||||
icons/FolderSlash 508 632 16 16
|
icons/FolderSlash 525 632 16 16
|
||||||
icons/Grid 177 567 22 22
|
icons/Grid 177 567 22 22
|
||||||
icons/GridOff 200 567 22 22
|
icons/GridOff 200 567 22 22
|
||||||
icons/Guitar 525 632 16 16
|
icons/Guitar 542 632 16 16
|
||||||
icons/HDD 542 632 16 16
|
icons/HDD 559 632 16 16
|
||||||
icons/Help 559 632 16 16
|
icons/Help 576 632 16 16
|
||||||
icons/Home 223 567 22 22
|
icons/Home 223 567 22 22
|
||||||
icons/Keyboard 576 632 16 16
|
icons/Keyboard 593 632 16 16
|
||||||
icons/KeyboardOff 593 632 16 16
|
icons/KeyboardOff 610 632 16 16
|
||||||
icons/LMB 453 434 11 14
|
icons/LMB 453 434 11 14
|
||||||
icons/Label 610 632 16 16
|
icons/Label 627 632 16 16
|
||||||
icons/LinesHorizontal 627 632 16 16
|
icons/LinesHorizontal 644 632 16 16
|
||||||
icons/Link 644 632 16 16
|
icons/Link 661 632 16 16
|
||||||
icons/LinkSlash 661 632 16 16
|
icons/LinkSlash 678 632 16 16
|
||||||
icons/Memory 678 632 16 16
|
icons/Memory 695 632 16 16
|
||||||
icons/Microchip 695 632 16 16
|
icons/Microchip 712 632 16 16
|
||||||
icons/NA 712 632 16 16
|
icons/NA 729 632 16 16
|
||||||
icons/NotificationError 477 434 11 11
|
icons/NotificationError 477 434 11 11
|
||||||
icons/NotificationInfo 489 434 11 11
|
icons/NotificationInfo 489 434 11 11
|
||||||
icons/NotificationWarning 501 434 11 11
|
icons/NotificationWarning 501 434 11 11
|
||||||
icons/Ocelot 729 632 16 16
|
icons/Ocelot 746 632 16 16
|
||||||
icons/Pause 746 632 16 16
|
icons/Paste 763 632 16 16
|
||||||
|
icons/Pause 780 632 16 16
|
||||||
icons/Pin 408 434 14 14
|
icons/Pin 408 434 14 14
|
||||||
icons/Play 763 632 16 16
|
icons/Play 797 632 16 16
|
||||||
icons/Plus 780 632 16 16
|
icons/Plus 814 632 16 16
|
||||||
icons/Power 797 632 16 16
|
icons/Power 831 632 16 16
|
||||||
icons/RMB 465 434 11 14
|
icons/RMB 465 434 11 14
|
||||||
icons/Restart 814 632 16 16
|
icons/Restart 848 632 16 16
|
||||||
icons/Save 831 632 16 16
|
icons/Save 865 632 16 16
|
||||||
icons/SaveAs 848 632 16 16
|
icons/SaveAs 882 632 16 16
|
||||||
icons/Server 865 632 16 16
|
icons/Server 899 632 16 16
|
||||||
icons/SettingsKeymap 356 434 12 17
|
icons/SettingsKeymap 356 434 12 17
|
||||||
icons/SettingsSound 369 434 12 17
|
icons/SettingsSound 369 434 12 17
|
||||||
icons/SettingsSystem 382 434 12 17
|
icons/SettingsSystem 382 434 12 17
|
||||||
@ -100,90 +102,90 @@ icons/SideSouth 573 434 11 11
|
|||||||
icons/SideUndefined 585 434 11 11
|
icons/SideUndefined 585 434 11 11
|
||||||
icons/SideUp 597 434 11 11
|
icons/SideUp 597 434 11 11
|
||||||
icons/SideWest 609 434 11 11
|
icons/SideWest 609 434 11 11
|
||||||
icons/Tier0 882 632 16 16
|
icons/Tier0 916 632 16 16
|
||||||
icons/Tier1 899 632 16 16
|
icons/Tier1 933 632 16 16
|
||||||
icons/Tier2 916 632 16 16
|
icons/Tier2 950 632 16 16
|
||||||
icons/Tiers 933 632 16 16
|
icons/Tiers 967 632 16 16
|
||||||
icons/Unpin 423 434 14 14
|
icons/Unpin 423 434 14 14
|
||||||
icons/WaveLFSR 254 540 24 10
|
icons/WaveLFSR 288 540 24 10
|
||||||
icons/WaveNoise 279 540 24 10
|
icons/WaveNoise 313 540 24 10
|
||||||
icons/WaveSawtooth 304 540 24 10
|
icons/WaveSawtooth 338 540 24 10
|
||||||
icons/WaveSine 329 540 24 10
|
icons/WaveSine 363 540 24 10
|
||||||
icons/WaveSquare 354 540 24 10
|
icons/WaveSquare 388 540 24 10
|
||||||
icons/WaveTriangle 379 540 24 10
|
icons/WaveTriangle 413 540 24 10
|
||||||
icons/Window 950 632 16 16
|
icons/Window 984 632 16 16
|
||||||
icons/WireArrowLeft 281 344 4 8
|
icons/WireArrowLeft 281 344 4 8
|
||||||
icons/WireArrowRight 286 344 4 8
|
icons/WireArrowRight 286 344 4 8
|
||||||
items/APU0 49 655 16 96
|
items/APU0 49 655 16 96
|
||||||
items/APU1 66 655 16 96
|
items/APU1 66 655 16 96
|
||||||
items/APU2 83 655 16 96
|
items/APU2 83 655 16 96
|
||||||
items/CPU0 967 632 16 16
|
items/CPU0 1001 632 16 16
|
||||||
items/CPU1 984 632 16 16
|
items/CPU1 358 674 16 16
|
||||||
items/CPU2 1001 632 16 16
|
items/CPU2 375 674 16 16
|
||||||
items/CardBase 358 674 16 16
|
items/CardBase 392 674 16 16
|
||||||
items/CircuitBoard 375 674 16 16
|
items/CircuitBoard 409 674 16 16
|
||||||
items/ComponentBus0 392 674 16 16
|
items/ComponentBus0 426 674 16 16
|
||||||
items/ComponentBus1 409 674 16 16
|
items/ComponentBus1 443 674 16 16
|
||||||
items/ComponentBus2 426 674 16 16
|
items/ComponentBus2 460 674 16 16
|
||||||
items/ComponentBus3 443 674 16 16
|
items/ComponentBus3 477 674 16 16
|
||||||
items/DataCard0 49 526 16 128
|
items/DataCard0 49 526 16 128
|
||||||
items/DataCard1 66 526 16 128
|
items/DataCard1 66 526 16 128
|
||||||
items/DataCard2 83 526 16 128
|
items/DataCard2 83 526 16 128
|
||||||
items/DebugCard 460 674 16 16
|
items/DebugCard 494 674 16 16
|
||||||
items/DiskDriveMountable 477 674 16 16
|
items/DiskDriveMountable 511 674 16 16
|
||||||
items/EEPROM 494 674 16 16
|
items/EEPROM 528 674 16 16
|
||||||
items/FloppyDisk_dyeBlack 511 674 16 16
|
items/FloppyDisk_dyeBlack 545 674 16 16
|
||||||
items/FloppyDisk_dyeBlue 528 674 16 16
|
items/FloppyDisk_dyeBlue 562 674 16 16
|
||||||
items/FloppyDisk_dyeBrown 545 674 16 16
|
items/FloppyDisk_dyeBrown 579 674 16 16
|
||||||
items/FloppyDisk_dyeCyan 562 674 16 16
|
items/FloppyDisk_dyeCyan 596 674 16 16
|
||||||
items/FloppyDisk_dyeGray 579 674 16 16
|
items/FloppyDisk_dyeGray 613 674 16 16
|
||||||
items/FloppyDisk_dyeGreen 596 674 16 16
|
items/FloppyDisk_dyeGreen 630 674 16 16
|
||||||
items/FloppyDisk_dyeLightBlue 613 674 16 16
|
items/FloppyDisk_dyeLightBlue 647 674 16 16
|
||||||
items/FloppyDisk_dyeLightGray 630 674 16 16
|
items/FloppyDisk_dyeLightGray 664 674 16 16
|
||||||
items/FloppyDisk_dyeLime 647 674 16 16
|
items/FloppyDisk_dyeLime 681 674 16 16
|
||||||
items/FloppyDisk_dyeMagenta 664 674 16 16
|
items/FloppyDisk_dyeMagenta 698 674 16 16
|
||||||
items/FloppyDisk_dyeOrange 681 674 16 16
|
items/FloppyDisk_dyeOrange 715 674 16 16
|
||||||
items/FloppyDisk_dyePink 698 674 16 16
|
items/FloppyDisk_dyePink 732 674 16 16
|
||||||
items/FloppyDisk_dyePurple 715 674 16 16
|
items/FloppyDisk_dyePurple 749 674 16 16
|
||||||
items/FloppyDisk_dyeRed 732 674 16 16
|
items/FloppyDisk_dyeRed 766 674 16 16
|
||||||
items/FloppyDisk_dyeWhite 749 674 16 16
|
items/FloppyDisk_dyeWhite 783 674 16 16
|
||||||
items/FloppyDisk_dyeYellow 766 674 16 16
|
items/FloppyDisk_dyeYellow 800 674 16 16
|
||||||
items/GraphicsCard0 783 674 16 16
|
items/GraphicsCard0 817 674 16 16
|
||||||
items/GraphicsCard1 800 674 16 16
|
items/GraphicsCard1 834 674 16 16
|
||||||
items/GraphicsCard2 817 674 16 16
|
items/GraphicsCard2 851 674 16 16
|
||||||
items/HardDiskDrive0 834 674 16 16
|
items/HardDiskDrive0 868 674 16 16
|
||||||
items/HardDiskDrive1 851 674 16 16
|
items/HardDiskDrive1 885 674 16 16
|
||||||
items/HardDiskDrive2 868 674 16 16
|
items/HardDiskDrive2 902 674 16 16
|
||||||
items/InternetCard 143 567 16 32
|
items/InternetCard 143 567 16 32
|
||||||
items/LinkedCard 100 655 16 96
|
items/LinkedCard 100 655 16 96
|
||||||
items/Memory0 885 674 16 16
|
items/Memory0 919 674 16 16
|
||||||
items/Memory1 902 674 16 16
|
items/Memory1 936 674 16 16
|
||||||
items/Memory2 919 674 16 16
|
items/Memory2 953 674 16 16
|
||||||
items/Memory3 936 674 16 16
|
items/Memory3 970 674 16 16
|
||||||
items/Memory4 953 674 16 16
|
items/Memory4 987 674 16 16
|
||||||
items/Memory5 970 674 16 16
|
items/Memory5 1004 674 16 16
|
||||||
items/Memory6 987 674 16 16
|
items/Memory6 134 707 16 16
|
||||||
items/NetworkCard 1004 674 16 16
|
items/NetworkCard 151 707 16 16
|
||||||
items/OcelotCard 100 526 16 128
|
items/OcelotCard 100 526 16 128
|
||||||
items/RedstoneCard0 134 707 16 16
|
items/RedstoneCard0 168 707 16 16
|
||||||
items/RedstoneCard1 151 707 16 16
|
items/RedstoneCard1 185 707 16 16
|
||||||
items/SelfDestructingCard 160 567 16 32
|
items/SelfDestructingCard 160 567 16 32
|
||||||
items/Server0 168 707 16 16
|
items/Server0 202 707 16 16
|
||||||
items/Server1 185 707 16 16
|
items/Server1 219 707 16 16
|
||||||
items/Server2 202 707 16 16
|
items/Server2 236 707 16 16
|
||||||
items/Server3 219 707 16 16
|
items/Server3 253 707 16 16
|
||||||
items/SoundCard 117 526 16 128
|
items/SoundCard 117 526 16 128
|
||||||
items/TapeCopper 236 707 16 16
|
items/TapeCopper 270 707 16 16
|
||||||
items/TapeDiamond 253 707 16 16
|
items/TapeDiamond 287 707 16 16
|
||||||
items/TapeGold 270 707 16 16
|
items/TapeGold 304 707 16 16
|
||||||
items/TapeGreg 287 707 16 16
|
items/TapeGreg 321 707 16 16
|
||||||
items/TapeIg 304 707 16 16
|
items/TapeIg 338 707 16 16
|
||||||
items/TapeIron 321 707 16 16
|
items/TapeIron 355 707 16 16
|
||||||
items/TapeNetherStar 338 707 16 16
|
items/TapeNetherStar 372 707 16 16
|
||||||
items/TapeSteel 355 707 16 16
|
items/TapeSteel 389 707 16 16
|
||||||
items/WirelessNetworkCard0 372 707 16 16
|
items/WirelessNetworkCard0 406 707 16 16
|
||||||
items/WirelessNetworkCard1 389 707 16 16
|
items/WirelessNetworkCard1 423 707 16 16
|
||||||
light-panel/BookmarkLeft 235 540 18 14
|
light-panel/BookmarkLeft 269 540 18 14
|
||||||
light-panel/BookmarkRight 197 600 20 14
|
light-panel/BookmarkRight 197 600 20 14
|
||||||
light-panel/BorderB 296 344 4 4
|
light-panel/BorderB 296 344 4 4
|
||||||
light-panel/BorderL 284 353 4 2
|
light-panel/BorderL 284 353 4 2
|
||||||
@ -196,94 +198,94 @@ light-panel/CornerTR 326 344 4 4
|
|||||||
light-panel/Fill 410 344 2 2
|
light-panel/Fill 410 344 2 2
|
||||||
light-panel/Vent 279 305 2 38
|
light-panel/Vent 279 305 2 38
|
||||||
nodes/Cable 300 316 8 8
|
nodes/Cable 300 316 8 8
|
||||||
nodes/Camera 406 707 16 16
|
nodes/Camera 440 707 16 16
|
||||||
nodes/Chest 438 434 14 14
|
nodes/Chest 438 434 14 14
|
||||||
nodes/HologramProjector0 423 707 16 16
|
nodes/HologramProjector0 457 707 16 16
|
||||||
nodes/HologramProjector1 440 707 16 16
|
nodes/HologramProjector1 474 707 16 16
|
||||||
nodes/IronNoteBlock 457 707 16 16
|
nodes/IronNoteBlock 491 707 16 16
|
||||||
nodes/Lamp 474 707 16 16
|
nodes/Lamp 508 707 16 16
|
||||||
nodes/LampFrame 491 707 16 16
|
nodes/LampFrame 525 707 16 16
|
||||||
nodes/LampGlow 49 305 128 128
|
nodes/LampGlow 49 305 128 128
|
||||||
nodes/NewNode 508 707 16 16
|
nodes/NewNode 542 707 16 16
|
||||||
nodes/NoteBlock 525 707 16 16
|
nodes/NoteBlock 559 707 16 16
|
||||||
nodes/OpenFMRadio 542 707 16 16
|
nodes/OpenFMRadio 576 707 16 16
|
||||||
nodes/Relay 559 707 16 16
|
nodes/Relay 593 707 16 16
|
||||||
nodes/TapeDrive 576 707 16 16
|
nodes/TapeDrive 610 707 16 16
|
||||||
nodes/computer/Default 593 707 16 16
|
nodes/computer/Default 627 707 16 16
|
||||||
nodes/computer/DiskActivity 610 707 16 16
|
nodes/computer/DiskActivity 644 707 16 16
|
||||||
nodes/computer/Error 627 707 16 16
|
nodes/computer/Error 661 707 16 16
|
||||||
nodes/computer/On 644 707 16 16
|
nodes/computer/On 678 707 16 16
|
||||||
nodes/disk-drive/Default 661 707 16 16
|
nodes/disk-drive/Default 695 707 16 16
|
||||||
nodes/disk-drive/DiskActivity 678 707 16 16
|
nodes/disk-drive/DiskActivity 712 707 16 16
|
||||||
nodes/disk-drive/Floppy 695 707 16 16
|
nodes/disk-drive/Floppy 729 707 16 16
|
||||||
nodes/holidays/Christmas 134 674 32 32
|
nodes/holidays/Christmas 134 674 32 32
|
||||||
nodes/holidays/Halloween 167 674 32 32
|
nodes/holidays/Halloween 167 674 32 32
|
||||||
nodes/holidays/Valentines 200 674 32 32
|
nodes/holidays/Valentines 200 674 32 32
|
||||||
nodes/microcontroller/Default 712 707 16 16
|
nodes/microcontroller/Default 746 707 16 16
|
||||||
nodes/microcontroller/Error 729 707 16 16
|
nodes/microcontroller/Error 763 707 16 16
|
||||||
nodes/microcontroller/On 746 707 16 16
|
nodes/microcontroller/On 780 707 16 16
|
||||||
nodes/ocelot-block/Default 117 655 16 80
|
nodes/ocelot-block/Default 117 655 16 80
|
||||||
nodes/ocelot-block/Rx 763 707 16 16
|
nodes/ocelot-block/Rx 797 707 16 16
|
||||||
nodes/ocelot-block/Tx 780 707 16 16
|
nodes/ocelot-block/Tx 814 707 16 16
|
||||||
nodes/rack/Default 797 707 16 16
|
nodes/rack/Default 831 707 16 16
|
||||||
nodes/rack/Empty 814 707 16 16
|
nodes/rack/Empty 848 707 16 16
|
||||||
nodes/rack/drive/0/Default 831 707 16 16
|
nodes/rack/drive/0/Default 865 707 16 16
|
||||||
nodes/rack/drive/0/DiskActivity 848 707 16 16
|
nodes/rack/drive/0/DiskActivity 882 707 16 16
|
||||||
nodes/rack/drive/0/Floppy 865 707 16 16
|
nodes/rack/drive/0/Floppy 899 707 16 16
|
||||||
nodes/rack/drive/1/Default 882 707 16 16
|
nodes/rack/drive/1/Default 916 707 16 16
|
||||||
nodes/rack/drive/1/DiskActivity 899 707 16 16
|
nodes/rack/drive/1/DiskActivity 933 707 16 16
|
||||||
nodes/rack/drive/1/Floppy 916 707 16 16
|
nodes/rack/drive/1/Floppy 950 707 16 16
|
||||||
nodes/rack/drive/2/Default 933 707 16 16
|
nodes/rack/drive/2/Default 967 707 16 16
|
||||||
nodes/rack/drive/2/DiskActivity 950 707 16 16
|
nodes/rack/drive/2/DiskActivity 984 707 16 16
|
||||||
nodes/rack/drive/2/Floppy 967 707 16 16
|
nodes/rack/drive/2/Floppy 1001 707 16 16
|
||||||
nodes/rack/drive/3/Default 984 707 16 16
|
nodes/rack/drive/3/Default 266 655 16 16
|
||||||
nodes/rack/drive/3/DiskActivity 1001 707 16 16
|
nodes/rack/drive/3/DiskActivity 283 655 16 16
|
||||||
nodes/rack/drive/3/Floppy 266 655 16 16
|
nodes/rack/drive/3/Floppy 300 655 16 16
|
||||||
nodes/rack/drive/Floppy 283 655 16 16
|
nodes/rack/drive/Floppy 317 655 16 16
|
||||||
nodes/rack/server/0/Default 300 655 16 16
|
nodes/rack/server/0/Default 334 655 16 16
|
||||||
nodes/rack/server/0/DiskActivity 317 655 16 16
|
nodes/rack/server/0/DiskActivity 351 655 16 16
|
||||||
nodes/rack/server/0/Error 334 655 16 16
|
nodes/rack/server/0/Error 368 655 16 16
|
||||||
nodes/rack/server/0/NetworkActivity 351 655 16 16
|
nodes/rack/server/0/NetworkActivity 385 655 16 16
|
||||||
nodes/rack/server/0/On 368 655 16 16
|
nodes/rack/server/0/On 402 655 16 16
|
||||||
nodes/rack/server/1/Default 385 655 16 16
|
nodes/rack/server/1/Default 419 655 16 16
|
||||||
nodes/rack/server/1/DiskActivity 402 655 16 16
|
nodes/rack/server/1/DiskActivity 436 655 16 16
|
||||||
nodes/rack/server/1/Error 419 655 16 16
|
nodes/rack/server/1/Error 453 655 16 16
|
||||||
nodes/rack/server/1/NetworkActivity 436 655 16 16
|
nodes/rack/server/1/NetworkActivity 470 655 16 16
|
||||||
nodes/rack/server/1/On 453 655 16 16
|
nodes/rack/server/1/On 487 655 16 16
|
||||||
nodes/rack/server/2/Default 470 655 16 16
|
nodes/rack/server/2/Default 504 655 16 16
|
||||||
nodes/rack/server/2/DiskActivity 487 655 16 16
|
nodes/rack/server/2/DiskActivity 521 655 16 16
|
||||||
nodes/rack/server/2/Error 504 655 16 16
|
nodes/rack/server/2/Error 538 655 16 16
|
||||||
nodes/rack/server/2/NetworkActivity 521 655 16 16
|
nodes/rack/server/2/NetworkActivity 555 655 16 16
|
||||||
nodes/rack/server/2/On 538 655 16 16
|
nodes/rack/server/2/On 572 655 16 16
|
||||||
nodes/rack/server/3/Default 555 655 16 16
|
nodes/rack/server/3/Default 589 655 16 16
|
||||||
nodes/rack/server/3/DiskActivity 572 655 16 16
|
nodes/rack/server/3/DiskActivity 606 655 16 16
|
||||||
nodes/rack/server/3/Error 589 655 16 16
|
nodes/rack/server/3/Error 623 655 16 16
|
||||||
nodes/rack/server/3/NetworkActivity 606 655 16 16
|
nodes/rack/server/3/NetworkActivity 640 655 16 16
|
||||||
nodes/rack/server/3/On 623 655 16 16
|
nodes/rack/server/3/On 657 655 16 16
|
||||||
nodes/raid/0/DiskActivity 640 655 16 16
|
nodes/raid/0/DiskActivity 674 655 16 16
|
||||||
nodes/raid/0/Error 657 655 16 16
|
nodes/raid/0/Error 691 655 16 16
|
||||||
nodes/raid/1/DiskActivity 674 655 16 16
|
nodes/raid/1/DiskActivity 708 655 16 16
|
||||||
nodes/raid/1/Error 691 655 16 16
|
nodes/raid/1/Error 725 655 16 16
|
||||||
nodes/raid/2/DiskActivity 708 655 16 16
|
nodes/raid/2/DiskActivity 742 655 16 16
|
||||||
nodes/raid/2/Error 725 655 16 16
|
nodes/raid/2/Error 759 655 16 16
|
||||||
nodes/raid/Default 742 655 16 16
|
nodes/raid/Default 776 655 16 16
|
||||||
nodes/screen/BottomLeft 759 655 16 16
|
nodes/screen/BottomLeft 793 655 16 16
|
||||||
nodes/screen/BottomMiddle 776 655 16 16
|
nodes/screen/BottomMiddle 810 655 16 16
|
||||||
nodes/screen/BottomRight 793 655 16 16
|
nodes/screen/BottomRight 827 655 16 16
|
||||||
nodes/screen/ColumnBottom 810 655 16 16
|
nodes/screen/ColumnBottom 844 655 16 16
|
||||||
nodes/screen/ColumnMiddle 827 655 16 16
|
nodes/screen/ColumnMiddle 861 655 16 16
|
||||||
nodes/screen/ColumnTop 844 655 16 16
|
nodes/screen/ColumnTop 878 655 16 16
|
||||||
nodes/screen/Middle 861 655 16 16
|
nodes/screen/Middle 895 655 16 16
|
||||||
nodes/screen/MiddleLeft 878 655 16 16
|
nodes/screen/MiddleLeft 912 655 16 16
|
||||||
nodes/screen/MiddleRight 895 655 16 16
|
nodes/screen/MiddleRight 929 655 16 16
|
||||||
nodes/screen/PowerOnOverlay 912 655 16 16
|
nodes/screen/PowerOnOverlay 946 655 16 16
|
||||||
nodes/screen/RowLeft 929 655 16 16
|
nodes/screen/RowLeft 963 655 16 16
|
||||||
nodes/screen/RowMiddle 946 655 16 16
|
nodes/screen/RowMiddle 980 655 16 16
|
||||||
nodes/screen/RowRight 963 655 16 16
|
nodes/screen/RowRight 997 655 16 16
|
||||||
nodes/screen/Standalone 980 655 16 16
|
nodes/screen/Standalone 201 540 16 16
|
||||||
nodes/screen/TopLeft 997 655 16 16
|
nodes/screen/TopLeft 218 540 16 16
|
||||||
nodes/screen/TopMiddle 201 540 16 16
|
nodes/screen/TopMiddle 235 540 16 16
|
||||||
nodes/screen/TopRight 218 540 16 16
|
nodes/screen/TopRight 252 540 16 16
|
||||||
panel/BorderB 331 344 4 4
|
panel/BorderB 331 344 4 4
|
||||||
panel/BorderL 289 353 4 2
|
panel/BorderL 289 353 4 2
|
||||||
panel/BorderR 336 344 4 4
|
panel/BorderR 336 344 4 4
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package ocelot.desktop.util
|
package ocelot.desktop.graphics
|
||||||
|
|
||||||
import ocelot.desktop.geometry.Rect2D
|
import ocelot.desktop.geometry.Rect2D
|
||||||
import ocelot.desktop.graphics.Texture
|
import ocelot.desktop.util.{Logging, Resource}
|
||||||
import org.lwjgl.opengl.GL11
|
import org.lwjgl.opengl.GL11
|
||||||
import totoro.ocelot.brain.util.FontUtils
|
import totoro.ocelot.brain.util.FontUtils
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ import scala.io.{Codec, Source}
|
|||||||
class Font(val name: String, val fontSize: Int) extends Resource with Logging {
|
class Font(val name: String, val fontSize: Int) extends Resource with Logging {
|
||||||
val AtlasWidth = 4096
|
val AtlasWidth = 4096
|
||||||
val AtlasHeight = 4096
|
val AtlasHeight = 4096
|
||||||
var glyphCount = 0
|
private var glyphCount = 0
|
||||||
var outOfRangeGlyphCount = 0
|
private var outOfRangeGlyphCount = 0
|
||||||
|
|
||||||
private val atlas: BufferedImage = {
|
private val atlas: BufferedImage = {
|
||||||
val icmArr = Array(0.toByte, 0xff.toByte)
|
val icmArr = Array(0.toByte, 0xff.toByte)
|
||||||
@ -134,3 +134,14 @@ class Font(val name: String, val fontSize: Int) extends Resource with Logging {
|
|||||||
texture.freeResource()
|
texture.freeResource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Font extends Resource {
|
||||||
|
val NormalFont = new Font("unscii-16", 16)
|
||||||
|
val SmallFont = new Font("unscii-8", 8)
|
||||||
|
|
||||||
|
override def freeResource(): Unit = {
|
||||||
|
super.freeResource()
|
||||||
|
NormalFont.freeResource()
|
||||||
|
SmallFont.freeResource()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@ 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
|
||||||
import ocelot.desktop.ui.UiHandler
|
import ocelot.desktop.ui.UiHandler
|
||||||
import ocelot.desktop.util.{Font, Logging, Resource, Spritesheet}
|
import ocelot.desktop.util.{Logging, Resource, Spritesheet}
|
||||||
import org.lwjgl.BufferUtils
|
import org.lwjgl.BufferUtils
|
||||||
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
||||||
|
|
||||||
@ -25,9 +25,7 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
|||||||
private val shaderProgram = new ShaderProgram("general")
|
private val shaderProgram = new ShaderProgram("general")
|
||||||
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
||||||
|
|
||||||
private[graphics] val normalFont = new Font("unscii-16", 16)
|
private var _font: Font = Font.NormalFont
|
||||||
private val smallFont = new Font("unscii-8", 8)
|
|
||||||
private var _font: Font = normalFont
|
|
||||||
private var oldFont: Font = _font
|
private var oldFont: Font = _font
|
||||||
|
|
||||||
private val stack = mutable.Stack[GraphicsState](GraphicsState())
|
private val stack = mutable.Stack[GraphicsState](GraphicsState())
|
||||||
@ -87,8 +85,6 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
|||||||
|
|
||||||
offscreenTexture.freeResource()
|
offscreenTexture.freeResource()
|
||||||
Spritesheet.freeResource()
|
Spritesheet.freeResource()
|
||||||
smallFont.freeResource()
|
|
||||||
normalFont.freeResource()
|
|
||||||
renderer.freeResource()
|
renderer.freeResource()
|
||||||
shaderProgram.freeResource()
|
shaderProgram.freeResource()
|
||||||
screenShaderProgram.freeResource()
|
screenShaderProgram.freeResource()
|
||||||
@ -97,15 +93,15 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
|||||||
def font: Font = _font
|
def font: Font = _font
|
||||||
|
|
||||||
def setNormalFont(): Unit = {
|
def setNormalFont(): Unit = {
|
||||||
if (_font == normalFont) return
|
if (_font == Font.NormalFont) return
|
||||||
flush()
|
flush()
|
||||||
_font = normalFont
|
_font = Font.NormalFont
|
||||||
}
|
}
|
||||||
|
|
||||||
def setSmallFont(): Unit = {
|
def setSmallFont(): Unit = {
|
||||||
if (_font == smallFont) return
|
if (_font == Font.SmallFont) return
|
||||||
flush()
|
flush()
|
||||||
_font = smallFont
|
_font = Font.SmallFont
|
||||||
}
|
}
|
||||||
|
|
||||||
def save(): Unit = {
|
def save(): Unit = {
|
||||||
|
|||||||
@ -205,6 +205,8 @@ object IconSource {
|
|||||||
val Delete: IconSource = get("Delete")
|
val Delete: IconSource = get("Delete")
|
||||||
val Label: IconSource = get("Label")
|
val Label: IconSource = get("Label")
|
||||||
val Copy: IconSource = get("Copy")
|
val Copy: IconSource = get("Copy")
|
||||||
|
val Cut: IconSource = get("Cut")
|
||||||
|
val Paste: IconSource = get("Paste")
|
||||||
val AspectRatio: IconSource = get("AspectRatio")
|
val AspectRatio: IconSource = get("AspectRatio")
|
||||||
val Eject: IconSource = get("Eject")
|
val Eject: IconSource = get("Eject")
|
||||||
val Restart: IconSource = get("Restart")
|
val Restart: IconSource = get("Restart")
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import ocelot.desktop.color.{Color, RGBAColorNorm}
|
|||||||
import ocelot.desktop.geometry.Transform2D
|
import ocelot.desktop.geometry.Transform2D
|
||||||
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
|
||||||
import ocelot.desktop.util.{Font, Resource, Spritesheet}
|
import ocelot.desktop.util.{Resource, Spritesheet}
|
||||||
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -19,7 +19,7 @@ class ScreenViewport(graphics: Graphics, private var _width: Int, private var _h
|
|||||||
private val shaderProgram = graphics.screenShaderProgram
|
private val shaderProgram = graphics.screenShaderProgram
|
||||||
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
|
||||||
|
|
||||||
private val _font = graphics.normalFont
|
private val _font = Font.NormalFont
|
||||||
private val spriteRect = Spritesheet.sprites("Empty")
|
private val spriteRect = Spritesheet.sprites("Empty")
|
||||||
|
|
||||||
private val emptySpriteTrans =
|
private val emptySpriteTrans =
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package ocelot.desktop.ui
|
|||||||
import buildinfo.BuildInfo
|
import buildinfo.BuildInfo
|
||||||
import ocelot.desktop.audio.{Audio, SoundBuffers, SoundSource}
|
import ocelot.desktop.audio.{Audio, SoundBuffers, SoundSource}
|
||||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.{Font, Graphics}
|
||||||
import ocelot.desktop.ui.event.handlers.HoverHandler
|
import ocelot.desktop.ui.event.handlers.HoverHandler
|
||||||
import ocelot.desktop.ui.event.sources.{BrainEvents, KeyEvents, MouseEvents, ScrollEvents}
|
import ocelot.desktop.ui.event.sources.{BrainEvents, KeyEvents, MouseEvents, ScrollEvents}
|
||||||
import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, HoverEvent, MouseEvent}
|
import ocelot.desktop.ui.event.{Capturing, CapturingEvent, Dispatchable, HoverEvent, MouseEvent}
|
||||||
@ -80,7 +80,7 @@ object UiHandler extends Logging {
|
|||||||
_clipboard.getData(DataFlavor.stringFlavor).toString
|
_clipboard.getData(DataFlavor.stringFlavor).toString
|
||||||
} catch {
|
} catch {
|
||||||
case _: UnsupportedFlavorException =>
|
case _: UnsupportedFlavorException =>
|
||||||
logger.debug("Trying to paste non-Unicode content from clipboard! Replaced with empty string.")
|
logger.debug("Trying to paste non-Unicode content from clipboard! Replaced with an empty string.")
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,6 +418,7 @@ object UiHandler extends Logging {
|
|||||||
KeyEvents.destroy()
|
KeyEvents.destroy()
|
||||||
MouseEvents.destroy()
|
MouseEvents.destroy()
|
||||||
graphics.freeResource()
|
graphics.freeResource()
|
||||||
|
Font.freeResource()
|
||||||
Audio.removeAllSources()
|
Audio.removeAllSources()
|
||||||
SoundBuffers.freeResource()
|
SoundBuffers.freeResource()
|
||||||
Display.destroy()
|
Display.destroy()
|
||||||
@ -430,10 +431,9 @@ object UiHandler extends Logging {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def dispatchCapturing(target: Widget)(event: CapturingEvent): Unit = {
|
private def dispatchCapturing(dispatchOrder: DispatchOrder)(event: CapturingEvent): Unit = {
|
||||||
val ancestors = target.ancestors.toSeq
|
dispatchEvent(dispatchOrder.capture)(Capturing(event))
|
||||||
dispatchEvent(ancestors.reverseIterator ++ Some(target))(Capturing(event))
|
dispatchEvent(dispatchOrder.targets.reverseIterator)(event)
|
||||||
dispatchEvent(Some(target))(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def dispatchBrainEvents(): Unit = {
|
private def dispatchBrainEvents(): Unit = {
|
||||||
@ -474,15 +474,13 @@ object UiHandler extends Logging {
|
|||||||
MouseEvents.releaseButtons()
|
MouseEvents.releaseButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val broadcastDispatchOrder = DispatchOrder.broadcast
|
||||||
|
|
||||||
// TODO: dispatch to the focused widget instead of broadcasting to the entire hierarchy.
|
// TODO: dispatch to the focused widget instead of broadcasting to the entire hierarchy.
|
||||||
for (event <- KeyEvents.events) {
|
for (event <- KeyEvents.events) {
|
||||||
dispatchEvent(hierarchy)(Capturing(event))
|
dispatchCapturing(broadcastDispatchOrder)(event)
|
||||||
dispatchEvent()(event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseEvents.events
|
|
||||||
.foreach(dispatchEvent(hierarchy.reverseIterator.filter(w => w.enabled && w.receiveAllMouseEvents)))
|
|
||||||
|
|
||||||
val scrollTarget = hierarchy.reverseIterator
|
val scrollTarget = hierarchy.reverseIterator
|
||||||
.find(w => w.receiveScrollEvents && w.clippedBounds.contains(mousePos))
|
.find(w => w.receiveScrollEvents && w.clippedBounds.contains(mousePos))
|
||||||
|
|
||||||
@ -490,30 +488,33 @@ object UiHandler extends Logging {
|
|||||||
.find(w => w.enabled && w.receiveMouseEvents && w.clippedBounds.contains(mousePos))
|
.find(w => w.enabled && w.receiveMouseEvents && w.clippedBounds.contains(mousePos))
|
||||||
|
|
||||||
for (scrollTarget <- scrollTarget) {
|
for (scrollTarget <- scrollTarget) {
|
||||||
ScrollEvents.events.foreach(dispatchCapturing(scrollTarget))
|
ScrollEvents.events.foreach(dispatchCapturing(DispatchOrder.resolve(scrollTarget)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mouseEventDispatchOrder = DispatchOrder.resolve(
|
||||||
|
hierarchy
|
||||||
|
.iterator
|
||||||
|
.filter(w => mouseTarget.contains(w) || w.enabled && w.receiveAllMouseEvents)
|
||||||
|
.toSeq
|
||||||
|
)
|
||||||
|
|
||||||
for (event <- MouseEvents.events) {
|
for (event <- MouseEvents.events) {
|
||||||
if (event.state == MouseEvent.State.Pressed) {
|
if (event.state == MouseEvent.State.Pressed) {
|
||||||
for (mouseTarget <- mouseTarget) {
|
dispatchCapturing(mouseEventDispatchOrder)(event)
|
||||||
dispatchCapturing(mouseTarget)(event)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
dispatchEvent(hierarchy)(Capturing(event))
|
dispatchCapturing(broadcastDispatchOrder)(event)
|
||||||
dispatchEvent(hierarchy.reverseIterator)(event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hierarchy.reverseIterator.foreach {
|
val hoverLeaveDispatchOrder = DispatchOrder.resolve(hierarchy.iterator.collect({
|
||||||
case h: HoverHandler if !mouseTarget.contains(h) && h._mouseOver.update(false) =>
|
case h: HoverHandler if !mouseTarget.contains(h) && h._mouseOver.update(false) => h
|
||||||
dispatchCapturing(h)(HoverEvent(HoverEvent.State.Leave))
|
}).toSeq)
|
||||||
|
|
||||||
case _ =>
|
dispatchCapturing(hoverLeaveDispatchOrder)(HoverEvent(HoverEvent.State.Leave))
|
||||||
}
|
|
||||||
|
|
||||||
mouseTarget.foreach {
|
mouseTarget.foreach {
|
||||||
case h: HoverHandler if h._mouseOver.update(true) =>
|
case h: HoverHandler if h._mouseOver.update(true) =>
|
||||||
dispatchCapturing(h)(HoverEvent(HoverEvent.State.Enter))
|
dispatchCapturing(DispatchOrder.resolve(h))(HoverEvent(HoverEvent.State.Enter))
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
@ -548,4 +549,43 @@ object UiHandler extends Logging {
|
|||||||
graphics.flush()
|
graphics.flush()
|
||||||
graphics.update()
|
graphics.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a dispatch order for [[CapturingEvent]]s.
|
||||||
|
* @param capture widgets to deliver events during capture phase, in hierarchy pre-order.
|
||||||
|
* @param targets widgets to deliver events during target phase, in hierarchy pre-order.
|
||||||
|
*/
|
||||||
|
private case class DispatchOrder(capture: collection.Seq[Widget], targets: collection.Seq[Widget])
|
||||||
|
|
||||||
|
private object DispatchOrder {
|
||||||
|
/**
|
||||||
|
* Creates a dispatch order for delivering events to multiple targets.
|
||||||
|
* @param targets event targets, in hierarchy pre-order.
|
||||||
|
*/
|
||||||
|
def resolve(targets: collection.Seq[Widget]): DispatchOrder = {
|
||||||
|
val capture = mutable.Set.empty[Widget]
|
||||||
|
|
||||||
|
for (widget <- targets) {
|
||||||
|
capture += widget
|
||||||
|
val ancestors = widget.ancestors
|
||||||
|
while (ancestors.hasNext && capture.add(ancestors.next())) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchOrder(
|
||||||
|
capture = hierarchy.iterator.filter(capture).toSeq,
|
||||||
|
targets = targets,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a dispatch order for delivering event to a single `target`.
|
||||||
|
*/
|
||||||
|
def resolve(target: Widget): DispatchOrder = {
|
||||||
|
val capture = (Iterator(target) ++ target.ancestors.iterator).toSeq
|
||||||
|
|
||||||
|
DispatchOrder(capture, Seq(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
def broadcast: DispatchOrder = DispatchOrder(hierarchy, hierarchy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
package ocelot.desktop.ui.event
|
||||||
|
|
||||||
|
import ocelot.desktop.geometry.Vector2D
|
||||||
|
|
||||||
|
case class DoubleClickEvent(button: MouseEvent.Button.Value, mousePos: Vector2D) extends Event
|
||||||
@ -1,5 +1,8 @@
|
|||||||
package ocelot.desktop.ui.event
|
package ocelot.desktop.ui.event
|
||||||
|
|
||||||
|
case class MouseEvent(state: MouseEvent.State.Value, button: MouseEvent.Button.Value)(val stateChanged: Boolean)
|
||||||
|
extends CapturingEvent
|
||||||
|
|
||||||
object MouseEvent {
|
object MouseEvent {
|
||||||
object State extends Enumeration {
|
object State extends Enumeration {
|
||||||
val Pressed, Released = Value
|
val Pressed, Released = Value
|
||||||
@ -10,6 +13,14 @@ object MouseEvent {
|
|||||||
val Right: Button.Value = Value(1)
|
val Right: Button.Value = Value(1)
|
||||||
val Middle: Button.Value = Value(2)
|
val Middle: Button.Value = Value(2)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
case class MouseEvent(state: MouseEvent.State.Value, button: MouseEvent.Button.Value) extends CapturingEvent
|
object StateChanged {
|
||||||
|
def unapply(event: MouseEvent): Option[(State.Value, Button.Value)] = {
|
||||||
|
if (event.stateChanged) {
|
||||||
|
MouseEvent.unapply(event)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package ocelot.desktop.ui.event.handlers
|
|||||||
|
|
||||||
import ocelot.desktop.geometry.Vector2D
|
import ocelot.desktop.geometry.Vector2D
|
||||||
import ocelot.desktop.ui.UiHandler
|
import ocelot.desktop.ui.UiHandler
|
||||||
import ocelot.desktop.ui.event.handlers.MouseHandler.Tolerance
|
import ocelot.desktop.ui.event.handlers.MouseHandler.{DoubleClickTime, Tolerance}
|
||||||
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, MouseEvent}
|
import ocelot.desktop.ui.event.{ClickEvent, DoubleClickEvent, DragEvent, MouseEvent}
|
||||||
import ocelot.desktop.ui.widget.Widget
|
import ocelot.desktop.ui.widget.Widget
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
@ -13,13 +13,23 @@ trait MouseHandler extends Widget {
|
|||||||
private val prevPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]()
|
private val prevPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]()
|
||||||
private val dragButtons = new mutable.HashSet[MouseEvent.Button.Value]()
|
private val dragButtons = new mutable.HashSet[MouseEvent.Button.Value]()
|
||||||
|
|
||||||
|
private var lastClickPosition: Vector2D = Vector2D.Zero
|
||||||
|
private var lastClickTime: Long = 0
|
||||||
|
private var lastClickButton = MouseEvent.Button.Left
|
||||||
|
|
||||||
override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents
|
override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents
|
||||||
|
|
||||||
protected def receiveClickEvents: Boolean = false
|
protected def receiveClickEvents: Boolean = false
|
||||||
|
|
||||||
protected def receiveDragEvents: Boolean = false
|
protected def receiveDragEvents: Boolean = false
|
||||||
|
|
||||||
/** If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
|
/**
|
||||||
|
* If `true`, drag events, once they start, will be sent on every update cycle even if the mouse does not move.
|
||||||
|
*/
|
||||||
|
protected def spamDragEvents: Boolean = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
|
||||||
* outside the tolerance threshold, as long as it stays within the widget's bounds.
|
* outside the tolerance threshold, as long as it stays within the widget's bounds.
|
||||||
*/
|
*/
|
||||||
protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents
|
protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents
|
||||||
@ -39,7 +49,7 @@ trait MouseHandler extends Widget {
|
|||||||
if (allowClickReleaseOutsideThreshold) {
|
if (allowClickReleaseOutsideThreshold) {
|
||||||
clippedBounds.contains(mousePos)
|
clippedBounds.contains(mousePos)
|
||||||
} else {
|
} else {
|
||||||
(p - mousePos).lengthSquared < Tolerance * Tolerance
|
withinTolerance(p, mousePos)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -50,11 +60,23 @@ trait MouseHandler extends Widget {
|
|||||||
|
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
handleEvent(ClickEvent(button, mousePos))
|
handleEvent(ClickEvent(button, mousePos))
|
||||||
|
|
||||||
|
val inTimeForDoubleClick = (System.currentTimeMillis() - lastClickTime) < DoubleClickTime * 1000
|
||||||
|
val sameButton = lastClickButton == button
|
||||||
|
val roughlySamePosition = withinTolerance(lastClickPosition, mousePos)
|
||||||
|
if (inTimeForDoubleClick && sameButton && roughlySamePosition) {
|
||||||
|
handleEvent(DoubleClickEvent(button, mousePos))
|
||||||
|
}
|
||||||
|
lastClickTime = System.currentTimeMillis()
|
||||||
|
lastClickPosition = mousePos
|
||||||
|
lastClickButton = button
|
||||||
}
|
}
|
||||||
|
|
||||||
startPositions.remove(button)
|
startPositions.remove(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def withinTolerance(a: Vector2D, b: Vector2D): Boolean = (b - a).lengthSquared < Tolerance * Tolerance
|
||||||
|
|
||||||
override def update(): Unit = {
|
override def update(): Unit = {
|
||||||
super.update()
|
super.update()
|
||||||
|
|
||||||
@ -65,14 +87,14 @@ trait MouseHandler extends Widget {
|
|||||||
val mousePos = UiHandler.mousePosition
|
val mousePos = UiHandler.mousePosition
|
||||||
|
|
||||||
for ((button, startPos) <- startPositions) {
|
for ((button, startPos) <- startPositions) {
|
||||||
if (!dragButtons.contains(button) && (startPos - mousePos).lengthSquared > Tolerance * Tolerance) {
|
if (!dragButtons.contains(button) && !withinTolerance(startPos, mousePos)) {
|
||||||
handleEvent(DragEvent(DragEvent.State.Start, button, mousePos, startPos, Vector2D(0, 0)))
|
handleEvent(DragEvent(DragEvent.State.Start, button, mousePos, startPos, Vector2D(0, 0)))
|
||||||
dragButtons += button
|
dragButtons += button
|
||||||
prevPositions += (button -> mousePos)
|
prevPositions += (button -> mousePos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dragButtons.foreach(button => {
|
for (button <- dragButtons if spamDragEvents || prevPositions(button) != mousePos) {
|
||||||
handleEvent(
|
handleEvent(
|
||||||
DragEvent(
|
DragEvent(
|
||||||
DragEvent.State.Drag,
|
DragEvent.State.Drag,
|
||||||
@ -82,12 +104,13 @@ trait MouseHandler extends Widget {
|
|||||||
mousePos - prevPositions(button),
|
mousePos - prevPositions(button),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
prevPositions(button) = mousePos
|
prevPositions(button) = mousePos
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object MouseHandler {
|
object MouseHandler {
|
||||||
private val Tolerance = 8
|
private val Tolerance = 8
|
||||||
|
private val DoubleClickTime = 0.2
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,13 +26,13 @@ object MouseEvents {
|
|||||||
if (MouseEvent.Button.values.map(_.id).contains(buttonIdx)) {
|
if (MouseEvent.Button.values.map(_.id).contains(buttonIdx)) {
|
||||||
val button = MouseEvent.Button(buttonIdx)
|
val button = MouseEvent.Button(buttonIdx)
|
||||||
val state = if (Mouse.getEventButtonState) MouseEvent.State.Pressed else MouseEvent.State.Released
|
val state = if (Mouse.getEventButtonState) MouseEvent.State.Pressed else MouseEvent.State.Released
|
||||||
_events += MouseEvent(state, button)
|
val changed = state match {
|
||||||
state match {
|
|
||||||
case MouseEvent.State.Pressed =>
|
case MouseEvent.State.Pressed =>
|
||||||
_pressedButtons += button
|
_pressedButtons.add(button)
|
||||||
case MouseEvent.State.Released =>
|
case MouseEvent.State.Released =>
|
||||||
_pressedButtons -= button
|
_pressedButtons.remove(button)
|
||||||
}
|
}
|
||||||
|
_events += MouseEvent(state, button)(changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
val delta = Mouse.getEventDWheel
|
val delta = Mouse.getEventDWheel
|
||||||
@ -49,7 +49,7 @@ object MouseEvents {
|
|||||||
|
|
||||||
def releaseButtons(): Unit = {
|
def releaseButtons(): Unit = {
|
||||||
for (button <- pressedButtons) {
|
for (button <- pressedButtons) {
|
||||||
_events += MouseEvent(MouseEvent.State.Released, button)
|
_events += MouseEvent(MouseEvent.State.Released, button)(stateChanged = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
_pressedButtons.clear()
|
_pressedButtons.clear()
|
||||||
|
|||||||
@ -16,8 +16,8 @@ class Button(tooltip: Option[Tooltip] = None) extends Widget with MouseHandler w
|
|||||||
def this(tooltip: Tooltip) = this(Some(tooltip))
|
def this(tooltip: Tooltip) = this(Some(tooltip))
|
||||||
|
|
||||||
protected def colorScheme: ColorScheme = ColorScheme.General
|
protected def colorScheme: ColorScheme = ColorScheme.General
|
||||||
override protected val hoverAnimationColorDefault: Color = colorScheme("ButtonBackground")
|
override protected val HoverAnimationColorDefault: Color = colorScheme("ButtonBackground")
|
||||||
override protected val hoverAnimationColorActive: Color = colorScheme("ButtonBackgroundActive")
|
override protected val HoverAnimationColorActive: Color = colorScheme("ButtonBackgroundActive")
|
||||||
|
|
||||||
def text: String = ""
|
def text: String = ""
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,8 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
|
|||||||
|
|
||||||
override def onInput(text: String): Unit = {
|
override def onInput(text: String): Unit = {
|
||||||
tickInterval = parseInput(text).map { interval =>
|
tickInterval = parseInput(text).map { interval =>
|
||||||
inputTPS.setInput(formatTPS(interval))
|
val tps = formatTPS(interval)
|
||||||
|
if (inputTPS.text != tps) inputTPS.text = tps
|
||||||
interval
|
interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +67,8 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
|
|||||||
|
|
||||||
override def onInput(text: String): Unit = {
|
override def onInput(text: String): Unit = {
|
||||||
tickInterval = parseInput(text).map { interval =>
|
tickInterval = parseInput(text).map { interval =>
|
||||||
inputMSPT.setInput(formatMSPT(interval))
|
val mspt = formatMSPT(interval)
|
||||||
|
if (inputMSPT.text != mspt) inputMSPT.text = mspt
|
||||||
interval
|
interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import ocelot.desktop.util.DrawUtils
|
|||||||
class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall: Boolean = false)
|
class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall: Boolean = false)
|
||||||
extends Widget with MouseHandler with HoverAnimation {
|
extends Widget with MouseHandler with HoverAnimation {
|
||||||
|
|
||||||
override protected val hoverAnimationColorDefault: Color = ColorScheme("CheckboxBackground")
|
override protected val HoverAnimationColorDefault: Color = ColorScheme("CheckboxBackground")
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("CheckboxBackgroundActive")
|
override protected val HoverAnimationColorActive: Color = ColorScheme("CheckboxBackgroundActive")
|
||||||
|
|
||||||
private var _checked: Boolean = initialValue
|
private var _checked: Boolean = initialValue
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,8 @@ import ocelot.desktop.util.DrawUtils
|
|||||||
class Slider(var value: Float, val text: String, val snapPoints: Int = 0)
|
class Slider(var value: Float, val text: String, val snapPoints: Int = 0)
|
||||||
extends Widget with MouseHandler with HoverAnimation {
|
extends Widget with MouseHandler with HoverAnimation {
|
||||||
|
|
||||||
override protected val hoverAnimationColorDefault: Color = ColorScheme("SliderBackground")
|
override protected val HoverAnimationColorDefault: Color = ColorScheme("SliderBackground")
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("SliderBackgroundActive")
|
override protected val HoverAnimationColorActive: Color = ColorScheme("SliderBackgroundActive")
|
||||||
|
|
||||||
def onValueChanged(value: Float): Unit = {}
|
def onValueChanged(value: Float): Unit = {}
|
||||||
def onValueFinal(value: Float): Unit = {}
|
def onValueFinal(value: Float): Unit = {}
|
||||||
|
|||||||
@ -3,22 +3,71 @@ 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.geometry.Size2D
|
import ocelot.desktop.geometry.Size2D
|
||||||
import ocelot.desktop.graphics.Graphics
|
import ocelot.desktop.graphics.{Font, Graphics, IconSource}
|
||||||
import ocelot.desktop.ui.UiHandler
|
import ocelot.desktop.ui.UiHandler
|
||||||
import ocelot.desktop.ui.event.handlers.MouseHandler
|
import ocelot.desktop.ui.event.handlers.MouseHandler
|
||||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
import ocelot.desktop.ui.event.sources.KeyEvents
|
||||||
import ocelot.desktop.ui.event.{ClickEvent, KeyEvent, MouseEvent}
|
import ocelot.desktop.ui.event.{DoubleClickEvent, DragEvent, KeyEvent, MouseEvent}
|
||||||
|
import ocelot.desktop.ui.widget.TextInput.{Cursor, Selection, Text}
|
||||||
|
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
|
||||||
import ocelot.desktop.ui.widget.traits.HoverAnimation
|
import ocelot.desktop.ui.widget.traits.HoverAnimation
|
||||||
import ocelot.desktop.util.DrawUtils
|
import ocelot.desktop.util.{DrawUtils, Register, Watcher}
|
||||||
import ocelot.desktop.util.animation.ColorAnimation
|
import ocelot.desktop.util.animation.ColorAnimation
|
||||||
import org.lwjgl.input.Keyboard
|
import org.lwjgl.input.Keyboard
|
||||||
|
|
||||||
import scala.collection.mutable.ArrayBuffer
|
import java.lang.Character.isWhitespace
|
||||||
|
|
||||||
class TextInput(val initialText: String = "") extends Widget with MouseHandler with HoverAnimation {
|
class TextInput(val initialText: String = "") extends Widget with MouseHandler with HoverAnimation {
|
||||||
override protected val hoverAnimationColorDefault: Color = ColorScheme("TextInputBackground")
|
private val CursorBlinkTime = 2f
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("TextInputBackgroundActive")
|
private val PlaceholderForegroundColor: Color = ColorScheme("TextInputForegroundDisabled")
|
||||||
|
private val BackgroundSelectedColor: Color = ColorScheme("TextInputBackgroundSelected")
|
||||||
|
private val ForegroundSelectedColor: Color = ColorScheme("TextInputForegroundSelected")
|
||||||
|
override protected val HoverAnimationColorDefault: Color = ColorScheme("TextInputBackground")
|
||||||
|
override protected val HoverAnimationColorActive: Color = ColorScheme("TextInputBackgroundActive")
|
||||||
|
|
||||||
|
// model
|
||||||
|
private val _text: Text = new Text(initialText.codePoints().toArray)
|
||||||
|
private val cursor: Cursor = new Cursor()
|
||||||
|
private val selectionWatcher = new Watcher[Option[Selection]](None)
|
||||||
|
|
||||||
|
// updated after all events are processed so that event handlers can refer to the previous position.
|
||||||
|
private val prevCursorPosition = Register.sampling(cursor.position)
|
||||||
|
|
||||||
|
// view
|
||||||
|
private var isFocused = false
|
||||||
|
private var scroll = 0f
|
||||||
|
private var blinkTimer = 0f
|
||||||
|
private var cursorOffset = 0f
|
||||||
|
private var selectionOffsets: Option[(Int, Int)] = None
|
||||||
|
|
||||||
|
private val enabledRegister = Register.sampling(enabled)
|
||||||
|
|
||||||
|
private def selection: Option[Selection] = selectionWatcher.value
|
||||||
|
private def selection_=(newValue: Option[Selection]): Unit = {
|
||||||
|
selectionWatcher.value = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.onChange(position => {
|
||||||
|
cursorOffset = charsWidth(_text.chars, 0, position)
|
||||||
|
blinkTimer = 0
|
||||||
|
adjustScroll()
|
||||||
|
})
|
||||||
|
|
||||||
|
selectionWatcher.onChange(newValue => {
|
||||||
|
selectionOffsets = newValue.map {
|
||||||
|
case Selection.Ordered(start, end) =>
|
||||||
|
(
|
||||||
|
charsWidth(_text.chars, 0, start),
|
||||||
|
charsWidth(_text.chars, 0, end),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
|
||||||
|
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
|
||||||
|
|
||||||
|
// public API
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
def onInput(text: String): Unit = {}
|
def onInput(text: String): Unit = {}
|
||||||
|
|
||||||
def onConfirm(): Unit = {
|
def onConfirm(): Unit = {
|
||||||
@ -28,125 +77,28 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
def validator(text: String): Boolean = true
|
def validator(text: String): Boolean = true
|
||||||
final def isInputValid: Boolean = validator(text)
|
final def isInputValid: Boolean = validator(text)
|
||||||
|
|
||||||
var isFocused = false
|
def text: String = new String(_text.chars, 0, _text.chars.length)
|
||||||
|
def text_=(value: String): Unit = {
|
||||||
def text: String = chars.mkString
|
_text.chars = value.codePoints().toArray
|
||||||
def text_=(value: String): Unit = chars = value.toCharArray
|
selection = None
|
||||||
|
cursor.position = cursor.position max 0 min _text.chars.length
|
||||||
protected var placeholder: Array[Char] = "".toCharArray
|
|
||||||
def placeholder_=(value: String): Unit = placeholder = value.toCharArray
|
|
||||||
|
|
||||||
private var cursorPos = 0
|
|
||||||
private var cursorOffset = 0f
|
|
||||||
private var scroll = 0f
|
|
||||||
private val CursorBlinkTime = 2f
|
|
||||||
private var blinkTimer = 0f
|
|
||||||
private var chars = initialText.toCharArray
|
|
||||||
private var textWidth = 0
|
|
||||||
private var textChanged = false
|
|
||||||
private val events = ArrayBuffer[TextEvent]()
|
|
||||||
|
|
||||||
override protected def receiveClickEvents: Boolean = true
|
|
||||||
|
|
||||||
// TODO: implement text selection
|
|
||||||
// override protected def receiveDragEvents: Boolean = true
|
|
||||||
|
|
||||||
override protected def allowClickReleaseOutsideThreshold: Boolean = false
|
|
||||||
|
|
||||||
override def receiveAllMouseEvents = true
|
|
||||||
|
|
||||||
private var prevEnabled = enabled
|
|
||||||
|
|
||||||
eventHandlers += {
|
|
||||||
case MouseEvent(MouseEvent.State.Released, MouseEvent.Button.Left) =>
|
|
||||||
if (isFocused && !clippedBounds.contains(UiHandler.mousePosition)) {
|
|
||||||
unfocus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case ClickEvent(MouseEvent.Button.Left, pos) if enabled =>
|
private def selectedText: String = selection match {
|
||||||
focus()
|
case Some(Selection.Ordered(start, end)) => new String(_text.chars, start, end)
|
||||||
events += new CursorMoveTo(pos.x)
|
case None => ""
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_LEFT, _) if isFocused =>
|
|
||||||
events += new CursorMoveLeft
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_RIGHT, _) if isFocused =>
|
|
||||||
events += new CursorMoveRight
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) if isFocused =>
|
|
||||||
events += new CursorMoveStart
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_END, _) if isFocused =>
|
|
||||||
events += new CursorMoveEnd
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_BACK, _) if isFocused =>
|
|
||||||
events += new EraseCharBack
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_DELETE, _) if isFocused =>
|
|
||||||
events += new EraseCharFront
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_INSERT, _) if isFocused =>
|
|
||||||
// TODO: insert the whole clipboard string at once instead of going char-by-char.
|
|
||||||
for (char <- UiHandler.clipboard.toCharArray) {
|
|
||||||
events += new WriteChar(char)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.consume()
|
protected var placeholder: Array[Int] = Array.empty
|
||||||
|
def placeholder_=(value: String): Unit = placeholder = value.codePoints().toArray
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_V, _)
|
|
||||||
if isFocused
|
|
||||||
&& KeyEvents.isControlDown =>
|
|
||||||
|
|
||||||
// TODO: insert the whole clipboard string at once instead of going char-by-char.
|
|
||||||
for (char <- UiHandler.clipboard.toCharArray) {
|
|
||||||
events += new WriteChar(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused =>
|
|
||||||
onConfirm()
|
|
||||||
event.consume()
|
|
||||||
|
|
||||||
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, _, char) if isFocused && !char.isControl =>
|
|
||||||
events += new WriteChar(char)
|
|
||||||
event.consume()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setInput(text: String): Unit = {
|
|
||||||
this.chars = text.toCharArray
|
|
||||||
cursorPos = 0
|
|
||||||
cursorOffset = 0
|
|
||||||
textWidth = 0
|
|
||||||
textChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override def minimumSize: Size2D = Size2D(200, 24)
|
|
||||||
override def maximumSize: Size2D = Size2D(Float.PositiveInfinity, 24)
|
|
||||||
|
|
||||||
private val foregroundAnimation = new ColorAnimation(targetForegroundColor, 7f)
|
|
||||||
private val borderAnimation = new ColorAnimation(targetBorderColor, 7f)
|
|
||||||
|
|
||||||
private def updateAnimationTargets(): Unit = {
|
|
||||||
foregroundAnimation.goto(targetForegroundColor)
|
|
||||||
borderAnimation.goto(targetBorderColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
def focus(): Unit = {
|
def focus(): Unit = {
|
||||||
if (!isFocused) {
|
if (!isFocused) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
isFocused = true
|
isFocused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAnimationTargets()
|
updateAnimationTargets()
|
||||||
}
|
}
|
||||||
|
|
||||||
blinkTimer = 0
|
blinkTimer = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,8 +109,298 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// widget management
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
|
override def minimumSize: Size2D = Size2D(200, 24)
|
||||||
|
override def maximumSize: Size2D = Size2D(Float.PositiveInfinity, 24)
|
||||||
|
|
||||||
|
override def receiveAllMouseEvents = true
|
||||||
|
override protected def receiveDragEvents: Boolean = true
|
||||||
|
override protected def receiveClickEvents: Boolean = true
|
||||||
|
override protected def spamDragEvents: Boolean = false
|
||||||
|
|
||||||
|
private def mouseInBounds: Boolean = clippedBounds.contains(UiHandler.mousePosition)
|
||||||
|
|
||||||
|
protected def font: Font = Font.NormalFont
|
||||||
|
|
||||||
|
private def charWidth(codePoint: Int): Int = font.charWidth(codePoint)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates given text width in pixels.
|
||||||
|
* @param from inclusive
|
||||||
|
* @param to exclusive
|
||||||
|
*/
|
||||||
|
// noinspection SameParameterValue
|
||||||
|
private def charsWidth(chars: Array[Int], from: Int, to: Int): Int = {
|
||||||
|
var width = 0
|
||||||
|
for (index <- (from max 0) until (to min chars.length)) {
|
||||||
|
width += font.charWidth(chars(index))
|
||||||
|
}
|
||||||
|
width
|
||||||
|
}
|
||||||
|
|
||||||
|
eventHandlers += {
|
||||||
|
case MouseEvent.StateChanged(MouseEvent.State.Pressed, _) if enabled =>
|
||||||
|
val inBounds = mouseInBounds
|
||||||
|
if (isFocused && !inBounds) unfocus()
|
||||||
|
if (!isFocused && inBounds) focus()
|
||||||
|
|
||||||
|
if (isFocused) {
|
||||||
|
val pos = pixelToCursorPosition(UiHandler.mousePosition.x - bounds.x)
|
||||||
|
cursor.position = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventHandlers += {
|
||||||
|
case MouseEvent.StateChanged(MouseEvent.State.Pressed, MouseEvent.Button.Right) if isFocused && mouseInBounds =>
|
||||||
|
val menu = new ContextMenu
|
||||||
|
|
||||||
|
if (selection.nonEmpty) {
|
||||||
|
menu.addEntry(ContextMenuEntry("Cut", IconSource.Icons.Cut) { cutSelection() })
|
||||||
|
menu.addEntry(ContextMenuEntry("Copy", IconSource.Icons.Copy) { copySelection() })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UiHandler.clipboard.nonEmpty) {
|
||||||
|
menu.addEntry(ContextMenuEntry("Paste", IconSource.Icons.Paste) { pasteSelection() })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_text.chars.nonEmpty) {
|
||||||
|
if (menu.children.nonEmpty) {
|
||||||
|
menu.addSeparator()
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.addEntry(ContextMenuEntry("Select all") { selectAll() })
|
||||||
|
}
|
||||||
|
|
||||||
|
root.get.contextMenus.open(menu)
|
||||||
|
|
||||||
|
case MouseEvent.StateChanged(MouseEvent.State.Pressed, _) if isFocused && mouseInBounds =>
|
||||||
|
selection = None
|
||||||
|
|
||||||
|
case DoubleClickEvent(MouseEvent.Button.Left, _) if isFocused && mouseInBounds =>
|
||||||
|
selectWord()
|
||||||
|
|
||||||
|
case DragEvent(DragEvent.State.Start | DragEvent.State.Drag, MouseEvent.Button.Left, mouse) if isFocused =>
|
||||||
|
val pos = if (mouse.y < bounds.y) {
|
||||||
|
0
|
||||||
|
} else if (mouse.y > bounds.max.y) {
|
||||||
|
_text.chars.length
|
||||||
|
} else {
|
||||||
|
pixelToCursorPosition(mouse.x - bounds.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
extendSelection(pos, prevCursorPosition.value)
|
||||||
|
cursor.position = pos
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_LEFT, _) if isFocused =>
|
||||||
|
handleKeyMovement(selection match {
|
||||||
|
case Some(Selection.Ordered(start, _)) if !KeyEvents.isShiftDown => start
|
||||||
|
case _ => cursor.position - 1
|
||||||
|
})
|
||||||
|
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_RIGHT, _) if isFocused =>
|
||||||
|
handleKeyMovement(selection match {
|
||||||
|
case Some(Selection.Ordered(_, end)) if !KeyEvents.isShiftDown => end
|
||||||
|
case _ => cursor.position + 1
|
||||||
|
})
|
||||||
|
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_HOME, _) if isFocused =>
|
||||||
|
handleKeyMovement(0)
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_END, _) if isFocused =>
|
||||||
|
handleKeyMovement(_text.chars.length)
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_BACK, _) if isFocused =>
|
||||||
|
if (selection.nonEmpty) {
|
||||||
|
deleteSelection()
|
||||||
|
} else {
|
||||||
|
val (lhs, rhs) = _text.chars.splitAt(cursor.position)
|
||||||
|
if (!lhs.isEmpty) {
|
||||||
|
_text.chars = lhs.take(lhs.length - 1) ++ rhs
|
||||||
|
cursor.position -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_DELETE, _) if isFocused =>
|
||||||
|
if (selection.nonEmpty) {
|
||||||
|
deleteSelection()
|
||||||
|
} else {
|
||||||
|
val (lhs, rhs) = _text.chars.splitAt(cursor.position)
|
||||||
|
if (!rhs.isEmpty) {
|
||||||
|
_text.chars = lhs ++ rhs.drop(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_A, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown =>
|
||||||
|
selectAll()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_W, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown =>
|
||||||
|
selectWord()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_C, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown && selection.nonEmpty =>
|
||||||
|
copySelection()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_X, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown && selection.nonEmpty =>
|
||||||
|
cutSelection()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_INSERT, _) if isFocused =>
|
||||||
|
pasteSelection()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, Keyboard.KEY_V, _)
|
||||||
|
if isFocused && KeyEvents.isControlDown =>
|
||||||
|
pasteSelection()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press, Keyboard.KEY_RETURN, _) if isFocused =>
|
||||||
|
onConfirm()
|
||||||
|
event.consume()
|
||||||
|
|
||||||
|
case event @ KeyEvent(KeyEvent.State.Press | KeyEvent.State.Repeat, _, char) if isFocused && !char.isControl =>
|
||||||
|
if (selection.nonEmpty) {
|
||||||
|
deleteSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
writeChar(char)
|
||||||
|
event.consume()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will try to find out which cursor position corresponds to the point on the screen.
|
||||||
|
* @param x widget-local coordinate
|
||||||
|
*/
|
||||||
|
private def pixelToCursorPosition(x: Float): Int = {
|
||||||
|
val absoluteX = x + scroll - 4
|
||||||
|
var width = 0
|
||||||
|
var pos = 0
|
||||||
|
while (width < absoluteX && pos < _text.chars.length) {
|
||||||
|
width += charWidth(_text.chars(pos))
|
||||||
|
if (width < absoluteX) pos += 1
|
||||||
|
}
|
||||||
|
pos
|
||||||
|
}
|
||||||
|
|
||||||
|
private def extendSelection(position: Int, cursorPosition: Int = cursor.position): Unit = {
|
||||||
|
selection = Selection(selection.fold(cursorPosition)(_.start), position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def handleKeyMovement(position: Int): Unit = {
|
||||||
|
if (KeyEvents.isShiftDown) {
|
||||||
|
extendSelection(position)
|
||||||
|
} else {
|
||||||
|
selection = None
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.position = position
|
||||||
|
}
|
||||||
|
|
||||||
|
private def selectAll(): Unit = {
|
||||||
|
selection = Selection(0, _text.chars.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def selectWord(): Unit = {
|
||||||
|
val from = 0 max (_text.chars.lastIndexWhere(isWhitespace, cursor.position - 1) + 1)
|
||||||
|
val to = _text.chars.indexWhere(isWhitespace, cursor.position)
|
||||||
|
val clampedTo = if (to >= 0 && to < _text.chars.length) to else _text.chars.length
|
||||||
|
selection = Selection(from, clampedTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def copySelection(): Unit = {
|
||||||
|
UiHandler.clipboard = selectedText
|
||||||
|
}
|
||||||
|
|
||||||
|
private def cutSelection(): Unit = {
|
||||||
|
UiHandler.clipboard = selectedText
|
||||||
|
deleteSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def pasteSelection(): Unit = {
|
||||||
|
if (selection.nonEmpty) deleteSelection()
|
||||||
|
writeString(UiHandler.clipboard)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def deleteSelection(): Unit = {
|
||||||
|
for (Selection.Ordered(start, end) <- selection) {
|
||||||
|
_text.chars = _text.chars.take(start) ++ _text.chars.drop(end)
|
||||||
|
cursor.position = start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def writeString(string: String): Unit = {
|
||||||
|
val (lhs, rhs) = _text.chars.splitAt(cursor.position)
|
||||||
|
val array = string.codePoints().toArray
|
||||||
|
_text.chars = lhs ++ array ++ rhs
|
||||||
|
cursor.position += array.length
|
||||||
|
}
|
||||||
|
|
||||||
|
private def writeChar(codePoint: Int): Unit = {
|
||||||
|
val (lhs, rhs) = _text.chars.splitAt(cursor.position)
|
||||||
|
_text.chars = lhs ++ Array(codePoint) ++ rhs
|
||||||
|
cursor.position += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a set of corrections to the scroll to make sure the cursor and text stay visible
|
||||||
|
*/
|
||||||
|
private def adjustScroll(): Unit = {
|
||||||
|
// make cursor visible
|
||||||
|
if (cursorOffset < scroll) scroll = cursorOffset
|
||||||
|
if (cursorOffset - scroll > size.width - 16) scroll = cursorOffset - size.width + 16
|
||||||
|
// apply pressure from the left (to maximize visible text, for nicer editing experience)
|
||||||
|
val fullTextWidth = charsWidth(_text.chars, 0, _text.chars.length)
|
||||||
|
val areaWidth = size.width - 16
|
||||||
|
if (fullTextWidth > areaWidth && fullTextWidth - scroll < areaWidth) scroll = fullTextWidth - areaWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
override def update(): Unit = {
|
||||||
|
super.update()
|
||||||
|
|
||||||
|
// process state changes
|
||||||
|
if (_text.changed()) {
|
||||||
|
onInput(text)
|
||||||
|
updateAnimationTargets()
|
||||||
|
adjustScroll()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabledRegister.update()) {
|
||||||
|
updateAnimationTargets()
|
||||||
|
}
|
||||||
|
if (isFocused && !enabled) {
|
||||||
|
unfocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// update everything
|
||||||
|
prevCursorPosition.update()
|
||||||
|
foregroundAnimation.update()
|
||||||
|
borderAnimation.update()
|
||||||
|
blinkTimer = (blinkTimer + UiHandler.dt) % CursorBlinkTime
|
||||||
|
}
|
||||||
|
|
||||||
|
private def updateAnimationTargets(): Unit = {
|
||||||
|
foregroundAnimation.goto(targetForegroundColor)
|
||||||
|
borderAnimation.goto(targetBorderColor)
|
||||||
|
}
|
||||||
|
|
||||||
private def targetBorderColor: Color = ColorScheme(
|
private def targetBorderColor: Color = ColorScheme(
|
||||||
if (validator(chars.mkString)) {
|
if (validator(text)) {
|
||||||
if (isFocused) "TextInputBorderFocused"
|
if (isFocused) "TextInputBorderFocused"
|
||||||
else if (!enabled) "TextInputBorderDisabled"
|
else if (!enabled) "TextInputBorderDisabled"
|
||||||
else "TextInputBorder"
|
else "TextInputBorder"
|
||||||
@ -168,39 +410,33 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
else "TextInputBorderError"
|
else "TextInputBorderError"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
private def targetForegroundColor: Color = ColorScheme(
|
private def targetForegroundColor: Color = ColorScheme(
|
||||||
if (!enabled) "TextInputForegroundDisabled"
|
if (!enabled) "TextInputForegroundDisabled"
|
||||||
else "TextInputForeground"
|
else "TextInputForeground"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val placeholderForegroundColor: Color = ColorScheme("TextInputForegroundDisabled")
|
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
if (textWidth == 0f && chars.nonEmpty)
|
|
||||||
textWidth = chars.map(g.font.charWidth(_)).sum
|
|
||||||
|
|
||||||
textChanged = false
|
|
||||||
for (event <- events) event.handle(g)
|
|
||||||
events.clear()
|
|
||||||
|
|
||||||
if (textChanged) {
|
|
||||||
val str = chars.mkString
|
|
||||||
onInput(str)
|
|
||||||
updateAnimationTargets()
|
|
||||||
}
|
|
||||||
|
|
||||||
g.rect(bounds, hoverAnimation.color)
|
g.rect(bounds, hoverAnimation.color)
|
||||||
DrawUtils.ring(g, position.x, position.y, size.width, size.height, thickness = 2, borderAnimation.color)
|
DrawUtils.ring(g, position.x, position.y, size.width, size.height, thickness = 2, borderAnimation.color)
|
||||||
|
|
||||||
g.setScissor(position.x + 4, position.y, size.width - 8f, size.height)
|
g.setScissor(position.x + 4, position.y, size.width - 8f, size.height)
|
||||||
|
|
||||||
|
for ((start, end) <- selectionOffsets) {
|
||||||
|
val width = end - start
|
||||||
|
g.rect(position.x + start + 8 - scroll, position.y + 4, width, size.height - 8, BackgroundSelectedColor)
|
||||||
|
}
|
||||||
|
|
||||||
g.background = Color.Transparent
|
g.background = Color.Transparent
|
||||||
g.foreground = if (chars.nonEmpty || isFocused) foregroundAnimation.color else placeholderForegroundColor
|
val foreground = if (_text.chars.nonEmpty || isFocused) foregroundAnimation.color else PlaceholderForegroundColor
|
||||||
|
g.foreground = foreground
|
||||||
|
|
||||||
var charOffset = 0
|
var charOffset = 0
|
||||||
val charsToDisplay = if (chars.nonEmpty || isFocused) chars else placeholder
|
val charsToDisplay = if (_text.chars.nonEmpty || isFocused) _text.chars else placeholder
|
||||||
for (char <- charsToDisplay) {
|
for (char <- charsToDisplay) {
|
||||||
|
for ((start, end) <- selectionOffsets) {
|
||||||
|
g.foreground = if (charOffset >= start && charOffset < end) ForegroundSelectedColor else foreground
|
||||||
|
}
|
||||||
|
|
||||||
g.char(position.x + 8 + charOffset - scroll, position.y + 4, char)
|
g.char(position.x + 8 + charOffset - scroll, position.y + 4, char)
|
||||||
charOffset += g.font.charWidth(char)
|
charOffset += g.font.charWidth(char)
|
||||||
}
|
}
|
||||||
@ -209,149 +445,36 @@ class TextInput(val initialText: String = "") extends Widget with MouseHandler w
|
|||||||
g.rect(position.x + 7 + cursorOffset - scroll, position.y + 4, 2, 16, borderAnimation.color)
|
g.rect(position.x + 7 + cursorOffset - scroll, position.y + 4, 2, 16, borderAnimation.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def update(): Unit = {
|
|
||||||
super.update()
|
|
||||||
|
|
||||||
val nextEnabled = enabled
|
|
||||||
|
|
||||||
if (nextEnabled != prevEnabled) {
|
|
||||||
updateAnimationTargets()
|
|
||||||
prevEnabled = nextEnabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFocused && !enabled) {
|
object TextInput {
|
||||||
unfocus()
|
class Text(initialValue: Array[Int]) extends Watcher(initialValue) {
|
||||||
|
def chars: Array[Int] = value
|
||||||
|
def chars_=(newValue: Array[Int]): Unit = value = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
foregroundAnimation.update()
|
class Cursor(initialValue: Int = 0) extends Watcher(initialValue) {
|
||||||
borderAnimation.update()
|
def position: Int = value
|
||||||
blinkTimer = (blinkTimer + UiHandler.dt) % CursorBlinkTime
|
def position_=(newValue: Int): Unit = value = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
private def charWidth(g: Graphics, c: Char): Int = g.font.charWidth(c)
|
case class Selection(start: Int, end: Int) {
|
||||||
|
require(start != end)
|
||||||
// noinspection SameParameterValue
|
|
||||||
private def charsWidth(g: Graphics, chars: Array[Char], first: Int, last: Int): Int = {
|
|
||||||
var width = 0
|
|
||||||
for (index <- first to last) {
|
|
||||||
width += g.font.charWidth(chars(index))
|
|
||||||
}
|
|
||||||
width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def adjustScroll(): Unit = {
|
object Selection {
|
||||||
if (cursorOffset < scroll)
|
def apply(start: Int, end: Int): Option[Selection] = {
|
||||||
scroll = cursorOffset
|
Option.when(start != end) {
|
||||||
if (cursorOffset - scroll > size.width - 16)
|
new Selection(start, end)
|
||||||
scroll = cursorOffset - size.width + 16
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class TextEvent {
|
|
||||||
def handle(g: Graphics): Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor this mess. have actions only move the cursor position explicitly.
|
|
||||||
// then calculate the cursor offset (incl. the scroll offset) based on that automatically
|
|
||||||
// rather than incrementally in the actions.
|
|
||||||
private class CursorMoveLeft extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
if (cursorPos == 0) return
|
|
||||||
|
|
||||||
cursorOffset -= charWidth(g, chars(cursorPos - 1))
|
|
||||||
cursorPos -= 1
|
|
||||||
blinkTimer = 0
|
|
||||||
|
|
||||||
adjustScroll()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CursorMoveRight extends TextEvent {
|
object Ordered {
|
||||||
override def handle(g: Graphics): Unit = {
|
def unapply(selection: Selection): Some[(Int, Int)] = {
|
||||||
if (cursorPos >= chars.length) return
|
val Selection(start, end) = selection
|
||||||
|
|
||||||
cursorOffset += charWidth(g, chars(cursorPos))
|
Some((start min end, start max end))
|
||||||
cursorPos += 1
|
}
|
||||||
blinkTimer = 0
|
|
||||||
|
|
||||||
adjustScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveStart extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
cursorPos = 0
|
|
||||||
cursorOffset = 0
|
|
||||||
blinkTimer = 0
|
|
||||||
scroll = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveEnd extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
cursorPos = chars.length
|
|
||||||
cursorOffset = textWidth
|
|
||||||
blinkTimer = 0
|
|
||||||
scroll = (textWidth - size.width + 16).max(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CursorMoveTo(mouseX: Float) extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val absoluteX = mouseX - bounds.x + scroll - 4
|
|
||||||
var width = 0
|
|
||||||
var pos = 0
|
|
||||||
while (width < absoluteX && pos < chars.length) {
|
|
||||||
width += g.font.charWidth(chars(pos))
|
|
||||||
if (width < absoluteX) pos += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cursorPos = chars.length.min(pos).max(0)
|
|
||||||
cursorOffset = if (cursorPos > 0) charsWidth(g, chars, 0, (cursorPos - 1).max(0)) else 0
|
|
||||||
blinkTimer = 0
|
|
||||||
adjustScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EraseCharBack extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val (lhs, rhs) = chars.splitAt(cursorPos)
|
|
||||||
if (lhs.isEmpty) return
|
|
||||||
|
|
||||||
val cw = charWidth(g, lhs.last)
|
|
||||||
chars = lhs.take(lhs.length - 1) ++ rhs
|
|
||||||
textChanged = true
|
|
||||||
textWidth -= cw
|
|
||||||
cursorOffset -= cw
|
|
||||||
cursorPos -= 1
|
|
||||||
blinkTimer = 0
|
|
||||||
scroll = (scroll - cw).max(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EraseCharFront extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val (lhs, rhs) = chars.splitAt(cursorPos)
|
|
||||||
if (rhs.isEmpty) return
|
|
||||||
|
|
||||||
val cw = charWidth(g, rhs.head)
|
|
||||||
chars = lhs ++ rhs.drop(1)
|
|
||||||
textChanged = true
|
|
||||||
textWidth -= cw
|
|
||||||
blinkTimer = 0
|
|
||||||
|
|
||||||
if (rhs.drop(1).map(charWidth(g, _)).sum < size.width - 16)
|
|
||||||
scroll = (scroll - cw).max(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class WriteChar(char: Char) extends TextEvent {
|
|
||||||
override def handle(g: Graphics): Unit = {
|
|
||||||
val (lhs, rhs) = chars.splitAt(cursorPos)
|
|
||||||
chars = lhs ++ Array(char) ++ rhs
|
|
||||||
textChanged = true
|
|
||||||
textWidth += charWidth(g, char)
|
|
||||||
(new CursorMoveRight).handle(g)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,9 +69,12 @@ class ContextMenuEntry(
|
|||||||
override protected def receiveClickEvents: Boolean = true
|
override protected def receiveClickEvents: Boolean = true
|
||||||
|
|
||||||
eventHandlers += {
|
eventHandlers += {
|
||||||
case MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) if !contextMenu.isOpening =>
|
case event @ MouseEvent(MouseEvent.State.Pressed, MouseEvent.Button.Left) if !contextMenu.isOpening =>
|
||||||
clickSoundSource.press.play()
|
clickSoundSource.press.play()
|
||||||
case ClickEvent(MouseEvent.Button.Left, _) if !contextMenu.isOpening => clicked()
|
event.consume()
|
||||||
|
case event @ ClickEvent(MouseEvent.Button.Left, _) if !contextMenu.isOpening =>
|
||||||
|
clicked()
|
||||||
|
event.consume()
|
||||||
case HoverEvent(HoverEvent.State.Enter) => enter()
|
case HoverEvent(HoverEvent.State.Enter) => enter()
|
||||||
case HoverEvent(HoverEvent.State.Leave) if !isGhost => leave()
|
case HoverEvent(HoverEvent.State.Leave) if !isGhost => leave()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,7 +158,7 @@ class SystemSettingsTab extends SettingsTab with Logging {
|
|||||||
|
|
||||||
private def setConfigPath(path: String): Unit = {
|
private def setConfigPath(path: String): Unit = {
|
||||||
Settings.get.brainCustomConfigPath = Some(path)
|
Settings.get.brainCustomConfigPath = Some(path)
|
||||||
textInput.setInput(path)
|
textInput.text = path
|
||||||
restartWarning.isVisible = true
|
restartWarning.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -157,8 +157,8 @@ class StatusBar extends Widget {
|
|||||||
|
|
||||||
override def receiveMouseEvents: Boolean = true
|
override def receiveMouseEvents: Boolean = true
|
||||||
|
|
||||||
override protected val hoverAnimationColorActive: Color = ColorScheme("StatusBarActive")
|
override protected val HoverAnimationColorActive: Color = ColorScheme("StatusBarActive")
|
||||||
override protected val hoverAnimationColorDefault: Color = hoverAnimationColorActive.toRGBANorm.withAlpha(0)
|
override protected val HoverAnimationColorDefault: Color = HoverAnimationColorActive.toRGBANorm.withAlpha(0)
|
||||||
|
|
||||||
override def draw(g: Graphics): Unit = {
|
override def draw(g: Graphics): Unit = {
|
||||||
g.rect(bounds, hoverAnimation.color)
|
g.rect(bounds, hoverAnimation.color)
|
||||||
|
|||||||
@ -13,23 +13,23 @@ import ocelot.desktop.util.animation.ColorAnimation
|
|||||||
*/
|
*/
|
||||||
trait HoverAnimation extends Widget with EventAware with HoverHandler with Updatable {
|
trait HoverAnimation extends Widget with EventAware with HoverHandler with Updatable {
|
||||||
//noinspection ScalaWeakerAccess
|
//noinspection ScalaWeakerAccess
|
||||||
protected val hoverAnimationSpeedEnter: Float = AnimationSpeedHoverEnter
|
protected val HoverAnimationSpeedEnter: Float = AnimationSpeedHoverEnter
|
||||||
//noinspection ScalaWeakerAccess
|
//noinspection ScalaWeakerAccess
|
||||||
protected val hoverAnimationSpeedLeave: Float = AnimationSpeedHoverLeave
|
protected val HoverAnimationSpeedLeave: Float = AnimationSpeedHoverLeave
|
||||||
protected val hoverAnimationColorDefault: Color = ColorScheme("ButtonBackground")
|
protected val HoverAnimationColorDefault: Color = ColorScheme("ButtonBackground")
|
||||||
protected val hoverAnimationColorActive: Color = ColorScheme("ButtonBackgroundActive")
|
protected val HoverAnimationColorActive: Color = ColorScheme("ButtonBackgroundActive")
|
||||||
|
|
||||||
protected lazy val hoverAnimation: ColorAnimation =
|
protected lazy val hoverAnimation: ColorAnimation =
|
||||||
new ColorAnimation(hoverAnimationColorDefault, hoverAnimationSpeedEnter)
|
new ColorAnimation(HoverAnimationColorDefault, HoverAnimationSpeedEnter)
|
||||||
|
|
||||||
eventHandlers += {
|
eventHandlers += {
|
||||||
case Capturing(HoverEvent(HoverEvent.State.Enter)) =>
|
case Capturing(HoverEvent(HoverEvent.State.Enter)) =>
|
||||||
hoverAnimation.speed = hoverAnimationSpeedEnter
|
hoverAnimation.speed = HoverAnimationSpeedEnter
|
||||||
hoverAnimation.goto(hoverAnimationColorActive)
|
hoverAnimation.goto(HoverAnimationColorActive)
|
||||||
|
|
||||||
case Capturing(HoverEvent(HoverEvent.State.Leave)) =>
|
case Capturing(HoverEvent(HoverEvent.State.Leave)) =>
|
||||||
hoverAnimation.speed = hoverAnimationSpeedLeave
|
hoverAnimation.speed = HoverAnimationSpeedLeave
|
||||||
hoverAnimation.goto(hoverAnimationColorDefault)
|
hoverAnimation.goto(HoverAnimationColorDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def update(): Unit = {
|
override def update(): Unit = {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
package ocelot.desktop.util
|
package ocelot.desktop.util
|
||||||
|
|
||||||
import ocelot.desktop.ui.widget.Updatable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a value updated by calls to [[update]].
|
* Stores a value updated by calls to [[update]].
|
||||||
*/
|
*/
|
||||||
|
|||||||
33
src/main/scala/ocelot/desktop/util/Watcher.scala
Normal file
33
src/main/scala/ocelot/desktop/util/Watcher.scala
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package ocelot.desktop.util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps a reference to an object
|
||||||
|
* and tells whether there were any changes to the value since the last check.
|
||||||
|
*/
|
||||||
|
class Watcher[T](initialValue: T) {
|
||||||
|
private var dirty = false
|
||||||
|
private var _callback: Option[T => Unit] = None
|
||||||
|
private var _value: T = initialValue
|
||||||
|
|
||||||
|
def value: T = _value
|
||||||
|
def value_=(newValue: T): Unit = {
|
||||||
|
dirty = _value != newValue
|
||||||
|
if (dirty) {
|
||||||
|
_callback.foreach(_(newValue))
|
||||||
|
}
|
||||||
|
_value = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
def onChange(callback: T => Unit): Unit = _callback = Some(callback)
|
||||||
|
|
||||||
|
def changed(): Boolean = {
|
||||||
|
if (dirty) {
|
||||||
|
dirty = false
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Watcher {
|
||||||
|
def apply[T](value: T) = new Watcher(value)
|
||||||
|
}
|
||||||
@ -24,7 +24,7 @@ class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWin
|
|||||||
children :+= new TextInput() {
|
children :+= new TextInput() {
|
||||||
override def onConfirm(): Unit = {
|
override def onConfirm(): Unit = {
|
||||||
pushLine(text)
|
pushLine(text)
|
||||||
setInput("")
|
text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user