badline 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/CLAUDE.md +8 -7
  4. data/CONTRIBUTING.md +7 -6
  5. data/README.md +12 -5
  6. data/doc/media.md +28 -1
  7. data/doc/pinned-behaviour.md +31 -1
  8. data/exe/badline-ruby +1 -1
  9. data/lib/badline/c128/bus.rb +4 -17
  10. data/lib/badline/c128/drives.rb +35 -22
  11. data/lib/badline/c128/fast_serial.rb +1 -0
  12. data/lib/badline/c128/io_pages.rb +35 -0
  13. data/lib/badline/c128/mmu.rb +9 -7
  14. data/lib/badline/c128/modes.rb +17 -4
  15. data/lib/badline/c128/saved_state.rb +22 -0
  16. data/lib/badline/c128/z80.rb +49 -0
  17. data/lib/badline/c128/z80_pages.rb +118 -0
  18. data/lib/badline/c128/z80_turns.rb +106 -0
  19. data/lib/badline/c128.rb +16 -19
  20. data/lib/badline/cartridge/action_replay.rb +2 -2
  21. data/lib/badline/cartridge/comal80.rb +1 -7
  22. data/lib/badline/cartridge/dinamic.rb +0 -8
  23. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  24. data/lib/badline/cartridge/fun_play.rb +0 -8
  25. data/lib/badline/cartridge/game_system.rb +0 -8
  26. data/lib/badline/cartridge/kcs_power.rb +10 -7
  27. data/lib/badline/cartridge/mach5.rb +2 -4
  28. data/lib/badline/cartridge/magic_desk.rb +0 -8
  29. data/lib/badline/cartridge/ocean.rb +0 -8
  30. data/lib/badline/cartridge/pagefox.rb +2 -2
  31. data/lib/badline/cartridge/retro_replay.rb +7 -6
  32. data/lib/badline/cartridge/rex_utility.rb +2 -6
  33. data/lib/badline/cartridge/rgcd.rb +2 -2
  34. data/lib/badline/cartridge/simons_basic.rb +0 -5
  35. data/lib/badline/cartridge/super_games.rb +4 -2
  36. data/lib/badline/cartridge/westermann.rb +2 -6
  37. data/lib/badline/cartridge.rb +37 -9
  38. data/lib/badline/cia/fast_forward.rb +21 -3
  39. data/lib/badline/cia.rb +8 -5
  40. data/lib/badline/computer/attachments.rb +4 -56
  41. data/lib/badline/computer/true_drives.rb +80 -0
  42. data/lib/badline/computer.rb +4 -0
  43. data/lib/badline/core.rb +2 -0
  44. data/lib/badline/drive/core.rb +8 -33
  45. data/lib/badline/drive/fast_serial.rb +73 -0
  46. data/lib/badline/drive/idle.rb +1 -11
  47. data/lib/badline/drive/orbit.rb +2 -5
  48. data/lib/badline/drive/sleep.rb +0 -2
  49. data/lib/badline/drive/vias.rb +84 -0
  50. data/lib/badline/drive.rb +2 -0
  51. data/lib/badline/drive1541.rb +1 -0
  52. data/lib/badline/drive1571/wd1770.rb +94 -8
  53. data/lib/badline/drive1571.rb +8 -58
  54. data/lib/badline/drive1581/bus.rb +137 -0
  55. data/lib/badline/drive1581/cpu.rb +11 -0
  56. data/lib/badline/drive1581/disk.rb +135 -0
  57. data/lib/badline/drive1581/disk_state.rb +87 -0
  58. data/lib/badline/drive1581/mechanism.rb +117 -0
  59. data/lib/badline/drive1581/serial_port.rb +52 -0
  60. data/lib/badline/drive1581/slot.rb +78 -0
  61. data/lib/badline/drive1581/track.rb +100 -0
  62. data/lib/badline/drive1581/track_bytes.rb +75 -0
  63. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  64. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  65. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  66. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  67. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  68. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  69. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  70. data/lib/badline/drive1581/wd1772.rb +216 -0
  71. data/lib/badline/drive1581.rb +233 -0
  72. data/lib/badline/frontend/app.rb +1 -3
  73. data/lib/badline/frontend/audio_device.rb +67 -0
  74. data/lib/badline/frontend/audio_sink.rb +16 -50
  75. data/lib/badline/frontend/boot.rb +18 -10
  76. data/lib/badline/frontend/buttons.rb +7 -7
  77. data/lib/badline/frontend/keys.rb +33 -8
  78. data/lib/badline/frontend/media_slots.rb +42 -0
  79. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  80. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  81. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  82. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  83. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
  84. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  85. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  86. data/lib/badline/frontend/menu/theme.rb +18 -0
  87. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  88. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  89. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  90. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  91. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  92. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  93. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  94. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  95. data/lib/badline/frontend/player/theme.rb +18 -0
  96. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  97. data/lib/badline/frontend/sound.rb +19 -38
  98. data/lib/badline/frontend/timeline.rb +3 -14
  99. data/lib/badline/frontend.rb +24 -20
  100. data/lib/badline/iec_bus.rb +14 -2
  101. data/lib/badline/machine.rb +5 -4
  102. data/lib/badline/media/boot_disk.rb +50 -0
  103. data/lib/badline/media/not_disk.rb +8 -0
  104. data/lib/badline/media/true_drive.rb +35 -15
  105. data/lib/badline/media.rb +9 -3
  106. data/lib/badline/options/event.rb +2 -2
  107. data/lib/badline/options/family.rb +3 -9
  108. data/lib/badline/options/help.rb +2 -1
  109. data/lib/badline/roms/README +9 -0
  110. data/lib/badline/roms/dos1581.rom +0 -0
  111. data/lib/badline/sdl.rb +37 -20
  112. data/lib/badline/snapshot/state.rb +5 -4
  113. data/lib/badline/storage/p00.rb +8 -0
  114. data/lib/badline/time_of_day.rb +3 -3
  115. data/lib/badline/version.rb +1 -1
  116. data/lib/badline/vic20/attachments.rb +21 -7
  117. data/lib/badline/vic20/saved_state.rb +2 -0
  118. data/lib/badline/vic20.rb +3 -0
  119. data/lib/badline/z80/core.rb +2 -0
  120. data/lib/badline/z80/saved_state.rb +69 -0
  121. metadata +54 -21
  122. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  123. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  124. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  125. /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
