From 3e9ef6b8cb36d7b0000f54375139aca2d7148c21 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 15 Jul 2018 20:19:06 -0400 Subject: [PATCH] Adds indicator lights for the SDL port. To complete #426 --- Activity/Observer.hpp | 11 +-- .../Mac/Clock Signal/Machine/CSMachine.mm | 6 -- OSBindings/SDL/main.cpp | 99 ++++++++++++++++++- 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/Activity/Observer.hpp b/Activity/Observer.hpp index 44875e4f7..349511123 100644 --- a/Activity/Observer.hpp +++ b/Activity/Observer.hpp @@ -24,13 +24,13 @@ namespace Activity { class Observer { public: /// Announces to the receiver that there is an LED of name @c name. - virtual void register_led(const std::string &name) = 0; + virtual void register_led(const std::string &name) {} /// Announces to the receiver that there is a drive of name @c name. - virtual void register_drive(const std::string &name) = 0; + virtual void register_drive(const std::string &name) {} /// Informs the receiver of the new state of the LED with name @c name. - virtual void set_led_status(const std::string &name, bool lit) = 0; + virtual void set_led_status(const std::string &name, bool lit) {} enum class DriveEvent { StepNormal, @@ -39,11 +39,10 @@ class Observer { }; /// Informs the receiver that the named event just occurred for the drive with name @c name. - virtual void announce_drive_event(const std::string &name, DriveEvent event) = 0; + virtual void announce_drive_event(const std::string &name, DriveEvent event) {} /// Informs the receiver of the motor-on status of the drive with name @c name. - virtual void set_drive_motor_status(const std::string &name, bool is_on) = 0; - + virtual void set_drive_motor_status(const std::string &name, bool is_on) {} }; } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index 52271e74f..8c34ea90d 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -57,9 +57,6 @@ struct ActivityObserver: public Activity::Observer { [machine addLED:[NSString stringWithUTF8String:name.c_str()]]; } - void register_drive(const std::string &name) override { - } - void set_led_status(const std::string &name, bool lit) override { [machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit]; } @@ -68,9 +65,6 @@ struct ActivityObserver: public Activity::Observer { [machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]]; } - void set_drive_motor_status(const std::string &name, bool is_on) override { - } - __unsafe_unretained CSMachine *machine; }; diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 01c0045cf..3bfc80787 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -21,6 +21,9 @@ #include "../../Concurrency/BestEffortUpdater.hpp" +#include "../../Activity/Observer.hpp" +#include "../../Outputs/CRT/Internals/Rectangle.hpp" + namespace { struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate { @@ -31,8 +34,8 @@ struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegat Machine::DynamicMachine *machine; }; -// This is set to a relatively large number for now. struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { + // This is set to a relatively large number for now. static const int buffer_size = 1024; void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector &buffer) override { @@ -69,6 +72,88 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate { std::vector audio_buffer_; }; +class ActivityObserver: public Activity::Observer { + public: + ActivityObserver(Activity::Source *source, float aspect_ratio) { + // Get the suorce to supply all LEDs and drives. + source->set_activity_observer(this); + + // The objective is to display drives on one side of the screen, other LEDs on the other. Drives + // may or may not have LEDs and this code intends to display only those which do; so a quick + // comparative processing of the two lists is called for. + + // Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed. + drives_.resize(std::remove_if(drives_.begin(), drives_.end(), [this](const std::string &string) { + return std::find(leds_.begin(), leds_.end(), string) == leds_.end(); + }) - drives_.begin()); + + // Remove from the list of LEDs any which are drives. Those will be represented separately. + leds_.resize(std::remove_if(leds_.begin(), leds_.end(), [this](const std::string &string) { + return std::find(drives_.begin(), drives_.end(), string) != drives_.end(); + }) - leds_.begin()); + + set_aspect_ratio(aspect_ratio); + } + + void set_aspect_ratio(float aspect_ratio) { + lights_.clear(); + + // Generate a bunch of LEDs for connected drives. + const float height = 0.05f; + const float width = height / aspect_ratio; + const float right_x = 1.0f - 2.0f * width; + float y = 1.0f - 2.0f * height; + for(const auto &drive: drives_) { + lights_.emplace(std::make_pair(drive, std::make_unique(right_x, y, width, height))); + y -= height * 2.0f; + } + + /* + This would generate LEDs for things other than drives; I'm declining for now + due to the inexpressiveness of just painting a rectangle. + + const float left_x = -1.0f + 2.0f * width; + y = 1.0f - 2.0f * height; + for(const auto &led: leds_) { + lights_.emplace(std::make_pair(led, std::make_unique(left_x, y, width, height))); + y -= height * 2.0f; + } + */ + } + + void draw() { + for(const auto &lit_led: lit_leds_) { + if(blinking_leds_.find(lit_led) == blinking_leds_.end() && lights_.find(lit_led) != lights_.end()) + lights_[lit_led]->draw(0.0, 0.8, 0.0); + } + blinking_leds_.clear(); + } + + private: + std::vector leds_; + void register_led(const std::string &name) override { + leds_.push_back(name); + } + + std::vector drives_; + void register_drive(const std::string &name) override { + drives_.push_back(name); + } + + void set_led_status(const std::string &name, bool lit) override { + if(lit) lit_leds_.insert(name); + else lit_leds_.erase(name); + } + + void announce_drive_event(const std::string &name, DriveEvent event) override { + blinking_leds_.insert(name); + } + + std::map> lights_; + std::set lit_leds_; + std::set blinking_leds_; +}; + bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) { #define BIND(x, y) case SDL_SCANCODE_##x: key = Inputs::Keyboard::Key::y; break; switch(scancode) { @@ -462,6 +547,16 @@ int main(int argc, char *argv[]) { } } + /* + If the machine offers anything for activity observation, + create and register an activity observer. + */ + std::unique_ptr activity_observer; + Activity::Source *const activity_source = machine->activity_source(); + if(activity_source) { + activity_observer.reset(new ActivityObserver(activity_source, 4.0f / 3.0f)); + } + // Run the main event loop until the OS tells us to quit. bool should_quit = false; Uint32 fullscreen_mode = 0; @@ -479,6 +574,7 @@ int main(int argc, char *argv[]) { glGetIntegerv(GL_FRAMEBUFFER_BINDING, &target_framebuffer); machine->crt_machine()->get_crt()->set_target_framebuffer(target_framebuffer); SDL_GetWindowSize(window, &window_width, &window_height); + if(activity_observer) activity_observer->set_aspect_ratio(static_cast(window_width) / static_cast(window_height)); } break; default: break; @@ -610,6 +706,7 @@ int main(int argc, char *argv[]) { // Display a new frame and wait for vsync. updater.update(); machine->crt_machine()->get_crt()->draw_frame(static_cast(window_width), static_cast(window_height), false); + if(activity_observer) activity_observer->draw(); SDL_GL_SwapWindow(window); }