diff --git a/Activity/Observer.hpp b/Activity/Observer.hpp index 2357fb164..307b404be 100644 --- a/Activity/Observer.hpp +++ b/Activity/Observer.hpp @@ -23,8 +23,15 @@ namespace Activity { */ class Observer { public: + /// Provides hints as to the sort of information presented on an LED. + enum LEDPresentation: uint8_t { + /// This LED informs the user of some sort of persistent state, e.g. scroll lock. + /// If this flag is absent then the LED describes an ephemeral state, such as media access. + Persistent = (1 << 0), + }; + /// Announces to the receiver that there is an LED of name @c name. - virtual void register_led([[maybe_unused]] const std::string &name) {} + virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {} /// Announces to the receiver that there is a drive of name @c name. /// diff --git a/Analyser/Static/Enterprise/StaticAnalyser.cpp b/Analyser/Static/Enterprise/StaticAnalyser.cpp index 5e1dd9acb..ec0904ab0 100644 --- a/Analyser/Static/Enterprise/StaticAnalyser.cpp +++ b/Analyser/Static/Enterprise/StaticAnalyser.cpp @@ -48,9 +48,10 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi auto volume = Storage::Disk::FAT::GetVolume(media.disks.front()); if(volume) { // If there's an EXDOS.INI then this disk should be able to boot itself. - // If not but if there's only one .COM or .BAS, automatically load that. - // Failing that, issue a :DIR and give the user a clue as to how to load. - const Storage::Disk::FAT::File *selected_file = nullptr; + // If not but if there's only one visible .COM or .BAS, automatically load + // that. Otherwise, issue a :DIR. + using File = Storage::Disk::FAT::File; + const File *selected_file = nullptr; bool has_exdos_ini = false; bool did_pick_file = false; for(const auto &file: (*volume).root_directory) { @@ -59,7 +60,9 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Medi break; } - if(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas")) { + if(!(file.attributes & File::Attribute::Hidden) && + (insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas")) + ) { did_pick_file = !selected_file; selected_file = &file; } diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 3b1b43bae..21bff7849 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -1119,7 +1119,7 @@ template class ConcreteMachine: } HalfCycles get_typer_frequency() const final { - return Cycles(80'000); // Perform one key transition per frame. + return Cycles(160'000); // Perform one key transition per frame and a half. } // See header; sets a key as either pressed or released. diff --git a/Machines/AmstradCPC/Keyboard.hpp b/Machines/AmstradCPC/Keyboard.hpp index 120dbc394..c5913637e 100644 --- a/Machines/AmstradCPC/Keyboard.hpp +++ b/Machines/AmstradCPC/Keyboard.hpp @@ -40,7 +40,7 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe struct CharacterMapper: public ::Utility::CharacterMapper { const uint16_t *sequence_for_character(char character) const override; - bool needs_pause_after_reset_all_keys() const override { return false; } + bool needs_pause_after_reset_all_keys() const override { return true; } bool needs_pause_after_key(uint16_t key) const override; }; diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 7590d8dc6..030fa5b1e 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -607,7 +607,7 @@ template class ConcreteMachine: void set_activity_observer(Activity::Observer *observer) final { activity_observer_ = observer; if(activity_observer_) { - activity_observer_->register_led(caps_led); + activity_observer_->register_led(caps_led, Activity::Observer::LEDPresentation::Persistent); activity_observer_->set_led_status(caps_led, caps_led_state_); } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 4411f1a27..8407c3f5b 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -632,11 +632,13 @@ class MachineDocument: private class LED { let levelIndicator: NSLevelIndicator - init(levelIndicator: NSLevelIndicator) { + init(levelIndicator: NSLevelIndicator, isPersistent: Bool) { self.levelIndicator = levelIndicator + self.isPersistent = isPersistent } var isLit = false var isBlinking = false + var isPersistent = false } private var leds: [String: LED] = [:] private var activityFader: ViewFader! = nil @@ -674,8 +676,8 @@ class MachineDocument: // Apply labels and create leds entries. for c in 0 ..< leds.count { - textFields[c].stringValue = leds[c] - self.leds[leds[c]] = LED(levelIndicator: activityIndicators[c]) + textFields[c].stringValue = leds[c].name + self.leds[leds[c].name] = LED(levelIndicator: activityIndicators[c], isPersistent: leds[c].isPersisent) } // Create a fader. @@ -719,10 +721,6 @@ class MachineDocument: led.levelIndicator.floatValue = led.isLit ? 1.0 : 0.0 led.isBlinking = false } - - // Treat a new blink as potentially re-showing the activity - // indicators, given windowed-mode behaviour. - self.updateActivityViewVisibility() } } } @@ -741,32 +739,35 @@ class MachineDocument: led.isLit = isLit // Possibly show or hide the activity subview. - self.updateActivityViewVisibility() + self.updateActivityViewVisibility(false, changed: ledName) } } } - private func updateActivityViewVisibility(_ isAppLaunch : Bool = false) { + private func updateActivityViewVisibility(_ isAppLaunch : Bool = false, changed: String? = nil) { if let window = self.windowControllers.first?.window, let activityFader = self.activityFader { - // If in a window, show the activity view transiently to - // acknowledge changes of state. In full screen show it - // permanently as long as at least one LED is lit. - if window.styleMask.contains(.fullScreen) { - let litLEDs = self.leds.filter { $0.value.isLit } - if litLEDs.isEmpty{ - activityFader.animateOut(delay: 0.2) - } else { - activityFader.animateIn() - } - } else if !isAppLaunch { - activityFader.showTransiently(for: 1.0) - } + // Rules applied below: + // + // Fullscreen: + // (i) always show activity view if any persistent LEDs are present; + // (ii) otherwise, show activity view only while at least one LED is lit. + // + // Windowed: + // (i) show while any non-persistent LED is lit; + // (ii) show transiently to indicate a change of state in any persistent LED. + // + let hasLitLEDs = !self.leds.filter { + $0.value.isLit && (!$0.value.isPersistent || window.styleMask.contains(.fullScreen)) || + ($0.value.isPersistent && window.styleMask.contains(.fullScreen)) + }.isEmpty + let shouldShowTransient = !window.styleMask.contains(.fullScreen) && changed != nil && self.leds[changed!]!.isPersistent - let litLEDs = self.leds.filter { $0.value.isLit } - if litLEDs.isEmpty || !window.styleMask.contains(.fullScreen) { - activityFader.animateOut(delay: window.styleMask.contains(.fullScreen) ? 0.2 : 0.0) - } else { + if hasLitLEDs { activityFader.animateIn() + } else if shouldShowTransient { + activityFader.showTransiently(for: 1.0) + } else { + activityFader.animateOut(delay: 0.2) } } } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index 258073934..745bc9eb3 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -33,6 +33,11 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { CSMachineKeyboardInputModeJoystick, }; +@interface CSMachineLED: NSObject +@property(nonatomic, nonnull, readonly) NSString *name; +@property(nonatomic, readonly) BOOL isPersisent; +@end + // Deliberately low; to ensure CSMachine has been declared as an @class already. #import "CSAtari2600.h" #import "CSZX8081.h" @@ -99,7 +104,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { @property (nonatomic, nullable) CSJoystickManager *joystickManager; // LED list. -@property (nonatomic, readonly, nonnull) NSArray *leds; +@property (nonatomic, readonly, nonnull) NSArray *leds; // Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type. @property (nonatomic, readonly, nullable) CSAtari2600 *atari2600; diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index d24bc889e..843009e58 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -37,7 +37,7 @@ @interface CSMachine() - (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length; - (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker; -- (void)addLED:(NSString *)led; +- (void)addLED:(NSString *)led isPersistent:(BOOL)isPersistent; @end struct LockProtectedDelegate { @@ -61,8 +61,8 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockP }; struct ActivityObserver: public Activity::Observer { - void register_led(const std::string &name) final { - [machine addLED:[NSString stringWithUTF8String:name.c_str()]]; + void register_led(const std::string &name, uint8_t flags) final { + [machine addLED:[NSString stringWithUTF8String:name.c_str()] isPersistent:flags & Activity::Observer::LEDPresentation::Persistent]; } void set_led_status(const std::string &name, bool lit) final { @@ -76,6 +76,19 @@ struct ActivityObserver: public Activity::Observer { __unsafe_unretained CSMachine *machine; }; +@implementation CSMachineLED + +- (instancetype)initWithName:(NSString *)name isPersistent:(BOOL)isPersistent { + self = [super init]; + if(self) { + _name = name; + _isPersisent = isPersistent; + } + return self; +} + +@end + @implementation CSMachine { SpeakerDelegate _speakerDelegate; ActivityObserver _activityObserver; @@ -86,7 +99,7 @@ struct ActivityObserver: public Activity::Observer { MachineTypes::JoystickMachine *_joystickMachine; CSJoystickManager *_joystickManager; - NSMutableArray *_leds; + NSMutableArray *_leds; CSHighPrecisionTimer *_timer; std::atomic_flag _isUpdating; @@ -623,11 +636,11 @@ struct ActivityObserver: public Activity::Observer { #pragma mark - Activity observation -- (void)addLED:(NSString *)led { - [_leds addObject:led]; +- (void)addLED:(NSString *)led isPersistent:(BOOL)isPersistent { + [_leds addObject:[[CSMachineLED alloc] initWithName:led isPersistent:isPersistent]]; } -- (NSArray *)leds { +- (NSArray *)leds { return _leds; } diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index ac6f6115f..8ea77609c 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -1323,7 +1323,7 @@ void MainWindow::addActivityObserver() { activitySource->set_activity_observer(this); } -void MainWindow::register_led(const std::string &name) { +void MainWindow::register_led(const std::string &name, uint8_t) { std::lock_guard guard(ledStatusesLock); ledStatuses[name] = false; QMetaObject::invokeMethod(this, "updateStatusBarText"); diff --git a/OSBindings/Qt/mainwindow.h b/OSBindings/Qt/mainwindow.h index 0fd3f6978..36e35ef59 100644 --- a/OSBindings/Qt/mainwindow.h +++ b/OSBindings/Qt/mainwindow.h @@ -152,7 +152,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat KeyboardMapper keyMapper; - void register_led(const std::string &) override; + void register_led(const std::string &, uint8_t) override; void set_led_status(const std::string &, bool) override; std::recursive_mutex ledStatusesLock; diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 24a98b930..418246faa 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -271,7 +271,7 @@ class ActivityObserver: public Activity::Observer { private: std::vector leds_; - void register_led(const std::string &name) final { + void register_led(const std::string &name, uint8_t) final { std::lock_guard lock_guard(mutex); leds_.push_back(name); }