@@ -6,7 +6,27 @@ module Badline
6
6
  # keys, the C128's own keys and, in joystick mode, the joystick
7
7
  # directions.
8
8
  module Keys
9
+ RETURN = 40
10
+ ESCAPE = 41
11
+ BACKSPACE = 42
9
12
  TAB = 43
13
+ SPACE = 44
14
+ F8 = 65
15
+ F9 = 66
16
+ F10 = 67
17
+ F11 = 68
18
+ F12 = 69
19
+ HOME = 74
20
+ PAGE_UP = 75
21
+ END_KEY = 77
22
+ PAGE_DOWN = 78
23
+ RIGHT = 79
24
+ LEFT = 80
25
+ DOWN = 81
26
+ UP = 82
27
+
28
+ # The arrow keys, which move through the menus.
29
+ DIRECTIONS = { RIGHT => :right, LEFT => :left, DOWN => :down, UP => :up }.freeze
10
30
 
11
31
  LETTERS = %i[a b c d e f g h i j k l m n o p q r s t u v w x y z].freeze
12
32
  DIGITS = %i[1 2 3 4 5 6 7 8 9 0].freeze
@@ -36,14 +56,6 @@ module Badline
36
56
  63 => :forty_eighty, 72 => :forty_eighty, 78 => :run_stop
37
57
  }.freeze
38
58
 
39
- ESCAPE = 41
40
-
41
- F8 = 65
42
- F9 = 66
43
- F10 = 67
44
- F11 = 68
45
- F12 = 69
46
-
47
59
  ARROWS = { 44 => :fire, 228 => :fire, 79 => :right, 80 => :left, 81 => :down, 82 => :up }.freeze
48
60
  WASD = { 225 => :fire, 7 => :right, 4 => :left, 22 => :down, 26 => :up }.freeze
49
61
 
@@ -65,6 +77,19 @@ module Badline
65
77
  C128_OTHERS[scancode] || c64_key(scancode)
66
78
  end
67
79
 
80
+ # The character the key types on a US layout: a letter, a capital
81
+ # with `shift`, or a digit, and "" for any other key.
82
+ def self.character(scancode, shift)
83
+ if scancode.between?(4, 29)
84
+ letter = (97 + scancode - 4).chr
85
+ shift ? letter.upcase : letter
86
+ elsif scancode.between?(30, 39)
87
+ "1234567890"[scancode - 30]
88
+ else
89
+ ""
90
+ end
91
+ end
92
+
68
93
  def self.arrows(scancode) = ARROWS[scancode] || :none
69
94
 
