diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index f51d778c6..ca8271523 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -57,15 +57,16 @@ VariableBoxController * AppsContainer::variableBoxController() { return &m_variableBoxController; } -bool AppsContainer::handleEvent(Ion::Events::Event event) { +void AppsContainer::dispatchEvent(Ion::Events::Event event) { if (event == Ion::Events::Home) { switchTo(appAtIndex(0)); - return true; + return; } if (event == Ion::Events::OnOff) { Ion::Power::suspend(); + return; } - return false; + Container::dispatchEvent(event); } void AppsContainer::switchTo(App * app) { diff --git a/apps/apps_container.h b/apps/apps_container.h index b97cdf8fd..70c7d55c2 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -27,7 +27,7 @@ public: Poincare::Context * globalContext(); MathToolbox * mathToolbox(); VariableBoxController * variableBoxController(); - bool handleEvent(Ion::Events::Event event) override; + void dispatchEvent(Ion::Events::Event event) override; void switchTo(App * app) override; void refreshPreferences(); private: diff --git a/escher/Makefile b/escher/Makefile index 539a7b201..218944271 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -36,6 +36,7 @@ objs += $(addprefix escher/src/,\ pointer_table_cell_with_switch.o\ pointer_text_view.o\ responder.o\ + run_loop.o\ scroll_view.o\ scroll_view_indicator.o\ selectable_table_view.o\ @@ -55,6 +56,7 @@ objs += $(addprefix escher/src/,\ text_view.o\ tiled_view.o\ toolbox.o\ + timer.o\ view.o\ view_controller.o\ warning_controller.o\ diff --git a/escher/include/escher/container.h b/escher/include/escher/container.h index b2aba06fb..a60a642b7 100644 --- a/escher/include/escher/container.h +++ b/escher/include/escher/container.h @@ -10,16 +10,17 @@ * When writing an Escher program, you typically subclass Container, and your * subclass owns one or more App. You then call "run()" on your container. */ +#include #include #include #include -class Container { +class Container : private RunLoop { public: Container(); void run(); App * activeApp(); - virtual bool handleEvent(Ion::Events::Event event); + virtual void dispatchEvent(Ion::Events::Event event) override; virtual void switchTo(App * app); protected: virtual Window * window() = 0; diff --git a/escher/include/escher/run_loop.h b/escher/include/escher/run_loop.h new file mode 100644 index 000000000..74215d8ad --- /dev/null +++ b/escher/include/escher/run_loop.h @@ -0,0 +1,20 @@ +#ifndef ESCHER_RUN_LOOP_H +#define ESCHER_RUN_LOOP_H + +#include +#include + +class RunLoop { +public: + RunLoop(); + void run(); +protected: + virtual void dispatchEvent(Ion::Events::Event e) = 0; + virtual int numberOfTimers(); + virtual Timer * timerAtIndex(int i); +private: + void step(); + int m_time; +}; + +#endif diff --git a/escher/include/escher/timer.h b/escher/include/escher/timer.h new file mode 100644 index 000000000..6dbb1dc79 --- /dev/null +++ b/escher/include/escher/timer.h @@ -0,0 +1,29 @@ +#ifndef ESCHER_TIMER_H +#define ESCHER_TIMER_H + +#include +#include + +/* Timers we'll need + * - Blink cursor timer + * - Dim Screen timer + * - Power down timer + * - Watchdog timer ? + * - Battery level timer + * - LED blink timer + */ + +class Timer { +public: + static constexpr int TickDuration = 100; // In Miliseconds + Timer(Invocation invocation, uint32_t period); // Period is in ticks + void tick(); + void reset(); +private: + void fire(); + Invocation m_invocation; + uint32_t m_period; + uint32_t m_numberOfTicksBeforeFire; +}; + +#endif diff --git a/escher/src/container.cpp b/escher/src/container.cpp index 619563dd4..dd54efafb 100644 --- a/escher/src/container.cpp +++ b/escher/src/container.cpp @@ -1,10 +1,8 @@ #include #include -#ifdef __EMSCRIPTEN__ -#include -#endif Container::Container() : + RunLoop(), m_activeApp(nullptr) { } @@ -26,36 +24,26 @@ App * Container::activeApp() { return m_activeApp; } -bool Container::handleEvent(Ion::Events::Event event) { - return false; +void Container::dispatchEvent(Ion::Events::Event event) { +#if ESCHER_LOG_EVENTS_BINARY + char message[2] = { (char)event.id(), 0}; + ion_log_string(message); +#endif +#if ESCHER_LOG_EVENTS_NAME + const char * name = event.name(); + if (name == nullptr) { + name = "UNDEFINED"; + } + ion_log_string("Ion::Events::"); + ion_log_string(name); + ion_log_string("\n"); +#endif + m_activeApp->processEvent(event); + window()->redraw(); } void Container::run() { window()->setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); window()->redraw(); - -#ifdef __EMSCRIPTEN__ - emscripten_set_main_loop_arg([](void * ctx){ ((Container *)ctx)->step(); }, this, 0, 1); -#else - while (true) { - step(); - } -#endif -} - -void Container::step() { - Ion::Events::Event event = Ion::Events::getEvent(); // This is a blocking call -#if ESCHER_LOG_EVENTS - char message[2] = { (char)event.id(), 0}; - Ion::Log::print(message); -#endif - - if (event == Ion::Events::None) { - return; - } - if (handleEvent(event)) { - return; - } - m_activeApp->processEvent(event); - window()->redraw(); + RunLoop::run(); } diff --git a/escher/src/run_loop.cpp b/escher/src/run_loop.cpp new file mode 100644 index 000000000..6a1f4d7e9 --- /dev/null +++ b/escher/src/run_loop.cpp @@ -0,0 +1,72 @@ +#include +#include +#ifdef __EMSCRIPTEN__ +#include +#endif + +RunLoop::RunLoop() : + m_time(0) { +} + +int RunLoop::numberOfTimers() { + return 0; +} + +Timer * RunLoop::timerAtIndex(int i) { + assert(false); + return nullptr; +} + +void RunLoop::run() { +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg([](void * ctx){ ((Container *)ctx)->step(); }, this, 0, 1); +#else + while (true) { + step(); + } +#endif +} + +void RunLoop::step() { + // Fetch the event, if any + int eventDuration = Timer::TickDuration; + int timeout = eventDuration; + Ion::Events::Event event = Ion::Events::getEvent(&timeout); + eventDuration -= timeout; + + assert(eventDuration >= 0); + assert(eventDuration <= Timer::TickDuration); + + /* At this point, eventDuration contains the time it took to retrieve the + * event. It can be zero, and is at most equal to the timeout value, Timer:: + * TickDuration. The event returned can be None if nothing worth taking care + * of happened. In other words, getEvent is a blocking call with a timeout. */ + + m_time += eventDuration; + + if (m_time >= Timer::TickDuration) { + m_time -= Timer::TickDuration; + for (int i=0; itick(); + } + } + +#if ESCHER_LOG_EVENTS_BINARY + char message[2] = { (char)event.id(), 0}; + ion_log_string(message); +#endif +#if ESCHER_LOG_EVENTS_NAME + const char * name = event.name(); + if (name == nullptr) { + name = "UNDEFINED"; + } + ion_log_string("Ion::Events::"); + ion_log_string(name); + ion_log_string("\n"); +#endif + + if (event != Ion::Events::None) { + dispatchEvent(event); + } +} diff --git a/escher/src/timer.cpp b/escher/src/timer.cpp new file mode 100644 index 000000000..7c79f94bf --- /dev/null +++ b/escher/src/timer.cpp @@ -0,0 +1,24 @@ +#include + +Timer::Timer(Invocation invocation, uint32_t period) : + m_invocation(invocation), + m_period(period), + m_numberOfTicksBeforeFire(period) +{ +} + +void Timer::tick() { + m_numberOfTicksBeforeFire--; + if (m_numberOfTicksBeforeFire == 0) { + fire(); + reset(); + } +} + +void Timer::reset() { + m_numberOfTicksBeforeFire = m_period; +} + +void Timer::fire() { + m_invocation.perform(this); +} diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index ab1d6057c..5346de9d0 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -34,7 +34,8 @@ private: uint8_t m_id; }; -Event getEvent(); +// Timeout is decremented +Event getEvent(int * timeout); bool isShiftActive(); bool isAlphaActive(); diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index 9380a1d7a..00e8559ed 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -38,8 +38,20 @@ void updateModifiersFromEvent(Event e) { } } +static bool sleepWithTimeout(int duration, int * timeout) { + if (*timeout >= duration) { + msleep(duration); + *timeout -= duration; + return false; + } else { + msleep(*timeout); + *timeout = 0; + return true; + } +} + // Debouncing, and change to get_key event. -Event getEvent() { +Event getEvent(int * timeout) { // Let's start by saving which keys we've seen up bool keySeenUp[Keyboard::NumberOfKeys]; for (int k=0; k