mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Merge pull request #975 from TomHarte/LEDStyles
Classify some LEDs as 'persistent'
This commit is contained in:
commit
a0799e14cc
@ -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.
|
||||
///
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1119,7 +1119,7 @@ template <bool has_fdc> 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.
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -607,7 +607,7 @@ template <bool has_scsi_bus> 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_);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<NSString *> *leds;
|
||||
@property (nonatomic, readonly, nonnull) NSArray<CSMachineLED *> *leds;
|
||||
|
||||
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
|
||||
@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600;
|
||||
|
@ -37,7 +37,7 @@
|
||||
@interface CSMachine() <CSScanTargetViewDisplayLinkDelegate>
|
||||
- (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<NSString *> *_leds;
|
||||
NSMutableArray<CSMachineLED *> *_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<NSString *> *)leds {
|
||||
- (NSArray<CSMachineLED *> *)leds {
|
||||
return _leds;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -271,7 +271,7 @@ class ActivityObserver: public Activity::Observer {
|
||||
|
||||
private:
|
||||
std::vector<std::string> 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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user