70
95
  def self.wasd(scancode) = WASD[scancode] || :none
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Frontend
5
+ # The machine's media slots as the pause menu and the --at events
6
+ # both take media out of them: device 8, a true drive there or else
7
+ # the KERNAL traps, and the expansion port.
8
+ module MediaSlots
9
+ class << self
10
+ # Whether device 8 has a disk in.
11
+ def disk?(computer)
12
+ drive = Media::TrueDrive.drive(computer)
13
+ drive.nil? ? computer.mounted? : !drive.disk.nil?
14
+ end
15
+
16
+ # The expanded path of the disk in device 8, however it went in, or
17
+ # an empty one.
18
+ def disk_path(computer)
19
+ drive = Media::TrueDrive.drive(computer)
20
+ unless drive.nil?
21
+ disk = drive.disk
22
+ return disk.nil? ? "" : disk.path.to_s
23
+ end
24
+
25
+ path = computer.mounted_path
26
+ path.empty? ? path : File.expand_path(path)
27
+ end
28
+
29
+ def eject_disk(computer)
30
+ drive = Media::TrueDrive.drive(computer)
31
+ drive.nil? ? computer.unmount : drive.insert(nil)
32
+ end
33
+
34
+ # Takes the cartridge out with the power off.
35
+ def remove_cartridge(computer)
36
+ computer.address_bus.detach_cartridge
37
+ computer.power_cycle!
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -43,11 +43,11 @@ module Badline
43
43
 
44
44
  def draw(left, top)
45
45
  painter = @painter
46
- painter.text(left, top, TITLES.fetch(@kind), PauseMenu::BRIGHT)
47
- painter.text(left, top + 20, Painter.fit(File.basename(@path), 40), PauseMenu::TEXT) unless @path.empty?
46
+ painter.text(left, top, TITLES.fetch(@kind), MenuTheme::BRIGHT)
47
+ painter.text(left, top + 20, Painter.fit(File.basename(@path), 40), MenuTheme::TEXT) unless @path.empty?
48
48
  warning = WARNINGS.fetch(@kind)
49
- painter.text(left, top + 44, warning[0], PauseMenu::TEXT)
50
- painter.text(left, top + 56, warning[1], PauseMenu::TEXT) unless warning[1].empty?
49
+ painter.text(left, top + 44, warning[0], MenuTheme::TEXT)
50
+ painter.text(left, top + 56, warning[1], MenuTheme::TEXT) unless warning[1].empty?
51
51
  @buttons.row([left, top + 80, MenuPages::ROW_WIDTH], "CANCEL", :cancel)
52
52
  @buttons.row([left, top + 80 + Buttons::HEIGHT + 2, MenuPages::ROW_WIDTH], ANSWERS.fetch(@kind), :confirm)
53
53
  end
@@ -12,19 +12,6 @@ module Badline
12
12
  ROWS = 12
13
13
  COLUMNS = 40
14
14
 
15
- RETURN = 40
16
- ESCAPE = 41
17
- BACKSPACE = 42
18
- HOME = 74
19
- PAGE_UP = 75
20
- END_KEY = 77
21
- PAGE_DOWN = 78
22
- RIGHT = 79
23
- LEFT = 80
24
- DOWN = 81
25
- UP = 82
26
- DIGITS = "1234567890"
27
-
28
15
  attr_reader :directory
29
16
 
30
17
  def initialize(painter, buttons)
@@ -60,9 +47,9 @@ module Badline
60
47
  # Handles a key, and returns the path of a file picked, :cancel when
61
48
  # Esc leaves the browser, or nil.
62
49
  def key(scancode)
63
- return :cancel if scancode == ESCAPE
64
- return pick if [RETURN, RIGHT].include?(scancode)
65
- return up if [LEFT, BACKSPACE].include?(scancode)
50
+ return :cancel if scancode == Keys::ESCAPE
51
+ return pick if [Keys::RETURN, Keys::RIGHT].include?(scancode)
52
+ return up if [Keys::LEFT, Keys::BACKSPACE].include?(scancode)
66
53
 
67
54
  select(@selected + step(scancode)) unless step(scancode).zero?
68
55
  jump(scancode)
@@ -83,14 +70,14 @@ module Badline
83
70
  end
84
71
 
85
72
  def draw(left, top, width, title)
86
- @painter.text(left, top, title, PauseMenu::BRIGHT)
87
- @painter.text(left, top + ROW + 2, shown_directory, PauseMenu::DIM)
73
+ @painter.text(left, top, title, MenuTheme::BRIGHT)
74
+ @painter.text(left, top + ROW + 2, shown_directory, MenuTheme::DIM)
88
75
  @list_top = top + (ROW * 2) + 8
89
76
  @buttons.area([left, @list_top, width, ROW * ROWS], :browse)
90
77
  draw_rows(left, width)
91
78
  draw_bar(left + width - 2) if @names.size > ROWS
92
79
  @painter.text(left, @list_top + (ROW * ROWS) + 6, "RETURN: OPEN LEFT: BACK ESC: CANCEL",
93
- PauseMenu::DIM)
80
+ MenuTheme::DIM)
94
81
  end
