| 124 | |_| (String::new(), false, 0, Vec::new(), 1.0), |
| 125 | |handle| { |
| 126 | ( |
| 127 | handle.state.cartridge_title.clone(), |
| 128 | handle.state.is_running, |
| 129 | handle.state.cycle, |
| 130 | handle.breakpoints.clone(), |
| 131 | handle.speed, |
| 132 | ) |
| 133 | }, |
| 134 | ); |
| 135 | |
| 136 | // Sync local speed with handle speed. A held fast-forward can drive it |
| 137 | // above the presets, in which case no preset is highlighted. |
| 138 | if current_speed > 0.0 { |
| 139 | self.speed = current_speed; |
| 140 | } |
| 141 | |
| 142 | ui.heading(cartridge_name); |
| 143 | |
| 144 | ui.horizontal(|ui| { |
| 145 | if ui |
| 146 | .add_enabled(!is_running, egui::Button::new("▶ Run")) |
| 147 | .clicked() |
| 148 | && let Ok(mut handle) = self.emu_handle.lock() |
| 149 | { |
| 150 | handle.send(EmuCommand::Run); |
| 151 | } |
| 152 | |
| 153 | if ui |
| 154 | .add_enabled(is_running, egui::Button::new("⏸ Pause")) |
| 155 | .clicked() |
| 156 | && let Ok(mut handle) = self.emu_handle.lock() |
| 157 | { |
| 158 | handle.send(EmuCommand::Pause); |
| 159 | } |
| 160 | }); |
| 161 | |
| 162 | // Speed control with preset buttons |
| 163 | ui.horizontal(|ui| { |
| 164 | ui.label("Speed:"); |
| 165 | |
| 166 | // Speed preset buttons. 8x is the maximum. |
| 167 | let speeds = [(1.0, "1x"), (2.0, "2x"), (4.0, "4x"), (8.0, "8x")]; |
| 168 | for (speed, label) in speeds { |
| 169 | let is_selected = (self.speed - speed).abs() < SPEED_EPSILON; |
| 170 | if ui.selectable_label(is_selected, label).clicked() { |
| 171 | self.set_speed(speed); |
| 172 | } |
| 173 | } |
| 174 | }); |
| 175 | |
| 176 | // Hold-to-fast-forward key binding. |
| 177 | ui.horizontal(|ui| { |
| 178 | let current = self.fast_forward_key.lock().map_or(Key::Space, |key| *key); |
| 179 | ui.label("Hold to fast-forward:"); |
| 180 | |
| 181 | let button_label = if self.rebinding_fast_forward { |
| 182 | "press a key...".to_string() |
| 183 | } else { |