95
82
 
96
83
  private
@@ -109,10 +96,10 @@ module Badline
109
96
  name = label(index)
110
97
  top = @list_top + (row * ROW)
111
98
  if index == @selected
112
- @painter.box(left - 2, top - 2, width - 4, ROW, PauseMenu::TEXT)
113
- @painter.text(left, top, name, PauseMenu::PANEL)
99
+ @painter.box(left - 2, top - 2, width - 4, ROW, MenuTheme::TEXT)
100
+ @painter.text(left, top, name, MenuTheme::PANEL)
114
101
  else
115
- @painter.text(left, top, name, index < @folders ? PauseMenu::BRIGHT : PauseMenu::TEXT)
102
+ @painter.text(left, top, name, index < @folders ? MenuTheme::BRIGHT : MenuTheme::TEXT)
116
103
  end
117
104
  end
118
105
  end
@@ -121,8 +108,8 @@ module Badline
121
108
  height = ROW * ROWS
122
109
  size = [(height * ROWS) / @names.size, 4].max
123
110
  offset = ((height - size) * @top) / [@names.size - ROWS, 1].max
124
- @painter.box(left, @list_top - 2, 2, height, PauseMenu::EDGE)
125
- @painter.box(left, @list_top - 2 + offset, 2, size, PauseMenu::TEXT)
111
+ @painter.box(left, @list_top - 2, 2, height, MenuTheme::EDGE)
112
+ @painter.box(left, @list_top - 2 + offset, 2, size, MenuTheme::TEXT)
126
113
  end
127
114
 
128
115
  def label(index)
@@ -133,12 +120,12 @@ module Badline
133
120
 
134
121
  def step(scancode)
135
122
  case scancode
136
- when UP then -1
137
- when DOWN then 1
138
- when PAGE_UP then -ROWS
139
- when PAGE_DOWN then ROWS
140
- when HOME then -@names.size
141
- when END_KEY then @names.size
123
+ when Keys::UP then -1
124
+ when Keys::DOWN then 1
125
+ when Keys::PAGE_UP then -ROWS
126
+ when Keys::PAGE_DOWN then ROWS
127
+ when Keys::HOME then -@names.size
128
+ when Keys::END_KEY then @names.size
142
129
  else 0
143
130
  end
144
131
  end
@@ -154,10 +141,7 @@ module Badline
154
141
  # Selects the next name after the one selected that starts with the
155
142
  # key's letter or digit.
156
143
  def jump(scancode)
157
- char = if scancode.between?(4, 29) then (97 + scancode - 4).chr
158
- elsif scancode.between?(30, 39) then DIGITS[scancode - 30]
159
- else ""
160
- end
144
+ char = Keys.character(scancode, false)
161
145
  return if char.empty? || @names.empty?
162
146
 
163
147
  @names.size.times do |offset|
@@ -14,10 +14,6 @@ module Badline
14
14
  TITLES = { disk: "INSERT A DISK", tape: "INSERT A TAPE", cartridge: "INSERT A CARTRIDGE",
15
15
  program: "QUICK OPEN", snapshot: "LOAD A SAVE" }.freeze
16
16
 
17
- ESCAPE = 41
18
- RETURN = 40
19
- SPACE = 44
20
-
21
17
  # The machine Quick open started or a save loaded.
22
18
  attr_reader :computer
23
19
 
@@ -78,10 +74,10 @@ module Badline
78
74
  if @name_field.open? && !@confirmation.open?
79
75
  return named(@name_field.key(scancode, SDL.event_mod(SDL.event).anybits?(SDL::KMOD_SHIFT)))
80
76
  end
81
- return answer(:cancel) if scancode == ESCAPE
82
- return answer(@buttons.focus) if [RETURN, SPACE].include?(scancode)
77
+ return answer(:cancel) if scancode == Keys::ESCAPE
78
+ return answer(@buttons.focus) if [Keys::RETURN, Keys::SPACE].include?(scancode)
83
79
 
84
- direction = PauseMenu::ARROWS[scancode]
80
+ direction = Keys::DIRECTIONS[scancode]
85
81
  @buttons.shift(direction == :up ? -1 : 1, 1) if %i[up down].include?(direction)
86
82
  nil
87
83
  end
@@ -25,40 +25,22 @@ module Badline
25
25
 
26
26
  def writable? = @writable
27
27
 
28
- # The expanded path of the disk in device 8, however it went in, or
29
- # an empty one.
30
- def disk_path
31
- drive = @computer.true_drive
32
- unless drive.nil?
33
- disk = drive.disk
34
- return disk.nil? ? "" : disk.path.to_s
35
- end
36
-
37
- path = @computer.mounted_path
38
- path.empty? ? path : File.expand_path(path)
39
- end
28
+ def disk_path = MediaSlots.disk_path(@computer)
40
29
 
41
- def inserted?
42
- drive = @computer.true_drive
43
- drive.nil? ? @computer.mounted? : !drive.disk.nil?
44
- end
30
+ def inserted? = MediaSlots.disk?(@computer)
45
31
 
46
32
  # Puts a disk in device 8 or a tape in the datasette, `kind` :disk or
47
33
  # :tape, and says whether it went in. A list puts its first disk in.
48
34
  def insert(kind, path)
49
- @problem = ""
50
- if kind == :disk
51
- disk = Media::DiskList.disk(path)
52
- Media.insert_disk(@computer, disk, read_only: !@writable)
53
- list(path)
54
- else
55
- @computer.datasette.insert(Storage::TAP.new(path))
35
+ succeeds? do
36
+ if kind == :disk
37
+ disk = Media::DiskList.disk(path)
38
+ Media.insert_disk(@computer, disk, read_only: !@writable)
39
+ list(path)
40
+ else
41
+ @computer.datasette.insert(Storage::TAP.new(path))
42
+ end
56
43
  end
57
- true
58
- rescue ArgumentError, SystemCallError, Storage::TAP::FormatError, Storage::T64::FormatError,
59
- Storage::G64Image::FormatError => e
60
- @problem = e.message
61
- false
62
44
  end
63
45
 
64
46
  # Does what the Drive page's :eject_disk, :previous_disk, :next_disk,
@@ -72,10 +54,7 @@ module Badline
72
54
  end
73
55
  end
74
56
 
75
- def eject_disk
76
- drive = @computer.true_drive
77
- drive.nil? ? @computer.unmount : drive.insert(nil)
78
- end
57
+ def eject_disk = MediaSlots.eject_disk(@computer)
79
58
 
80
59
  # Sets whether disks are writable, for the disk in the drive too,
81
60
  # which goes in again as the notch now says.
@@ -111,12 +90,7 @@ module Badline
111
90
  # Swaps the cartridge for the one at `path`, which power cycles the
112
91
  # machine, and says whether it went in.
113
92
  def attach_cartridge(path)
114
- @problem = ""
115
- Media.attach(@computer, path)
116
- true
117
- rescue ArgumentError, SystemCallError, Storage::CRTFile::FormatError, Cartridge::UnsupportedTypeError => e
118
- @problem = e.message
119
- false
93
+ succeeds? { Media.attach(@computer, path) }
120
94
  end
121
95
 
122
96
  # What a save of the machine is named after: the cartridge, the disk,
@@ -147,27 +121,28 @@ module Badline
147
121
  end
148
122
 
149
123
  # Takes the cartridge out with the power off.
150
- def remove_cartridge
151
- @computer.address_bus.detach_cartridge
152
- @computer.power_cycle!
153
- end
124
+ def remove_cartridge = MediaSlots.remove_cartridge(@computer)
154
125
 
155
126
  # A new machine started on `path`, as the command line would, or nil
156
127
  # when it won't start.
157
128
  def start(options, path)
158
- @problem = ""
159
- computer = Frontend.start(options, path, @writable)
160
- list(path)
161
- computer
162
- rescue ArgumentError, SystemCallError, Media::TrueDrive::Error, Storage::SIDFile::FormatError,
163
- Storage::T64::FormatError, Storage::TAP::FormatError, Storage::CRTFile::FormatError,
164
- Storage::G64Image::FormatError, Cartridge::UnsupportedTypeError => e
165
- @problem = e.message
166
- nil
129
+ computer = nil
130
+ started = succeeds? do
131
+ computer = Frontend.start(options, path, @writable)
132
+ list(path)
133
+ end
134
+ started ? computer : nil
167
135
  end
168
136
 
169
137
  private
170
138
 
139
+ # Runs the block, keeps the media error it raised as the problem, and
140
+ # says whether there was none.
141
+ def succeeds?(&)
142
+ @problem = Frontend.media_problem(&)
143
+ @problem.empty?
144
+ end
145
+
171
146
  # Keeps `path` as the list the set comes from, if it's a list.
172
147
  def list(path)
173
148
  return unless Media::DiskList.list?(path)
@@ -9,13 +9,10 @@ module Badline
9
9
  class MenuPages
10
10
  include MenuPorts
11
11
 
12
- TEXT = PauseMenu::TEXT
13
- DIM = PauseMenu::DIM
14
12
  LINE = 12
15
13
  COLUMNS = 25
16
14
  ROW_WIDTH = 200
17
15
 
18
- WARNING = 0xff7a6b
19
16
  RECENT = %i[recent0 recent1 recent2 recent3 recent4 recent5 recent6 recent7].freeze
20
17
 
21
18
  SID_MODELS = %i[mos6581 mos8580].freeze
@@ -51,7 +48,7 @@ module Badline
51
48
 
52
49
  def draw(page, left, top)
53
50
  problem = @media.problem
54
- @painter.text(left, top + 186, problem[0, COLUMNS], WARNING) unless problem.empty?
51
+ @painter.text(left, top + 186, problem[0, COLUMNS], MenuTheme::WARNING) unless problem.empty?
55
52
  @left = left
56
53
  @top = top
57
54
  case page
@@ -95,7 +92,7 @@ module Badline
95
92
  # Says in the dim colour that the VIC-20 hasn't got what the page
96
93
  # shows.
97
94
  def note
98
- @painter.text(@left, @top, "NOT ON THE VIC-20 YET", DIM)
95
+ @painter.text(@left, @top, "NOT ON THE VIC-20 YET", MenuTheme::DIM)
99
96
  end
100
97
 
101
98
  def draw_drive
@@ -182,11 +179,11 @@ module Badline
182
179
  # A label, and its value under it, or NONE in the dim colour when the
183
180
  # value is empty.
184
181
  def info(label, value)
185
- @painter.text(@left, @top, label, DIM)
182
+ @painter.text(@left, @top, label, MenuTheme::DIM)
186
183
  if value.empty?
187
- @painter.text(@left, @top + LINE, "NONE", DIM)
184
+ @painter.text(@left, @top + LINE, "NONE", MenuTheme::DIM)
188
185
  else
189
- @painter.text(@left, @top + LINE, Painter.fit(value, COLUMNS), TEXT)
186
+ @painter.text(@left, @top + LINE, Painter.fit(value, COLUMNS), MenuTheme::TEXT)
190
187
  end
191
188
  @top += (LINE * 2) + 6
192
189
  end
@@ -8,14 +8,8 @@ module Badline
8
8
  # name and Esc leaves it.
9
9
  class NameField
10
10
  COLUMNS = 30
11
- LETTERS = "abcdefghijklmnopqrstuvwxyz"
12
- DIGITS = "1234567890"
13
11
  OTHERS = { 44 => [" ", " "], 45 => ["-", "_"], 55 => [".", "."] }.freeze
14
12
 
15
- RETURN = 40
16
- ESCAPE = 41
17
- BACKSPACE = 42
18
-
19
13
  attr_reader :text
20
14
 
21
15
  def initialize(painter)
@@ -47,11 +41,11 @@ module Badline
47
41
  # Handles a key, and returns :done for Return, :cancel for Esc, or
48
42
  # nil.
49
43
  def key(scancode, shift)
50
- return :done if scancode == RETURN
51
- return :cancel if scancode == ESCAPE
44
+ return :done if scancode == Keys::RETURN
45
+ return :cancel if scancode == Keys::ESCAPE
52
46
 
53
47
  @problem = ""
54
- if scancode == BACKSPACE
48
+ if scancode == Keys::BACKSPACE
55
49
  @text = @text[0, @text.length - 1].to_s
56
50
  else
57
51
  char = character(scancode, shift)
@@ -62,26 +56,19 @@ module Badline
62
56
 
63
57
  def draw(left, top, width)
64
58
  painter = @painter
65
- painter.text(left, top, @title, PauseMenu::BRIGHT)
66
- painter.box(left, top + 18, width, Buttons::HEIGHT + 4, PauseMenu::FILL)
67
- painter.text(left + 4, top + 22, "#{@text}_", PauseMenu::TEXT)
68
- painter.text(left, top + 42, @problem, MenuPages::WARNING) unless @problem.empty?
69
- painter.text(left, top + 60, "RETURN: SAVE ESC: CANCEL", PauseMenu::DIM)
59
+ painter.text(left, top, @title, MenuTheme::BRIGHT)
60
+ painter.box(left, top + 18, width, Buttons::HEIGHT + 4, MenuTheme::FILL)
61
+ painter.text(left + 4, top + 22, "#{@text}_", MenuTheme::TEXT)
62
+ painter.text(left, top + 42, @problem, MenuTheme::WARNING) unless @problem.empty?
63
+ painter.text(left, top + 60, "RETURN: SAVE ESC: CANCEL", MenuTheme::DIM)
70
64
  end
71
65
 
72
66
  private
73
67
 
74
68
  def character(scancode, shift)
75
- if scancode.between?(4, 29)
76
- letter = LETTERS[scancode - 4]
77
- shift ? letter.upcase : letter
78
- elsif scancode.between?(30, 39)
79
- DIGITS[scancode - 30]
80
- elsif OTHERS.key?(scancode)
81
- OTHERS[scancode][shift ? 1 : 0]
82
- else
83
- ""
84
- end
69
+ return OTHERS[scancode][shift ? 1 : 0] if OTHERS.key?(scancode)
70
+
71
+ Keys.character(scancode, shift)
85
72
  end
86
73
  end
87
74
  end
@@ -17,30 +17,18 @@ module Badline
17
17
  SECTIONS = ["SNAPSHOTS", "DRIVE 8", "DATASETTE", "EXPANSION PORT", "PORTS", "SOUND", "POWER"].freeze
18
18
  PAGES = %i[snapshots drive datasette expansion ports sound power].freeze
19
19
 
20
- PANEL = 0x1d2230
21
- EDGE = 0x3a4256
22
- TEXT = 0xc9d1e0
23
- BRIGHT = 0xffc66d
24
- DIM = 0x6b7489
25
- FILL = 0x343c50
26
-
27
20
  MARGIN = 16
28
21
  WIDTH = 352
29
22
  HEIGHT = 240
30
23
  BODY = 138
31
24
 
32
- ESCAPE = 41
33
- RETURN = 40
34
- SPACE = 44
35
25
  SNAPSHOT_ACTIONS = %i[quicksave_now save_as load_save].freeze
36
- ARROWS = { 79 => :right, 80 => :left, 81 => :down, 82 => :up }.freeze
37
- MOUSEWHEEL = 0x403
38
26
 
39
27
  # Takes the media, the REU and whether disks are writable from Options.
40
28
  def initialize(painter, options, snapshots)
41
29
  media_path = options.media_path.to_s
42
30
  @painter = painter
43
- @buttons = Buttons.new(painter, [TEXT, BRIGHT, PANEL, FILL])
31
+ @buttons = Buttons.new(painter, [MenuTheme::TEXT, MenuTheme::BRIGHT, MenuTheme::PANEL, MenuTheme::FILL])
44
32
  @pages = MenuPages.new(painter, @buttons, media_path, options, snapshots)
45
33
  @snapshots = snapshots
46
34
  @dialogs = MenuDialogs.new(painter, @buttons, @pages.media, options, snapshots)
@@ -76,10 +64,7 @@ module Badline
76
64
  # goes straight in, and anything else asks first.
77
65
  def drop(computer, controls, sound)
78
66
  show(computer, controls, sound)
79
- file = SDL.event_file(SDL.event)
80
- asks = @dialogs.drop(LibC.strstr(file, ""))
81
- LibC.free(file)
82
- asks
67
+ @dialogs.drop(SDL.dropped_file)
83
68
  end
84
69
 
85
70
  # Handles a key pressed while the menu is open: the arrows move
@@ -91,10 +76,10 @@ module Badline
91
76
  def key(scancode)
92
77
  return :resume if scancode == Keys::F9
93
78
  return @dialogs.key(scancode) if @dialogs.open?
94
- return :resume if scancode == ESCAPE
95
- return choose(@buttons.focus) if [RETURN, SPACE].include?(scancode)
79
+ return :resume if scancode == Keys::ESCAPE
80
+ return choose(@buttons.focus) if [Keys::RETURN, Keys::SPACE].include?(scancode)
96
81
 
97
- direction = ARROWS[scancode]
82
+ direction = Keys::DIRECTIONS[scancode]
98
83
  move(direction) unless direction.nil?
99
84
  nil
100
85
  end
@@ -134,18 +119,18 @@ module Badline
134
119
  def draw
135
120
  painter = @painter
136
121
  @buttons.forget
137
- painter.box(@left, @top, WIDTH, HEIGHT, PANEL)
138
- painter.box(@left, @top + 24, WIDTH, 1, EDGE)
139
- painter.text(@left + 8, @top + 9, "PAUSED", BRIGHT)
122
+ painter.box(@left, @top, WIDTH, HEIGHT, MenuTheme::PANEL)
123
+ painter.box(@left, @top + 24, WIDTH, 1, MenuTheme::EDGE)
124
+ painter.text(@left + 8, @top + 9, "PAUSED", MenuTheme::BRIGHT)
140
125
  resume = "RESUME"
141
126
  @buttons.plain(@left + WIDTH - 8 - Painter.width(resume) - (Buttons::PAD * 2), @top + 7, resume, :resume)
142
127
  return @dialogs.draw(@left + 8, @top + 34, WIDTH - 16) if @dialogs.open?
143
128
 
144
- painter.box(@left + BODY - 8, @top + 32, 1, HEIGHT - 56, EDGE)
129
+ painter.box(@left + BODY - 8, @top + 32, 1, HEIGHT - 56, MenuTheme::EDGE)
145
130
  draw_sections
146
131
  @buttons.plain(@left + 6, @top + HEIGHT - 28, "QUICK OPEN...", :quick_open)
147
- painter.text(@left + 8, @top + HEIGHT - 12, "F9/ESC: RESUME", DIM)
148
- painter.text(@left + BODY, @top + HEIGHT - 12, "F6: 40/80 KEY F8: SCREEN", DIM) if @pages.c128?
132
+ painter.text(@left + 8, @top + HEIGHT - 12, "F9/ESC: RESUME", MenuTheme::DIM)
133
+ painter.text(@left + BODY, @top + HEIGHT - 12, "F6: 40/80 KEY F8: SCREEN", MenuTheme::DIM) if @pages.c128?
149
134
  @pages.draw(PAGES[@section], @left + BODY, @top + 34)
150
135
  end
151
136
 
@@ -241,13 +226,13 @@ module Badline
241
226
  when SDL::MOUSEMOTION then @buttons.point(SDL.event_x(event), SDL.event_y(event)) && nil
242
227
  when SDL::MOUSEBUTTONDOWN
243
228
  SDL.event_button(event) == 1 ? click(SDL.event_x(event), SDL.event_y(event)) : nil
244
- when MOUSEWHEEL then @dialogs.scroll(-3 * SDL.event_x(event)) && nil
229
+ when SDL::MOUSEWHEEL then @dialogs.scroll(-3 * SDL.event_x(event)) && nil
245
230
  end
246
231
  end
247
232
 
248
233
  # A key held down repeats only the arrows.
249
234
  def key_down(scancode, repeat)
250
- repeat.zero? || ARROWS.key?(scancode) ? key(scancode) : nil
235
+ repeat.zero? || Keys::DIRECTIONS.key?(scancode) ? key(scancode) : nil
251
236
  end
252
237
 
253
238
  def draw_sections
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Frontend
5
+ # The pause menu's colours, which its pages and dialogs share. They lie
6
+ # outside the C64's palette, so the menu reads as the emulator's and
7
+ # not the machine's.
8
+ module MenuTheme
9
+ PANEL = 0x1d2230
10
+ EDGE = 0x3a4256
11
+ TEXT = 0xc9d1e0
12
+ BRIGHT = 0xffc66d
13
+ DIM = 0x6b7489
14
+ FILL = 0x343c50
15
+ WARNING = 0xff7a6b
16
+ end
17
+ end
18
+ end
@@ -6,10 +6,10 @@ module Badline
6
6
  # resonance and volume, and its response across the audible range on
7
7
  # the chip playing, whose cutoff curve the two models don't share.
8
8
  class FilterView
9
- TEXT = SIDView::TEXT
10
- BRIGHT = SIDView::BRIGHT
11
- DIM = SIDView::DIM
12
- BOX = SIDView::BOX
9
+ TEXT = PlayerTheme::TEXT
10
+ BRIGHT = PlayerTheme::BRIGHT
11
+ DIM = PlayerTheme::DIM
12
+ BOX = PlayerTheme::BOX
13
13
 
14
14
  MODES = ["LP", "BP", "HP", "3 OFF"].freeze
15
15
 
@@ -52,7 +52,7 @@ module Badline
52
52
  def draw
53
53
  index = 0
54
54
  while index < ROWS && @offset + index < @lines.size
55
- @painter.text(LEFT, @top + (index * LINE), @lines[@offset + index], SIDView::TEXT)
55
+ @painter.text(LEFT, @top + (index * LINE), @lines[@offset + index], PlayerTheme::TEXT)
56
56
  index += 1
57
57
  end
58
58
  draw_bar if @lines.size > ROWS
@@ -110,10 +110,10 @@ module Badline
110
110
 
111
111
  def draw_bar
112
112
  @buttons.area([BAR_LEFT, @top, BAR, HEIGHT], :scroll)
113
- @painter.box(BAR_LEFT, @top, BAR, HEIGHT, SIDView::BOX)
113
+ @painter.box(BAR_LEFT, @top, BAR, HEIGHT, PlayerTheme::BOX)
114
114
  thumb = [HEIGHT * ROWS / @lines.size, 8].max
115
115
  place = (HEIGHT - thumb) * @offset / [last_offset, 1].max
116
- @painter.box(BAR_LEFT, @top + place, BAR, thumb, SIDView::TEXT)
116
+ @painter.box(BAR_LEFT, @top + place, BAR, thumb, PlayerTheme::TEXT)
117
117
  end
118
118
  end
119
119
  end