From 0b683b6994ee2b268a79b0d1efd946e0064bc2b4 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 23 Jul 2020 10:28:28 +0200 Subject: [PATCH 01/67] [apps/shared] Handle identical layout in MinimalSizeForOptimalDisplay Change-Id: I99a523e9e88ea9c3064e4367d96b01edb0020df9 --- apps/calculation/history_view_cell.cpp | 1 + apps/shared/scrollable_multiple_expressions_view.cpp | 2 +- apps/shared/scrollable_multiple_expressions_view.h | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index e560ccbf3..e30995750 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -299,6 +299,7 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded, b m_calculationDisplayOutput = calculation->displayOutput(context); // We must set which subviews are displayed before setLayouts to mark the right rectangle as dirty + m_scrollableOutputView.setDisplayableCenter(m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate || m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle); m_scrollableOutputView.setDisplayCenter(m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate || m_calculationExpanded); m_scrollableOutputView.setLayouts(Poincare::Layout(), exactOutputLayout, approximateOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp index 50f97419b..ed9bb5db7 100644 --- a/apps/shared/scrollable_multiple_expressions_view.cpp +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -172,7 +172,7 @@ KDSize AbstractScrollableMultipleExpressionsView::ContentCell::privateMinimalSiz } KDSize centerSize = KDSizeZero; - if (displayCenter() || (forceFullDisplay && !m_centeredExpressionView.layout().isUninitialized())) { + if (displayCenter() || (forceFullDisplay && displayableCenter())) { centerSize = m_centeredExpressionView.minimalSizeForOptimalDisplay(); width += centerSize.width() + 2 * AbstractScrollableMultipleExpressionsView::k_horizontalMargin + m_approximateSign.minimalSizeForOptimalDisplay().width(); } diff --git a/apps/shared/scrollable_multiple_expressions_view.h b/apps/shared/scrollable_multiple_expressions_view.h index 89dc76b16..54f1e67b0 100644 --- a/apps/shared/scrollable_multiple_expressions_view.h +++ b/apps/shared/scrollable_multiple_expressions_view.h @@ -33,6 +33,7 @@ public: } bool displayCenter() const { return constContentCell()->displayCenter(); } void setDisplayCenter(bool display); + void setDisplayableCenter(bool displayable) { contentCell()->setDisplayableCenter(displayable); } void reloadScroll(); bool handleEvent(Ion::Events::Event event) override; Poincare::Layout layout() const { return constContentCell()->layout(); } @@ -63,7 +64,9 @@ protected: } void setSelectedSubviewPosition(SubviewPosition subviewPosition); bool displayCenter() const { return m_displayCenter && !m_centeredExpressionView.layout().isUninitialized(); } + bool displayableCenter() const { return m_displayableCenter && !m_centeredExpressionView.layout().isUninitialized(); } void setDisplayCenter(bool display); + void setDisplayableCenter(bool displayable) {m_displayableCenter = displayable;} void layoutSubviews(bool force = false) override; int numberOfSubviews() const override; virtual Poincare::Layout layout() const override; @@ -79,6 +82,7 @@ protected: ExpressionView m_centeredExpressionView; SubviewPosition m_selectedSubviewPosition; bool m_displayCenter; + bool m_displayableCenter; }; virtual ContentCell * contentCell() = 0; virtual const ContentCell * constContentCell() const = 0; From 7eb694822d6202832083b340d7cd8ccf74c2d8d8 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 3 Aug 2020 17:58:36 +0200 Subject: [PATCH 02/67] [python] Add magenta color Change-Id: I7a5e12f6f3d79da802149a6aeef88387941ccdd8 --- python/port/port.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/port/port.cpp b/python/port/port.cpp index bc2ee4113..1a4eb61df 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -202,7 +202,8 @@ KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ NamedColor("orange", Palette::Orange), NamedColor("purple", Palette::Purple), NamedColor("grey", Palette::GreyDark), - NamedColor("cyan", Palette::Cyan) + NamedColor("cyan", Palette::Cyan), + NamedColor("magenta", Palette::Magenta) }; for (NamedColor p : pairs) { if (strcmp(p.name(), color) == 0) { From cf5cd35bc96523fccc060514224ff38445a5dd2c Mon Sep 17 00:00:00 2001 From: Charlotte THOMAS Date: Mon, 17 Aug 2020 21:52:39 +0200 Subject: [PATCH 03/67] [Change] Constants based on the CODATA 2018 (#398) --- apps/shared.universal.i18n | 58 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index c2226b9d0..7775218d0 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -336,28 +336,28 @@ PHilaireLeRoux = "@0Babass2" SpeedOfLight = "2.99792458·10^8_m_s^-1" YearLight = "9.461·10^15_m" Boltzmann = "1.380649·10^-23_J_K^-1" -StefanBoltzmann = "5.670374·10^-8_W_m^-2_K^-4" -VacuumImpedance = "376.730313461_Ω" +StefanBoltzmann = "5.670374419·10^-8_W_m^-2_K^-4" +VacuumImpedance = "376.730313668_Ω" WaterTriplePoint = "273.16_K" AtmosphericPressure = "101325_Pa" -AtomicMassUnit = "1.660539040·10^-27_kg" -Wien = "2.8977729·10^-3_K_m" -BohrRadius = "5.2917721067·10^-11_m" -BohrMagneton = "9.274009994·10^-24_A_m^2" -NuclearMagneton = "5.050783699·10^-27_A_m^2" -MuonMass = "1.883531594·10^-28_kg" +AtomicMassUnit = "1.66053906660·10^-27_kg" +Wien = "2.897771955·10^-3_K_m" +BohrRadius = "5.29177210903·10^-11_m" +BohrMagneton = "9.2740100783·10^-24_A_m^2" +NuclearMagneton = "5.0507837461·10^-27_A_m^2" +MuonMass = "1.883531627·10^-28_kg" Avogadro = "6.02214076·10^23_mol^-1" -Gas = "8.31446261815324_J_mol^-1_K^-1" -Coulomb = "8.9875517887·10^9_N_m^2_C^-2" +Gas = "8.314462618_J_mol^-1_K^-1" +Coulomb = "8.9875517923·10^9_N_m^2_C^-2" Vacuum_permittivity = "8.8541878128·10^-12_F_m^-1" -Vacuum_permeability = "4π·10^-7_H_m^-1" +Vacuum_permeability = "1.25663706212·10^-6_H_m^-1" Planck = "6.62607015·10^-34_J_s" -ElectronMass = "9.109383·10^-31_kg" -ProtonMass = "1.672649·10^-27_kg" -NeutronMass = "1.67493·10^-27_kg" -ElementalCharge = "1.60217662·10^-19_C" +ElectronMass = "9.1093837015·10^-31_kg" +ProtonMass = "1.67262192369·10^-27_kg" +NeutronMass = "1.67492749804·10^-27_kg" +ElementalCharge = "1.602176634·10^-19_C" GAcceleration = "9.80665_m_s^-2" -GConstant = "6.674·10^-11_m^3_kg^-1_s^-2" +GConstant = "6.67430·10^-11_m^3_kg^-1_s^-2" SpeedOfSound0 = "343.4_m_s^-1" SpeedOfSoundWater = "1480_m_s^-1" SpeedOfSoundSteel = "5600_m_s^-1" @@ -370,7 +370,7 @@ EarthRadius = "6378140_m" MoonRadius = "3474600_m" EarthMoonDistance = "384400000_m" EarthSunDistance = "149597870000_m" -FaradayConstant = "96484_C_mol^-1" +FaradayConstant = "96485.33212_C_mol^-1" EscapeVelocityOfEarth = "11186_m_s^-1" EscapeVelocityOfMoon = "2380_m_s^-1" EscapeVelocityOfSun = "42100_m_s^-1" @@ -409,11 +409,11 @@ Pka14Value = "2.1" Pka15Value = "1.9" Pka16Value = "0" Pka = "pKa" -PlanckReduce = "1.504571800·10^-34_J_s" -PlanckMass = "2.176470·10^-8_kg" -PlanckLength = "1.616229·10^-35_m" -PlanckTime = "5.39116·10^-44_s" -PlanckTemperature = "1.416808·10^32_K" +PlanckReduce = "1.054571817·10^-34_J_s" +PlanckMass = "2.176434·10^-8_kg" +PlanckLength = "1.616255·10^-35_m" +PlanckTime = "5.391247·10^-44_s" +PlanckTemperature = "1.416784·10^32_K" PlanckCharge = "1.875·10^-18_C" PlanckForce = "1.210·10^44_N" PlanckEnergy = "1.956·10^9_J" @@ -425,12 +425,12 @@ PlanckTension = "1.0432·10^27_V" PlanckCurrent = "3.479·10^25_A" PlanckPressure = "4.635·10^113_Pa" PlanckImpedance = "29.986_Ω" -TauonMass = "3.16747·10^-27_kg" +TauonMass = "3.16754·10^-27_kg" WBosonMass = "1.4334·10^-25_kg" ZBosonMass = "1.62556·10^-25_kg" -FineStructure = "7.2973525664·10^-3" -RydbergConstant = "1.0973731568508·10^7_m^-1" -HartreeConstant = "4.359744650·10^-18_J" -MagneticFluxQuantum = "2.067833831·10^-15_Wb" -ConductanceQuantum = "7.7480917310·10^-5_S" -CirculationQuantum = "3.6369475486·10^-4_m^2_s^-1" \ No newline at end of file +FineStructure = "7.2973525693·10^-3" +RydbergConstant = "10973731.568160_m^-1" +HartreeConstant = "4.3597447222071·10^-18_J" +MagneticFluxQuantum = "2.067833848·10^-15_Wb" +ConductanceQuantum = "7.748091729·10^-5_S" +CirculationQuantum = "3.6369475516·10^-4_m^2_s^-1" \ No newline at end of file From d154611023a8c2bb3b8d48a660553346c4728d3b Mon Sep 17 00:00:00 2001 From: Joachim Le Fournis <43498612+RedGl0w@users.noreply.github.com> Date: Sun, 9 Aug 2020 08:44:18 +0200 Subject: [PATCH 04/67] [Ion] Fix broken link --- ion/src/device/shared/usb/calculator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index 6dd9679f5..9b3a2f28f 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -66,7 +66,7 @@ public: 0, // bInterfaceNumber k_dfuInterfaceAlternateSetting, // bAlternateSetting 0, // bNumEndpoints: Other than endpoint 0 - 0xFE, // bInterfaceClass: DFU (http://www.usb.org/developers/defined_class) + 0xFE, // bInterfaceClass: DFU (https://www.usb.org/defined-class-codes) 1, // bInterfaceSubClass: DFU 2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1) 4, // iInterface: Index of the Interface string, see m_descriptor From 38e15da5d0d00c0d72c9ddffe92d0919448170e6 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 13 Aug 2020 18:09:16 +0200 Subject: [PATCH 05/67] [kandinsky/color] Blend identical colors KDColor::blend used to produce different colors when blending two identical colors (ex : use draw_string in Python to print white text on a white background). blend now escapes early when its two color arguments are identical. Change-Id: I01dc5a0d5e4e6a20e09fee0f346dafc313dae97b --- kandinsky/src/color.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/kandinsky/src/color.cpp b/kandinsky/src/color.cpp index b61c45061..57256d820 100644 --- a/kandinsky/src/color.cpp +++ b/kandinsky/src/color.cpp @@ -7,7 +7,11 @@ KDColor KDColor::blend(KDColor first, KDColor second, uint8_t alpha) { * dealt with. So let's make a special case for them. */ if (alpha == 0) { return second; - } else if (alpha == 0xFF) { + } + if (alpha == 0xFF) { + return first; + } + if (first == second) { return first; } @@ -21,8 +25,8 @@ KDColor KDColor::blend(KDColor first, KDColor second, uint8_t alpha) { uint16_t blue = first.blue()*alpha + second.blue()*oneMinusAlpha; return RGB888(red>>8, green>>8, blue>>8); - - // Blend White + black, ask for white - // white.red() = 0x1F << 3 = 0xF8 -// white.red() * 0xFF = 0xF708, we wanted 0xF800 + /* The formula used to blend colors produces some unwanted results : + * blend white + black, alpha = OxFF (should be white) + * white.red() * OxFF = OxF708, while we would like 0xF800 + * Escaping early solves this issue. */ } From 078bba2fb941600aebe6d04f04fac8fe09abc004 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 19 Aug 2020 15:51:44 +0200 Subject: [PATCH 06/67] [kandinsky/color] Fix blend method When computing the barycenter between two colors, the some of the two factors a and (1-a) was equal to 255/256 instead of 1. Change-Id: Ia9a779d43470ef42d9430ad730e842da0f007140 --- kandinsky/src/color.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/kandinsky/src/color.cpp b/kandinsky/src/color.cpp index 57256d820..55bf9e8aa 100644 --- a/kandinsky/src/color.cpp +++ b/kandinsky/src/color.cpp @@ -19,14 +19,9 @@ KDColor KDColor::blend(KDColor first, KDColor second, uint8_t alpha) { // First is RRRRR GGGGGG BBBBB // Second is same - uint8_t oneMinusAlpha = 0xFF-alpha; + uint16_t oneMinusAlpha = 0x100-alpha; uint16_t red = first.red()*alpha + second.red()*oneMinusAlpha; uint16_t green = first.green()*alpha + second.green()*oneMinusAlpha; uint16_t blue = first.blue()*alpha + second.blue()*oneMinusAlpha; return RGB888(red>>8, green>>8, blue>>8); - - /* The formula used to blend colors produces some unwanted results : - * blend white + black, alpha = OxFF (should be white) - * white.red() * OxFF = OxF708, while we would like 0xF800 - * Escaping early solves this issue. */ } From e33543aacb12382e5762fbc96f0e7f7f8012de12 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 19 Aug 2020 16:12:18 +0200 Subject: [PATCH 07/67] [kandinsky/color] Add tests on color blending Change-Id: If1792bd9ec44052238632d28b806f5582b190fe9 --- kandinsky/test/color.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/kandinsky/test/color.cpp b/kandinsky/test/color.cpp index e9bd1fa90..9cbf32dec 100644 --- a/kandinsky/test/color.cpp +++ b/kandinsky/test/color.cpp @@ -17,10 +17,25 @@ QUIZ_CASE(kandinsky_color_rgb) { quiz_assert(KDColor::RGB24(0x123456) == 0x11AA); } +void assert_colors_blend_to(KDColor c1, KDColor c2, uint8_t alpha, KDColor res) { + quiz_assert(KDColor::blend(c1, c2, alpha) == res ); +} + QUIZ_CASE(kandinsky_color_blend) { KDColor midGray = KDColor::RGB24(0x7F7F7F); - KDColor res = KDColor::blend(KDColorWhite, KDColorBlack, 0xFF); - quiz_assert(res == KDColorWhite); - quiz_assert(KDColor::blend(KDColorWhite, KDColorBlack, 0) == KDColorBlack); - quiz_assert(KDColor::blend(KDColorWhite, KDColorBlack, 0x7F) == midGray); + KDColor midTurquoise = KDColor::RGB24(0x007F7F); + + assert_colors_blend_to(KDColorWhite, KDColorBlack, 0x00, KDColorBlack); + assert_colors_blend_to(KDColorWhite, KDColorBlack, 0xFF, KDColorWhite); + assert_colors_blend_to(KDColorWhite, KDColorBlack, 0x7F, midGray); + + assert_colors_blend_to(KDColorGreen, KDColorBlue, 0x00, KDColorBlue); + assert_colors_blend_to(KDColorGreen, KDColorBlue, 0xFF, KDColorGreen); + assert_colors_blend_to(KDColorGreen, KDColorBlue, 0x7F, midTurquoise); + + // Assert that blending two identical colors does not produce strange colors. + for (uint16_t col = 0; col < 0xFFFF; col++) { + KDColor color = KDColor::RGB16(col); + assert_colors_blend_to(color, color, col>>8, color); + } } From 0a493d1f3433815122abc56efb163ab97fd02fc4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 25 Aug 2020 15:25:30 +0200 Subject: [PATCH 08/67] [kandinsky/color] Update comment on blend method Change-Id: I5fb68608657d2f604c8cb7c9294382ed4a8603ff --- kandinsky/src/color.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kandinsky/src/color.cpp b/kandinsky/src/color.cpp index 55bf9e8aa..1aa0225cb 100644 --- a/kandinsky/src/color.cpp +++ b/kandinsky/src/color.cpp @@ -4,7 +4,8 @@ KDColor KDColor::blend(KDColor first, KDColor second, uint8_t alpha) { /* This function is a hot path since it's being called for every single pixel * whenever we want to display a string. In this context, we're quite often * calling it with a value of either 0 or 0xFF, which can be very trivially - * dealt with. So let's make a special case for them. */ + * dealt with. Similarly, blending the same two colors yields a trivial + * result and can be bypassed. Let's make a special case for them. */ if (alpha == 0) { return second; } From 34ebf1e6e0a67df47191a3a4db7c880d3756fe92 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 10:16:28 +0200 Subject: [PATCH 09/67] [python/kandinsky] Remove additional interrupt Additional checks for interruptions were making the kandinsky module slower. /!\ Some scripts are now very difficult to interrupt Change-Id: I4c18273d8895deaac68084411a52556c8459d52b --- python/port/mod/kandinsky/modkandinsky.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index cf1b326d3..f6f797bc7 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -62,16 +62,6 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : KDColorWhite; MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->drawString(text, point, KDFont::LargeFont, textColor, backgroundColor); - /* Before and after execution of "modkandinsky_draw_string", - * "micropython_port_vm_hook_loop" is called by "mp_execute_bytecode" and will - * call "micropython_port_interrupt_if_needed" every 20000 calls. - * However, "drawString" function might take some time to execute leading to - * drastically decrease the frequency of calls to - * "micropython_port_vm_hook_loop" and thus to - * "micropython_port_interrupt_if_needed". So we add an extra - * check for user interruption here. This way the user can still interrupt an - * infinite loop calling 'drawString' for instance. */ - micropython_port_interrupt_if_needed(); return mp_const_none; } @@ -92,7 +82,5 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) { KDColor color = MicroPython::Color::Parse(args[4]); MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->fillRect(rect, color); - // Cf comment on modkandinsky_draw_string - micropython_port_interrupt_if_needed(); return mp_const_none; } From 7dcf1662b0912a15354c3b267ec9867ee254f0df Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 11:08:54 +0200 Subject: [PATCH 10/67] [python/helpers] Change interrupt check delay Keyboard interruption used to be checked once every 20000 calls to micropython_port_vm_hook_loop. However, if costly functions were executed in between calls to this method, the delay for activating interruptions would increase. Now, keyboard interruption is checked after a fixed amount of time has passed. This way, if the process waits a long time between two calls to micropython_port_vm_hook_loop, it is still interrupted in a timely manner. Change-Id: I37ca3bd4a996fa086078f504340dd857526e356a --- python/port/helpers.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/port/helpers.cpp b/python/port/helpers.cpp index eb905f91b..1a425556f 100644 --- a/python/port/helpers.cpp +++ b/python/port/helpers.cpp @@ -12,12 +12,15 @@ bool micropython_port_vm_hook_loop() { /* Doing too many things here slows down Python execution quite a lot. So we * only do things once in a while and return as soon as possible otherwise. */ - static int c = 0; - c = (c + 1) % 20000; - if (c != 0) { + static uint64_t t = Ion::Timing::millis(); + static constexpr uint64_t delay = 100; + + uint64_t t2 = Ion::Timing::millis(); + if (t2 - t < delay) { return false; } + t = t2; micropython_port_vm_hook_refresh_print(); // Check if the user asked for an interruption from the keyboard From b7c76957075b5a9dac528314cb117bb8bbf11bb1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 15:59:30 +0200 Subject: [PATCH 11/67] [apps/code] Remove interruption check A check for interruption in ConsoleController::printText caused script to immediately stop when launched after an interrupted script. This check was used to break out of infinite print loop, but now becomes redundant with how micropython_port_vm_hook_loop was changed. Change-Id: Ifa8d415e1b2c2406ad67300eb14ce46889af296f --- apps/code/console_controller.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index de947fa9c..ac0dc330d 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -450,26 +450,6 @@ void ConsoleController::printText(const char * text, size_t length) { flushOutputAccumulationBufferToStore(); micropython_port_vm_hook_refresh_print(); } -#if __EMSCRIPTEN__ - /* If we called micropython_port_interrupt_if_needed here, we would need to - * put in the WHITELIST all the methods that call - * ConsoleController::printText, which means all the MicroPython methods that - * call print... This is a lot of work + might reduce the performance as - * emterpreted code is slower. - * - * We thus do not allow print interruption on the web simulator. It would be - * better to allow it, but the biggest problem was on the device anyways - * -> It is much quicker to interrupt Python on the web simulator than on the - * device. - * - * TODO: Allow print interrpution on emscripten -> maybe by using WASM=1 ? */ -#else - /* micropython_port_vm_hook_loop is not enough to detect user interruptions, - * because it calls micropython_port_interrupt_if_needed every 20000 - * operations, and a print operation is quite long. We thus explicitely call - * micropython_port_interrupt_if_needed here. */ - micropython_port_interrupt_if_needed(); -#endif } void ConsoleController::autoImportScript(Script script, bool force) { From 4c41b1699d56ce6eafc4adfeb0ef699c4149b88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 15 Jun 2020 11:11:04 +0200 Subject: [PATCH 12/67] [docs] Add/remove dependencies in windows SDK --- docs/build/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build/index.md b/docs/build/index.md index b34fd48ca..18a34197b 100644 --- a/docs/build/index.md +++ b/docs/build/index.md @@ -11,7 +11,7 @@ breadcrumb: SDK We recommend using the [Msys2](https://www.msys2.org/) environment to install most of the required tools. We support Windows 7 and up. Once Msys2 has been installed, launch the Msys2 terminal application, and enter the following commands ``` -pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-fltk git make python +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python echo "export PATH=/mingw64/bin:$PATH" >> .bashrc ``` From 1c0f6a79137bda277580692479c2436ec9233b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 30 Jun 2020 12:48:33 +0200 Subject: [PATCH 13/67] [ion] Keyboard: don't detect a key down when the mouse is too far away from a key --- ion/include/ion/keyboard.h | 4 ++- ion/src/simulator/shared/layout.cpp | 40 +++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/ion/include/ion/keyboard.h b/ion/include/ion/keyboard.h index f2b8042bd..ba056ffd2 100644 --- a/ion/include/ion/keyboard.h +++ b/ion/include/ion/keyboard.h @@ -43,7 +43,9 @@ public: } operator uint64_t() const { return m_bitField; } void setKey(Key k) { - m_bitField |= (uint64_t)1 << (uint8_t)k; + if (k != Key::None) { + m_bitField |= (uint64_t)1 << (uint8_t)k; + } } void clearKey(Key k) { m_bitField &= ~((uint64_t)1 << (uint8_t)k); diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 088e5444c..f930015cc 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -1,4 +1,5 @@ #include "layout.h" +#include namespace Ion { namespace Simulator { @@ -147,16 +148,51 @@ static void getKeyCenter(int validKeyIndex, SDL_Point * point) { makeAbsolute(&sKeyCenters[validKeyIndex], point); } +#if 0 +/* TODO: once we use std++14, we can make maxHalfDistanceBetweekAdjacentKeys + * constexpr and use it to compute the k_closenessThreshold. */ + +static int maxHalfDistanceBetweekAdjacentKeys() { + int maxSquaredDistance = 0; + for (int i=0; i maxSquaredDistance) { + maxSquaredDistance = minSquaredDistance; + } + } + return maxSquaredDistance/4; // return 843 +} +#endif + Keyboard::Key keyAt(SDL_Point * p) { - int minSquaredDistance = -1; + int minSquaredDistance = INT_MAX; Keyboard::Key nearestKey = Keyboard::Key::None; + /* The maximal distance between two adjacent keys is between keys Home and + * OK (index 3 and 7). So we set the threshold to correspond to slightly over + * half the distance between these keys. + * TODO: Looking for the maximal distance between adjacent keys could be done + * constexpr once we use c++14 standard. + * (Cf maxHalfDistanceBetweekAdjacentKeys above) */ + constexpr int k_closenessThreshold = 3*843/2; // 3*maxHalfDistanceBetweekAdjacentKeys()/2 for (int i=0; ix; int dy = keyCenter.y - p->y; int squaredDistance = dx*dx + dy*dy; - if (squaredDistance < minSquaredDistance || minSquaredDistance < 0) { + if (squaredDistance < k_closenessThreshold && squaredDistance < minSquaredDistance) { minSquaredDistance = squaredDistance; nearestKey = Keyboard::ValidKeys[i]; } From b020cb2f988f7a2e5dc2538b7ecfbd95bd6f6684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 1 Jul 2020 15:58:24 +0200 Subject: [PATCH 14/67] [ion/simulator] Add haptic feedback --- ion/include/ion/events.h | 1 + ion/src/device/shared/Makefile | 1 + ion/src/device/shared/events.cpp | 10 +++++ ion/src/shared/events_keyboard.cpp | 1 + ion/src/simulator/Makefile | 1 + ion/src/simulator/android/Makefile | 1 + ion/src/simulator/ios/Makefile | 1 + ion/src/simulator/linux/Makefile | 1 + ion/src/simulator/macos/Makefile | 1 + ion/src/simulator/shared/dummy/haptics.cpp | 19 ++++++++++ ion/src/simulator/shared/events.cpp | 13 +++++++ ion/src/simulator/shared/haptics.cpp | 44 ++++++++++++++++++++++ ion/src/simulator/shared/haptics.h | 16 ++++++++ ion/src/simulator/shared/main_sdl.cpp | 9 ++++- ion/src/simulator/web/Makefile | 1 + ion/src/simulator/windows/Makefile | 1 + 16 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 ion/src/device/shared/events.cpp create mode 100644 ion/src/simulator/shared/dummy/haptics.cpp create mode 100644 ion/src/simulator/shared/events.cpp create mode 100644 ion/src/simulator/shared/haptics.cpp create mode 100644 ion/src/simulator/shared/haptics.h diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index fa7db1e3f..a76a80f0c 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -60,6 +60,7 @@ bool isLockActive(); void setLongRepetition(bool longRepetition); bool isLongRepetition(); void updateModifiersFromEvent(Event e); +void didPressNewKey(); // Used for haptic feedback on simulators // Plain diff --git a/ion/src/device/shared/Makefile b/ion/src/device/shared/Makefile index d13ec99b6..2db3b7db4 100644 --- a/ion/src/device/shared/Makefile +++ b/ion/src/device/shared/Makefile @@ -3,5 +3,6 @@ include ion/src/device/shared/usb/Makefile include ion/src/device/shared/drivers/Makefile ion_device_src += $(addprefix ion/src/device/shared/, \ + events.cpp \ stack.cpp \ ) diff --git a/ion/src/device/shared/events.cpp b/ion/src/device/shared/events.cpp new file mode 100644 index 000000000..6eab2b3fc --- /dev/null +++ b/ion/src/device/shared/events.cpp @@ -0,0 +1,10 @@ +#include + +namespace Ion { +namespace Events { + +void didPressNewKey() { +} + +} +} diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index f9535dae3..ad356b126 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -65,6 +65,7 @@ Event getEvent(int * timeout) { if (keysSeenTransitionningFromUpToDown != 0) { sEventIsRepeating = false; resetLongRepetition(); + didPressNewKey(); /* The key that triggered the event corresponds to the first non-zero bit * in "match". This is a rather simple logic operation for the which many * processors have an instruction (ARM thumb uses CLZ). diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index 9ea96074d..cab9b80e5 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -11,6 +11,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ console_stdio.cpp:-consoledisplay \ crc32.cpp \ display.cpp:-headless \ + events.cpp \ events_keyboard.cpp:-headless \ events_stdin.cpp:+headless \ framebuffer_base.cpp \ diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index d3fb36236..db1f2c006 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -5,6 +5,7 @@ ion_src += $(addprefix ion/src/simulator/android/src/cpp/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/language.cpp \ + haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index 334e1d4d1..fa14dc461 100644 --- a/ion/src/simulator/ios/Makefile +++ b/ion/src/simulator/ios/Makefile @@ -5,6 +5,7 @@ ion_src += $(addprefix ion/src/simulator/ios/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ + haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index aa8500471..a7bd38c3a 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -15,6 +15,7 @@ ion_src += $(addprefix ion/src/simulator/linux/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ + dummy/haptics.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ ) diff --git a/ion/src/simulator/macos/Makefile b/ion/src/simulator/macos/Makefile index b00459f75..4fc46018e 100644 --- a/ion/src/simulator/macos/Makefile +++ b/ion/src/simulator/macos/Makefile @@ -5,6 +5,7 @@ ion_src += $(addprefix ion/src/simulator/macos/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ + dummy/haptics.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ ) diff --git a/ion/src/simulator/shared/dummy/haptics.cpp b/ion/src/simulator/shared/dummy/haptics.cpp new file mode 100644 index 000000000..d0010df7d --- /dev/null +++ b/ion/src/simulator/shared/dummy/haptics.cpp @@ -0,0 +1,19 @@ +#include "haptics.h" + +namespace Ion { +namespace Simulator { +namespace Haptics { + +void init() { +} + +void shutdown() { +} + +void perform() { +} + + +} +} +} diff --git a/ion/src/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp new file mode 100644 index 000000000..04e35f9c8 --- /dev/null +++ b/ion/src/simulator/shared/events.cpp @@ -0,0 +1,13 @@ +#include +#include "haptics.h" +#include + +namespace Ion { +namespace Events { + +void didPressNewKey() { + Simulator::Haptics::perform(); +} + +} +} diff --git a/ion/src/simulator/shared/haptics.cpp b/ion/src/simulator/shared/haptics.cpp new file mode 100644 index 000000000..a4f45e31a --- /dev/null +++ b/ion/src/simulator/shared/haptics.cpp @@ -0,0 +1,44 @@ +#include "haptics.h" +#include + +namespace Ion { +namespace Simulator { +namespace Haptics { + +#if !EPSILON_SDL_SCREEN_ONLY +static SDL_Haptic * sSDLHaptic = nullptr; +#endif + +void init() { +#if !EPSILON_SDL_SCREEN_ONLY + if (SDL_Init(SDL_INIT_HAPTIC) == 0) { + sSDLHaptic = SDL_HapticOpen(0); + if (sSDLHaptic) { + if (SDL_HapticRumbleInit(sSDLHaptic) != 0) { + sSDLHaptic = nullptr; + } + } + } +#endif +} + +void shutdown() { +#if !EPSILON_SDL_SCREEN_ONLY + if (sSDLHaptic) { + SDL_HapticClose(sSDLHaptic); + } +#endif +} + +void perform() { +#if !EPSILON_SDL_SCREEN_ONLY + if (sSDLHaptic) { + SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40); + } +#endif +} + + +} +} +} diff --git a/ion/src/simulator/shared/haptics.h b/ion/src/simulator/shared/haptics.h new file mode 100644 index 000000000..1a8f88378 --- /dev/null +++ b/ion/src/simulator/shared/haptics.h @@ -0,0 +1,16 @@ +#ifndef ION_SIMULATOR_HAPTICS_H +#define ION_SIMULATOR_HAPTICS_H + +namespace Ion { +namespace Simulator { +namespace Haptics { + +void init(); +void perform(); +void shutdown(); + +} +} +} + +#endif diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index 241b2495c..c75cdd233 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -1,9 +1,10 @@ #include "main.h" #include "display.h" -#include "platform.h" +#include "haptics.h" #if !EPSILON_SDL_SCREEN_ONLY #include "layout.h" #endif +#include "platform.h" #include "telemetry.h" #include "random.h" @@ -27,11 +28,17 @@ int main(int argc, char * argv[]) { arguments.push_back(language); } + // Init #if EPSILON_TELEMETRY Ion::Simulator::Telemetry::init(); #endif Ion::Simulator::Main::init(); + Ion::Simulator::Haptics::init(); + ion_main(arguments.size(), &arguments[0]); + + // Shutdown + Ion::Simulator::Haptics::shutdown(); Ion::Simulator::Main::quit(); #if EPSILON_TELEMETRY Ion::Simulator::Telemetry::shutdown(); diff --git a/ion/src/simulator/web/Makefile b/ion/src/simulator/web/Makefile index 61a7641dc..66f8d8e80 100644 --- a/ion/src/simulator/web/Makefile +++ b/ion/src/simulator/web/Makefile @@ -20,6 +20,7 @@ ion_src += $(addprefix ion/src/simulator/web/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/language.cpp \ + dummy/haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 50c6396ac..0a1913c6f 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -6,6 +6,7 @@ ion_src += $(addprefix ion/src/simulator/windows/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ + dummy/haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp From 158afa7a272d3867e031d1465d18438bd9281aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 1 Jul 2020 18:45:01 +0200 Subject: [PATCH 15/67] [ion] simulator: the key detection distance threshold is responsive to the frame dimension --- ion/src/simulator/shared/layout.cpp | 42 +++++------------------------ 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index f930015cc..4079f7e9a 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -148,51 +148,21 @@ static void getKeyCenter(int validKeyIndex, SDL_Point * point) { makeAbsolute(&sKeyCenters[validKeyIndex], point); } -#if 0 -/* TODO: once we use std++14, we can make maxHalfDistanceBetweekAdjacentKeys - * constexpr and use it to compute the k_closenessThreshold. */ - -static int maxHalfDistanceBetweekAdjacentKeys() { - int maxSquaredDistance = 0; - for (int i=0; i maxSquaredDistance) { - maxSquaredDistance = minSquaredDistance; - } - } - return maxSquaredDistance/4; // return 843 -} -#endif - Keyboard::Key keyAt(SDL_Point * p) { int minSquaredDistance = INT_MAX; Keyboard::Key nearestKey = Keyboard::Key::None; - /* The maximal distance between two adjacent keys is between keys Home and - * OK (index 3 and 7). So we set the threshold to correspond to slightly over - * half the distance between these keys. - * TODO: Looking for the maximal distance between adjacent keys could be done - * constexpr once we use c++14 standard. - * (Cf maxHalfDistanceBetweekAdjacentKeys above) */ - constexpr int k_closenessThreshold = 3*843/2; // 3*maxHalfDistanceBetweekAdjacentKeys()/2 + /* The closenessThreshold is apportioned to the size of the frame. As the + * width and the height have a constant ratio, we can compute the + * closenessThreshold from the frame width exclusively. */ + int closenessThreshold = sFrame.w/6; + int squaredClosenessThreshold = closenessThreshold*closenessThreshold; for (int i=0; ix; int dy = keyCenter.y - p->y; int squaredDistance = dx*dx + dy*dy; - if (squaredDistance < k_closenessThreshold && squaredDistance < minSquaredDistance) { + if (squaredDistance < squaredClosenessThreshold && squaredDistance < minSquaredDistance) { minSquaredDistance = squaredDistance; nearestKey = Keyboard::ValidKeys[i]; } From ca56ba632738b32d8d4f2f937b5df3fc1ea75e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 2 Jul 2020 10:14:34 +0200 Subject: [PATCH 16/67] [ion] Fix include --- ion/src/simulator/shared/dummy/haptics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/dummy/haptics.cpp b/ion/src/simulator/shared/dummy/haptics.cpp index d0010df7d..1d148beb1 100644 --- a/ion/src/simulator/shared/dummy/haptics.cpp +++ b/ion/src/simulator/shared/dummy/haptics.cpp @@ -1,4 +1,4 @@ -#include "haptics.h" +#include "../haptics.h" namespace Ion { namespace Simulator { From f7479e0cf518c631bddac805fd32add939bae9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 3 Jul 2020 16:03:20 +0200 Subject: [PATCH 17/67] [ion][escher] Move image inliner to ion --- escher/Makefile | 31 ------------------------------- ion/Makefile | 32 ++++++++++++++++++++++++++++++++ {escher => ion}/image/inliner.c | 0 3 files changed, 32 insertions(+), 31 deletions(-) rename {escher => ion}/image/inliner.c (100%) diff --git a/escher/Makefile b/escher/Makefile index ce108c712..65244a136 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -91,34 +91,3 @@ escher_src += $(addprefix escher/src/,\ tests_src += $(addprefix escher/test/,\ layout_field.cpp\ ) - -$(eval $(call rule_for, \ - HOSTCC, \ - escher/image/inliner, \ - escher/image/inliner.c $(addprefix ion/src/external/lz4/, lz4.c lz4hc.c), \ - $$(HOSTCC) -std=c99 `libpng-config --cflags` $$^ `libpng-config --ldflags` -o $$@, \ - global \ -)) - -INLINER := $(BUILD_DIR)/escher/image/inliner - -.PRECIOUS: $(BUILD_DIR)/%.h $(BUILD_DIR)/%.cpp -$(eval $(call rule_for, \ - INLINER, \ - %.h %.cpp, \ - %.png $$(INLINER), \ - $$(INLINER) $$< $$(basename $$@).h $$(basename $$@).cpp, \ - global \ -)) - -# Mark a .cpp file as depending on a .png one -# This is called with $1 = code.cpp and $2 = image.png -# First, we mark code.o as requiring image.o. Rules will take care of inlining -# the PNG and building the inlined cpp file. Second, we add the directory -# corresponding to the one of code.cpp in the output dir as a header search -# path. Since $1 can be a list, we have to map with foreach. -define depends_on_image -$(call object_for,$(1)): $(call object_for,$(2)) -$(call object_for,$(1)): SFLAGS += $(foreach d,$(sort $(dir $(call object_for,$(1)))),-I$(d)) -escher_src += $(2) -endef diff --git a/ion/Makefile b/ion/Makefile index 1c325ebc6..72ec4a6cd 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -11,6 +11,38 @@ ifndef ION_KEYBOARD_LAYOUT endif SFLAGS += -Iion/include/ion/keyboard/$(ION_KEYBOARD_LAYOUT) +# Image inliner +$(eval $(call rule_for, \ + HOSTCC, \ + ion/image/inliner, \ + ion/image/inliner.c $(addprefix ion/src/external/lz4/, lz4.c lz4hc.c), \ + $$(HOSTCC) -std=c99 `libpng-config --cflags` $$^ `libpng-config --ldflags` -o $$@, \ + global \ +)) + +INLINER := $(BUILD_DIR)/ion/image/inliner + +.PRECIOUS: $(BUILD_DIR)/%.h $(BUILD_DIR)/%.cpp +$(eval $(call rule_for, \ + INLINER, \ + %.h %.cpp, \ + %.png $$(INLINER), \ + $$(INLINER) $$< $$(basename $$@).h $$(basename $$@).cpp, \ + global \ +)) + +# Mark a .cpp file as depending on a .png one +# This is called with $1 = code.cpp and $2 = image.png +# First, we mark code.o as requiring image.o. Rules will take care of inlining +# the PNG and building the inlined cpp file. Second, we add the directory +# corresponding to the one of code.cpp in the output dir as a header search +# path. Since $1 can be a list, we have to map with foreach. +define depends_on_image +$(call object_for,$(1)): $(call object_for,$(2)) +$(call object_for,$(1)): SFLAGS += $(foreach d,$(sort $(dir $(call object_for,$(1)))),-I$(d)) +ion_src += $(2) +endef + include ion/src/$(PLATFORM)/Makefile -include ion/test/$(PLATFORM)/Makefile include ion/src/shared/tools/Makefile diff --git a/escher/image/inliner.c b/ion/image/inliner.c similarity index 100% rename from escher/image/inliner.c rename to ion/image/inliner.c From 8f8f42edd5b23736dab40dc18888be4fcd4276d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 3 Jul 2020 16:04:36 +0200 Subject: [PATCH 18/67] [ion] simulator: add forgotten Display::quit --- ion/src/simulator/shared/main_sdl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index c75cdd233..cc3b9c68e 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -155,6 +155,7 @@ void refresh() { } void quit() { + Display::quit(); SDL_DestroyWindow(sWindow); SDL_Quit(); } From fcf4c011cbaecd1e32ac20c47f7b3c4ed9d63dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 3 Jul 2020 16:06:15 +0200 Subject: [PATCH 19/67] [ion] simulator/layout: change signature of makeAbsolute --- ion/src/simulator/shared/layout.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 4079f7e9a..f644c3882 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -16,16 +16,16 @@ static constexpr SDL_FRect screenRect = {X(192), Y(191), X(776), Y(582)}; static SDL_Rect sFrame; -static void makeAbsolute(const SDL_FRect * f, SDL_Rect * r) { - r->x = f->x * sFrame.w + sFrame.x; - r->y = f->y * sFrame.h + sFrame.y; - r->w = f->w * sFrame.w; - r->h = f->h * sFrame.h; +static void makeAbsolute(const SDL_FRect f, SDL_Rect * r) { + r->x = f.x * sFrame.w + sFrame.x; + r->y = f.y * sFrame.h + sFrame.y; + r->w = f.w * sFrame.w; + r->h = f.h * sFrame.h; } -static void makeAbsolute(const SDL_FPoint * f, SDL_Point * p) { - p->x = f->x * sFrame.w + sFrame.x; - p->y = f->y * sFrame.h + sFrame.y; +static void makeAbsolute(const SDL_FPoint f, SDL_Point * p) { + p->x = f.x * sFrame.w + sFrame.x; + p->y = f.y * sFrame.h + sFrame.y; } void recompute(int width, int height) { @@ -75,7 +75,7 @@ void recompute(int width, int height) { } void getScreenRect(SDL_Rect * rect) { - makeAbsolute(&screenRect, rect); + makeAbsolute(screenRect, rect); } void getBackgroundRect(SDL_Rect * rect) { @@ -143,9 +143,8 @@ static constexpr SDL_FPoint sKeyCenters[Keyboard::NumberOfValidKeys] = { }; static void getKeyCenter(int validKeyIndex, SDL_Point * point) { - assert(validKeyIndex >= 0); - assert(validKeyIndex < Keyboard::NumberOfValidKeys); - makeAbsolute(&sKeyCenters[validKeyIndex], point); + assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys); + makeAbsolute(sKeyCenters[validKeyIndex], point); } Keyboard::Key keyAt(SDL_Point * p) { From 94851da7f936e4e7b32453fe09508753e8870989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 3 Jul 2020 16:52:01 +0200 Subject: [PATCH 20/67] [ion] Add drafts of images reprensenting key shapes --- ion/src/simulator/Makefile | 6 ++++++ .../shared/key_layouts/horizontal_arrow.png | Bin 0 -> 8616 bytes .../shared/key_layouts/large_squircle.png | Bin 0 -> 8583 bytes ion/src/simulator/shared/key_layouts/round.png | Bin 0 -> 8906 bytes .../shared/key_layouts/small_squircle.png | Bin 0 -> 8637 bytes .../shared/key_layouts/vertical_arrow.png | Bin 0 -> 8616 bytes 6 files changed, 6 insertions(+) create mode 100644 ion/src/simulator/shared/key_layouts/horizontal_arrow.png create mode 100644 ion/src/simulator/shared/key_layouts/large_squircle.png create mode 100644 ion/src/simulator/shared/key_layouts/round.png create mode 100644 ion/src/simulator/shared/key_layouts/small_squircle.png create mode 100644 ion/src/simulator/shared/key_layouts/vertical_arrow.png diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index cab9b80e5..2d16076e3 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -26,5 +26,11 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ timing.cpp \ ) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/horizontal_arrow.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/large_squircle.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/round.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/small_squircle.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/vertical_arrow.png)) + include ion/src/simulator/$(TARGET)/Makefile include ion/src/simulator/external/Makefile diff --git a/ion/src/simulator/shared/key_layouts/horizontal_arrow.png b/ion/src/simulator/shared/key_layouts/horizontal_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..a25d82e100dffee4d5f114cfe3643cb2afe935a9 GIT binary patch literal 8616 zcmb_>2Q*w=zxNO&AxLzRFj|5bGZK6$L8=005v;RZ-L- zoc#%h)@3rnKeWnc003YtwpCEjQnYn-c0hRp07f09>9$b)-O27Fgs0oAebefC#+vu- zeMtMVt-K^YT{<=1WZ?o@&aenFeuh_C{P7$LIZRUP7n0jKH92RieiNSG z=Gg|N?X#;7i5A$tKZ%S~GzR47FRyDdc0p+#fLRwW0fUIh7D%OAgX)>N$jMCsL9LS) z5R!+xq(A`R!OEGXCFkOWbxE+C3lSjV65t1U7pWo

kqlWe~HEB;YnXK+&_a`~9Vd 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 literal 0 HcmV?d00001 diff --git a/ion/src/simulator/shared/key_layouts/round.png b/ion/src/simulator/shared/key_layouts/round.png new file mode 100644 index 0000000000000000000000000000000000000000..a7911d540630f70478523cc054724f1b238d8960 GIT binary patch literal 8906 zcmb_>2UJsCw{7S}x(W!=L7G595=!Vziqa8KY6t-$B_V{~5kUb#LI;%&A_xkCD7{OU zjtB?>(wlTcx%d_D_wM`eegC-QWsKzPv-jL<&9&CtduN>U$Usk%mYR(k007WxYpENZ zUju)ADap@&<3d~n008D(l$x4>I?4m%0`~;~%v)Zid72D2y=z<~Oxb_Vuc@g~`*0Jc zac|jyiV>u&s%ogCW2h{wp+Utctin%9blJp&;j)1VDHWA(wBq)`bnxa=@BXySpgsk# z_eQ{GkN5XJ;zr5kE1aA(LjVRQHjQ%7;DS5WYqqJDfpth&5rdlI#?%y1IT*n?K_XZ$*kXKPbG%`y(=nRWFJd5Px%YhbxNu>w<$M z_{m2)ye}r@&|5D4J4K7)5`29NJfR4gwqzv~EY|6%d^CsF)t)APut zvv=X7nrG{34=uM&`4JgYryn<6c!hdg;?(N)Elw8zn@Wo7<-u>?oY-8uvGV>HiPjIS zsH5d6xZHptp2BKl@m}BaTl$lM1l0b1_#N6pZ^gML|6SfZ75|LN08P zFmhAPhcFpXOf&*giEr9cJPl>%CJtsG)|z2>NI}I-W*<_)O@ArW$bjx;qo)CVy0YjJ zDSZ>bhxmD@nJskyAhU_roGgQZ6wXboPX9)A@(DLTU8jo3lWX>XG&QLw;CZ4Wm23mZ zCHiOKl36YQ+0j&Yi*`}EC0 z7}Bd0f=!My#MLzQ#C+a?D10{LGutSn5yw{Efpmaw|Vkhe3 z(bi`tE$k#jDm*N7UAR`5&e-12#Ne#~jd4KMcbDkC)KPqgAp55CjJ#;$05+G1daz@;6i<=>wa)Oja_0lX& zcKTMxR>W&P4SfL(r0RmM=hK&|FXQheu82a(D!D7^D%m}+dMfU@uE1Bw*r?ff*c90G z(-Dw1h&QSoW$LtyLZZY_YEEF3vlGE-aS4c1#gXHtaoV^A9Q~5R665aiuHUZu&xGB} zy9&Epte|%i5A#LqkCBJTH_4O9PsvBuUaf7f9jHz5jr5T|3O`yqVmfl$s{7utUA#s3 zvG~J#TY?&M!JM-7!Z3C1r91``#$6V9iE+{DYy8*eg%U-E`5}Tmye_;~1uaG8g;f~_ zm>w|*U-T7;V!q8t%T&kxQ=Nv{_|guGC^I{AWLr`zNvp)eIIS#x69H9$F8+3bZ9EJQ z!dK#ljBXiyH+p2GYh;s8mS>gMuYI6%hBr*4N-lchD{xcLStLW~3vUg-;5E%e(PV!S z{U_3iO5$IypINC`Hfhf0G>7xnuu8sv&zFP^A?<-kH=W4QX2t|dH<9XGZ+yc@gXxx}~ocz0+ol4gl!jW&vA zn)Sz(pI1iN#My+|I6y1F8&@)}WP&-RXCXZ4CK=b$MKU;{>ryQlpVKd=)qz>Sf@$x} z8<`83WtkHQWCxKO=yKBJYss?7Wv{|t@s@1d$$j;zB>I)o#K(!&FS`?iUp{;p9WR|& zo=S0u7$a~W8SZnV;g6br$gyW zqe_KK`{}8KS&2=X8_vKyG26x`*tQ0m8&P&`j*&7}-{%A~87%@0M zL_HLg?_YS}_|bme(FMr_Cg^uR7niB?Xg}yNNH!X6t5~YYcFfEif|(`hW^kk^Bp~94 zQo9o}o)h@`70M)Rcp7%)Hv)E#iimf@zN>~dS3I!LIMwKn@{C3&ViNO`O02f(fqsSr zJ1b#F)Q#o#{E(X$Qyz;;CsQRYq~vxZx|B?1Om?je-x~!ct>0@OS?~JPRV0$}a^v2@ z$_iU-ddb~tsSjSK>8q83V|!)NW7?xu^-Vawxog!|0(XYQ;w<`Cdsk}QiYH5J_Znh{C`6s=qebshwWz(^8b^<@)H8D3~25XjM zhMmA1KTOtGD`5P#-hTPu|DvO0aX_sllHSkL?+X`7{@%LrYR#9j!LN`k$S0W>GUHq3 z+wLt-1`Xz3|G?~A91#@bBixv{QF%kIW4D7-5$X%uayhGrU)=8(Y`5wtyjCmt-I}!F z@Ko~q!uR5f2`@T##~@`pUoo}9{-Qtk+Y<+ePf#bzP2N?T*1ZgliVblA4SPJxe6xtt z>jy4-PbV!`YiExW^eGer53ip3dhjenM#ihj@=n64tjaKHiel?osxh1;Iqg$^OV~Wa^D#%XzwUEnJ|Au5DMXD z86RT_$=3l;_5nUA5dpks`mg+KAX8BcXsjvp`B9WbB6+{Q$(NEP@0t#g>%;9P>-^M$ z;YJoCm0=#p=5p<_5|}Bi;7;wGudgjI5Ue{k!RM||%HeymL^99tnD9OIVfD*x>aFkj zodn|X&PE=C5=e67mxYyu?#tzuFD5l7(Z6IZ7J2DjlsnR}!9KS1sboN8uy0U%On0dJ zQ@`X#sUA=lv=7_~?uE)Z2DS4d&V;+|ixWl#UGXmZ69S(FW%*&(WH`JPzcKl94?=fs zLo(jU4YYKCKZ>TO&IGm>j=vk+tO~Yh%x#R%N@)V~T*xk-eMkwz+veR%y#3auXFzSy zPL%##D=kM_tPLzeM$aQmEL1T5jNgVyUs99$rbvtM3d9m787PNrI(|Zy^~AvtU9Y<%h1rr&0t zQMwMRLS%6=KOr~xhZw)pn%Y#cTupyUW|`@G$>-##{IHmejWpvydN}%#$)n_`g=j~u zs|hhre&DN8K=|7!35koQS+~P4r6d+4WxjY@H2PwpICGpO4_;Q3FHq21pq3v!JT+=D za^?VZlyQZ?+ScAiaSuySVHMV zYC1ne`#x2!;iHO5QzjtNV zyitoRoHP9f(!3M*h|^3lJH_{ij9?Ve(z&EeLL)sI@$HfugM^M&)=w?OO+u=5 z#;jBVsRxAET=H=X2_oAs`JpzRDS^yv6Y13ROFOHWW$cpsiU->k>krm#R*y7HdU-l1 zZODA)0C64O%~342s+&RkMXW}QAQq|Hk*QbQcT?PkzNe;M>2P#Ci4fl5(eGZy(mFOh zqzJC(I(9f&B>qmac>jBtqnd--Vsm|X8Gc6VgHWhbK5K#+g`FB*M+cHq_)(ecg!;5D zL63%iSHLT?HpL{I>*8n7-gK*b2e%CZmpSG8d8^9309AD~G$kPxeUN-Nc+v)C;cT;NzG}I7H~95*-E_r1rqX+10Q>de z6is47uB31(0|CqL*<aPdh2Bz)7_Id~2#Ty3=^=;mA zFqys(m~}>}DBh#jZVOhjd6GPq@8!5FFyW8;fFs%6-!-Pm zrfFd9m!y)cl#~NXRM=ODyso#EYZC8L>~arJ$n*#ijmn6w$y@}Mv2HroUk&mgicx-N zi+>Udyiu*suQse2bHn9UtwFP~kt)_|B_5Z3>tRX#)xyb$FgjOd;?=(Yi0**H z?3sY2JpKIp{Th{xdz_%>wARJb_Y|q>Nltr+f%0Gi(pE=h1;r*Xm97S3r7_l48;s}fJyYuy1JAEGZdb($M^f32Emm<{+&b7vMEoy zNxOnAea-I}aTq)MTb+{c^gjD}BzN&KNiY4~4|n~)w8I=ejQTtI3lOf8^K!D#9mE{_ zi2Rrg%-LGl8KXhF7F0O4D@yji4}N)P{Lo>$&d%3y#I+7#>_eE`+@zpp__)1Do}?dXsh!{xB!mXFL_1v zc*Z94p4M?lZID{1F(iV{<*rgW@hxAN1h7DTMsP*K7%6WP*eur1`;wlHH{ey z^$IJH!%s(Lc6ME~q%`zApWit>2y5oO|83#ILjHcr8{~m#PN|M*pPAVhxm8M$MZ;&S z%)pKs!@9JijnTRX1dsKvH4IZ%=|?!ur4Mg~;-CyL)s-KX5 z=eci2zp%|jgX{I8(}y;JTM4h1%dKiyg0D4_w1)70B6nz{@*!c8q3a7_!qC1AQ`uq* zxNrNAk?8@1$3lofCnPFM?i27WV9wTkocLOk28O;iW=2znTauBU;fvN~dzx~__pwHr z0$D#l?K*kydhSZKQ+tqAJn(FsF38G#n3LLw`c`VdB!mzu+;TW{2xG_&iNcG--@6Zs z@JqWE<&d;b$#E-1uV2}wi@WJlvs3$FvT%A@)cc6GROItLNQV3u(S3e8RIJ(4BFiYt z&G$RLJwEVh(|YqD--*Cyhp2Vvs;6tHLuOI?Tf&m07rg!zEoZ$vb5{@6OV*3 z69$7kLscPH5%dd}qNbHZf;l7{I`X#UEeQ_qDSG~}!;$vQ4ZC)SYdi&xz3DxL`*UI= zuT)FAU92ZS6JKESFhy?%cUjC#&Z?X=;{hhlRO0Hu2%$h?w7D_NC9c9BDtW~G@VP;J+s=*25v%)hrRWrQ5Hm8gSf1BM=cxI;e8Dp_ zPH0}t&lK-&c*cZ#Um<@KhOWrEp{08uw9~Hdy-$LW{+x zo*(HxmvVNXa1>00NOFB>FGIt3{?wWZqjkpv01!WKi}Mlrk~qJ8%E$mf+?71A=o_;8pqM zklS3>fLjfXMRG&MrNj^*2$Wk|RvaV+hRDiZ=avM5WF&wx5+JY`ND2l4!XzcR|N8Kr zd&Am0z>L&y{N?NXPJ!16hr_@mB)q-7#l6AeXsn|INLE%>0w^gVDJgc25cBYL!@+&T z+&uXHwxEvmKwwcA9188m{mUZU4(*9k;5}#hX9%vC-)7xB{z}t%!X$j)7zvO#@K;EG z0PPXKaTrgm%OB3|5fVrjq$|=5=W&h&{l;RP&^WY*6Z*d~{d@TTaB!YlUESY4{v{Vz z*WWHYa2j6cVf>Yle~I=m@x>q|jF28^Pb>nd;dM@v?^iS!m>L!d$Dy$%Xtc}Ug);b? zGPj!AufoW33+ciUD7Rm62>)XiQXP&%D)9a)ofr@-29z-Y0b%FW14@emK`<)ivlJ86MC*2dpHjM{{-73U=C=kEBrialq=j3DS>fw zS%=Luj>Dr|K~!m zMb_N=ybUD|^mL%1%ty!9hw4gp`$n+RK2>$DqFy|A&ZRS}2e6^7{U@ zhK!N!f3;jt+<)i>21oo_4+^}9UqV2hGx)O`^)K@AZ+ZS}zPA(d-01(HjX&LZpdE1D za4b^A@jQ3`o&Y8Oy9hksUjH+Hh%6K#E$!eS2C~44+eV7uSz!D@xm(-0Ec~Kq=c+L3$>5OT4ntyLma-~LfK2t_9G6Cj zs(9>rrMF{!e1S@9ja-A*VmY|tj>AJeef|Ef@?(#HfB*%y#~X36v8SfTI1FaXFlF!T z=d+-)%_aYzZsZz7Th&5Q(bQQr^8J})=6j2_C7Wh*^7rH)7jLU%Q^3xTpfazS_1W3k zhLe+%EN(R7ahb1^VmiwbF2LT|`!;dV6{dODk|Wgf7h%kr-ue246Wad%{#>B@bv@~2 z-(MZ&60d|R6vcM?<|}37N&T|MH_?1 z{H8bwNp1ef1y(e6&H1EsQk+alKCG?>f8W=+K`1KicHPvCuQ$c7(Lx_c6Zy`S21_EA zl90a3x4oqY_V8va9Y?zNnvz(<{`N2 z=cqoD$BlG{%>3Z5znPvD!4Y9m8)5aDl#=vnWzeP+qnfaueQj%I=n!fq;%OBQyYXRS z!Hg2Z;r0w!{DOYe4?h~P5txK>z9dd!K8w}+Nbc< z?pjrMiq{0h&fHqtmt~eqKE4|r7}a>|&X1v2S;d~@&E+i@H8e ziJhs(Qw6yvWISo|-)u&$GkLj(SU`)#0Xr7qhzZArwtEL|ZQY@QYb(6h74t`-UkI3`xW-4BpSzmOVN_DPg@yCu_S^)XZUY^2imwW9&}Apd&| zoyXz}Yj<-pHG!8c)Mk^=NH_K@5*W=AFsmhm?jMaw!KKz|j`r#INDrUx_P`G<1e~Pf zRfoPOe2YNMSQqoi&#&j}ybEIe&g}*VDXG=N$SI$M{UhWv43$yZaQx z^@uR#5wT%Z<%{$#YGc&rlFiU0tSDlu-SjLgkvY;O3eywp%%aneM(S_v`b&E!eW?2? zCn1;m!M5n^20OYsb-Q=9Znk`Uy>`@|Zw=Pz#e-d>nQva#sclT%6-VzA&o$!(QIaF` z9R=Y6=t(;p&DzZ8lWjUR2SdTF^F?9W`F{DLMr+J0;z2B~>w73;ktn+eOm1Ir29oC? z_1HsEL$&w-rII6|S9{vb_^$HW`96i`HQ3qsJ(Z->YLz9!rR|IoeYVFp=qZCfjsH{| zl@?yP50`#AI|^GFZGYXpb9|ZGPfRs{)~$MkY7!7o7LthsF*M4NT%CDFOt+;+bcULQ zk9gn9*Dt@MwLuyb;I9+_d{^Q+ANyo|BYR|;&y~k+&vJ0ML4H{*Irh0`aHRZ(qy5Zr zpAS_sfHA>Bvv0~<&tiAOJSDG0x9o%U>c(|vXp@zasa<`F4%;i^Ro%hM7vM`p`&}>V z$3DDOOo+IvIn)2@D&cPAD9QL<+)2@0$2{iAfdDAFHxp6&tTLFzzXsf^zk@?QKCs`; zEZ)iX8vOLrX3m*2U4Jo2;FI6wtkU)gs}%(2&urpAn>i=>$K3-Q@gkqt1+dZfVxouH qTLpmP?w`RYdJSXVy9&;l`@wd8N_As4JUzeuzH4jfsh6tShW!V^A5%vF literal 0 HcmV?d00001 diff --git a/ion/src/simulator/shared/key_layouts/small_squircle.png b/ion/src/simulator/shared/key_layouts/small_squircle.png new file mode 100644 index 0000000000000000000000000000000000000000..9dfbf335fecdad1cb22c9bd799c39a29169308b7 GIT binary patch literal 8637 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 literal 0 HcmV?d00001 diff --git a/ion/src/simulator/shared/key_layouts/vertical_arrow.png b/ion/src/simulator/shared/key_layouts/vertical_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..a25d82e100dffee4d5f114cfe3643cb2afe935a9 GIT binary patch literal 8616 zcmb_>2Q*w=zxNO&AxLzRFj|5bGZK6$L8=005v;RZ-L- zoc#%h)@3rnKeWnc003YtwpCEjQnYn-c0hRp07f09>9$b)-O27Fgs0oAebefC#+vu- zeMtMVt-K^YT{<=1WZ?o@&aenFeuh_C{P7$LIZRUP7n0jKH92RieiNSG z=Gg|N?X#;7i5A$tKZ%S~GzR47FRyDdc0p+#fLRwW0fUIh7D%OAgX)>N$jMCsL9LS) z5R!+xq(A`R!OEGXCFkOWbxE+C3lSjV65t1U7pWo

kqlWe~HEB;YnXK+&_a`~9Vd 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 UYs2pqzn)4}l{6L0 Date: Fri, 3 Jul 2020 16:52:54 +0200 Subject: [PATCH 21/67] [ion] Simulator: highlight keys when the mouse is over --- ion/src/simulator/shared/keyboard_sdl.cpp | 4 +- ion/src/simulator/shared/layout.cpp | 224 ++++++++++++++++------ ion/src/simulator/shared/layout.h | 3 +- ion/src/simulator/shared/main_sdl.cpp | 1 + 4 files changed, 173 insertions(+), 59 deletions(-) diff --git a/ion/src/simulator/shared/keyboard_sdl.cpp b/ion/src/simulator/shared/keyboard_sdl.cpp index a1b173719..5ea6b8bdc 100644 --- a/ion/src/simulator/shared/keyboard_sdl.cpp +++ b/ion/src/simulator/shared/keyboard_sdl.cpp @@ -69,8 +69,8 @@ State scan() { // Register a key for the mouse, if any SDL_Point p; Uint32 mouseState = SDL_GetMouseState(&p.x, &p.y); - if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) { - Key k = Simulator::Layout::keyAt(&p); + Key k = Simulator::Layout::highlightKeyAt(&p); + if (mouseState && SDL_BUTTON(SDL_BUTTON_LEFT)) { state.setKey(k); } #endif diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index f644c3882..4266b6853 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -1,5 +1,12 @@ #include "layout.h" +#include "main.h" +#include #include +#include "key_layouts/horizontal_arrow.h" +#include "key_layouts/large_squircle.h" +#include "key_layouts/round.h" +#include "key_layouts/small_squircle.h" +#include "key_layouts/vertical_arrow.h" namespace Ion { namespace Simulator { @@ -85,69 +92,124 @@ void getBackgroundRect(SDL_Rect * rect) { rect->h = sFrame.h; } -static constexpr SDL_FPoint sKeyCenters[Keyboard::NumberOfValidKeys] = { - {X(185), Y(1029)}, // A1, Left - {X(273), Y(941)}, // A2, Up - {X(273), Y(1117)}, // A3, Down - {X(361), Y(1029)}, // A4, Right - {X(810), Y(1029)}, // A5, OK - {X(963), Y(1029)}, // A6, Back +class KeyLayout { +public: + enum class Shape { + HorizontalArrow, + VerticalArrow, + Round, + SmallSquircle, + LargeSquircle + }; + constexpr KeyLayout(float x, float y, Shape shape) : + m_center{X(x), Y(y)}, + m_shape(shape) {} + SDL_FPoint center() const { return m_center; } + Shape shape() const { return m_shape; } - {X(580), Y(958)}, // B1, Home - {X(580), Y(1094)}, // B2, Power - - {X(198), Y(1252)}, // C1, Shift - {X(352), Y(1252)}, // C2, Alpha - {X(506), Y(1252)}, // C3, xnt - {X(656), Y(1252)}, // C4, var - {X(810), Y(1252)}, // C5, toolbox - {X(963), Y(1252)}, // C6, Delete - - {X(198), Y(1375)}, // D1, exp - {X(352), Y(1375)}, // D2, ln - {X(506), Y(1375)}, // D3, log - {X(656), Y(1375)}, // D4, i - {X(810), Y(1375)}, // D5, comma - {X(963), Y(1375)}, // D6, power - - {X(198), Y(1498)}, // E1, sin - {X(352), Y(1498)}, // E2, cos - {X(506), Y(1498)}, // E3, tan - {X(656), Y(1498)}, // E4, pi - {X(810), Y(1498)}, // E5, sqrt - {X(963), Y(1498)}, // E6, square - - {X(210), Y(1629)}, // F1, 7 - {X(395), Y(1629)}, // F2, 8 - {X(580), Y(1629)}, // F3, 9 - {X(765), Y(1629)}, // F4, ( - {X(950), Y(1629)}, // F5, ) - - {X(210), Y(1766)}, // G1, 4 - {X(395), Y(1766)}, // G2, 5 - {X(580), Y(1766)}, // G3, 6 - {X(765), Y(1766)}, // G4, * - {X(950), Y(1766)}, // G5, / - - {X(210), Y(1902)}, // H1, 1 - {X(395), Y(1902)}, // H2, 2 - {X(580), Y(1902)}, // H3, 3 - {X(765), Y(1902)}, // H4, + - {X(950), Y(1902)}, // H5, - - - {X(210), Y(2040)}, // I1, 0 - {X(395), Y(2040)}, // I2, . - {X(580), Y(2040)}, // I3, x10 - {X(765), Y(2040)}, // I4, Ans - {X(950), Y(2040)}, // I5, EXE +private: + SDL_FPoint m_center; + Shape m_shape; }; +static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { + KeyLayout(185, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left + KeyLayout(273, 941, KeyLayout::Shape::VerticalArrow), // A2, Up + KeyLayout(273, 1117, KeyLayout::Shape::VerticalArrow), // A3, Down + KeyLayout(361, 1029, KeyLayout::Shape::HorizontalArrow), // A4, Right + KeyLayout(810, 1029, KeyLayout::Shape::Round), // A5, OK + KeyLayout(963, 1029, KeyLayout::Shape::Round), // A6, Back + + KeyLayout(580, 958, KeyLayout::Shape::LargeSquircle), // B1, Home + KeyLayout(580, 1094, KeyLayout::Shape::LargeSquircle), // B2, Power + + KeyLayout(198, 1252, KeyLayout::Shape::SmallSquircle), // C1, Shift + KeyLayout(352, 1252, KeyLayout::Shape::SmallSquircle), // C2, Alpha + KeyLayout(506, 1252, KeyLayout::Shape::SmallSquircle), // C3, xnt + KeyLayout(656, 1252, KeyLayout::Shape::SmallSquircle), // C4, var + KeyLayout(810, 1252, KeyLayout::Shape::SmallSquircle), // C5, toolbox + KeyLayout(963, 1252, KeyLayout::Shape::SmallSquircle), // C6, Delete + + KeyLayout(198, 1375, KeyLayout::Shape::SmallSquircle), // D1, exp + KeyLayout(352, 1375, KeyLayout::Shape::SmallSquircle), // D2, ln + KeyLayout(506, 1375, KeyLayout::Shape::SmallSquircle), // D3, log + KeyLayout(656, 1375, KeyLayout::Shape::SmallSquircle), // D4, i + KeyLayout(810, 1375, KeyLayout::Shape::SmallSquircle), // D5, comma + KeyLayout(963, 1375, KeyLayout::Shape::SmallSquircle), // D6, power + + KeyLayout(198, 1498, KeyLayout::Shape::SmallSquircle), // E1, sin + KeyLayout(352, 1498, KeyLayout::Shape::SmallSquircle), // E2, cos + KeyLayout(506, 1498, KeyLayout::Shape::SmallSquircle), // E3, tan + KeyLayout(656, 1498, KeyLayout::Shape::SmallSquircle), // E4, pi + KeyLayout(810, 1498, KeyLayout::Shape::SmallSquircle), // E5, sqrt + KeyLayout(963, 1498, KeyLayout::Shape::SmallSquircle), // E6, square + + KeyLayout(210, 1629, KeyLayout::Shape::LargeSquircle), // F1, 7 + KeyLayout(395, 1629, KeyLayout::Shape::LargeSquircle), // F2, 8 + KeyLayout(580, 1629, KeyLayout::Shape::LargeSquircle), // F3, 9 + KeyLayout(765, 1629, KeyLayout::Shape::LargeSquircle), // F4, ( + KeyLayout(950, 1629, KeyLayout::Shape::LargeSquircle), // F5, ) + + KeyLayout(210, 1766, KeyLayout::Shape::LargeSquircle), // G1, 4 + KeyLayout(395, 1766, KeyLayout::Shape::LargeSquircle), // G2, 5 + KeyLayout(580, 1766, KeyLayout::Shape::LargeSquircle), // G3, 6 + KeyLayout(765, 1766, KeyLayout::Shape::LargeSquircle), // G4, * + KeyLayout(950, 1766, KeyLayout::Shape::LargeSquircle), // G5, / + + KeyLayout(210, 1902, KeyLayout::Shape::LargeSquircle), // H1, 1 + KeyLayout(395, 1902, KeyLayout::Shape::LargeSquircle), // H2, 2 + KeyLayout(580, 1902, KeyLayout::Shape::LargeSquircle), // H3, 3 + KeyLayout(765, 1902, KeyLayout::Shape::LargeSquircle), // H4, + + KeyLayout(950, 1902, KeyLayout::Shape::LargeSquircle), // H5, - + + KeyLayout(210, 2040, KeyLayout::Shape::LargeSquircle), // I1, 0 + KeyLayout(395, 2040, KeyLayout::Shape::LargeSquircle), // I2, . + KeyLayout(580, 2040, KeyLayout::Shape::LargeSquircle), // I3, x10 + KeyLayout(765, 2040, KeyLayout::Shape::LargeSquircle), // I4, Ans + KeyLayout(950, 2040, KeyLayout::Shape::LargeSquircle), // I5, EXE +}; + +const Image * imageForKey(int keyIndex) { + if (keyIndex == -1) { + return nullptr; + } + assert(keyIndex >= 0 && keyIndex < Keyboard::NumberOfValidKeys); + switch (sKeyLayouts[keyIndex].shape()) { + case KeyLayout::Shape::Round: + return ImageStore::Round; + case KeyLayout::Shape::LargeSquircle: + return ImageStore::LargeSquircle; + case KeyLayout::Shape::SmallSquircle: + return ImageStore::SmallSquircle; + case KeyLayout::Shape::VerticalArrow: + return ImageStore::VerticalArrow; + default: + assert(sKeyLayouts[keyIndex].shape() == KeyLayout::Shape::HorizontalArrow); + return ImageStore::HorizontalArrow; + } +} + static void getKeyCenter(int validKeyIndex, SDL_Point * point) { assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys); - makeAbsolute(sKeyCenters[validKeyIndex], point); + makeAbsolute(sKeyLayouts[validKeyIndex].center(), point); } -Keyboard::Key keyAt(SDL_Point * p) { +static void getKeyRectangle(int validKeyIndex, SDL_Rect * rect) { + assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys); + SDL_FPoint point = sKeyLayouts[validKeyIndex].center(); + const Image * img = imageForKey(validKeyIndex); + SDL_FRect fRect; + fRect.w = X(img->width()); + fRect.h = Y(img->height()); + fRect.x = point.x - fRect.w/2.0f; + fRect.y = point.y - fRect.h/2.0f; + makeAbsolute(fRect, rect); +} + +int sHighlightedKeyIndex; + +Keyboard::Key highlightKeyAt(SDL_Point * p) { + int newHighlightedKeyIndex = -1; int minSquaredDistance = INT_MAX; Keyboard::Key nearestKey = Keyboard::Key::None; /* The closenessThreshold is apportioned to the size of the frame. As the @@ -164,11 +226,61 @@ Keyboard::Key keyAt(SDL_Point * p) { if (squaredDistance < squaredClosenessThreshold && squaredDistance < minSquaredDistance) { minSquaredDistance = squaredDistance; nearestKey = Keyboard::ValidKeys[i]; + newHighlightedKeyIndex = i; } } + if (newHighlightedKeyIndex != sHighlightedKeyIndex) { + sHighlightedKeyIndex = newHighlightedKeyIndex; + Main::setNeedsRefresh(); + } return nearestKey; } +SDL_PixelFormat * sRgbaPixelFormat = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); + +// LargeSquircle png file is ? x ? = ? +static constexpr size_t k_maxKeyLayoutWidth = 125; // TODO: recompute +static constexpr size_t k_maxKeyLayoutHeight = 125; // TODO: recompute +static constexpr uint8_t k_blendingRatio = 0x44; + +void fillRGBABufferWithImage(Uint32 * buffer, const Image * img) { + KDColor pixelBuffer[k_maxKeyLayoutWidth*k_maxKeyLayoutHeight]; + Ion::decompress( + img->compressedPixelData(), + reinterpret_cast(pixelBuffer), + img->compressedPixelDataSize(), + img->width() * img->height() * sizeof(KDColor) + ); + for (int i = 0; i < img->width() * img->height(); i++) { + buffer[i] = SDL_MapRGBA(sRgbaPixelFormat, pixelBuffer[i].red(), pixelBuffer[i].green(), pixelBuffer[i].blue(), k_blendingRatio); + } +} + +void drawHighlightedKey(SDL_Renderer * renderer) { + if (sHighlightedKeyIndex < 0) { + return; + } + const Image * img = imageForKey(sHighlightedKeyIndex); + SDL_Texture * framebufferTexture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STREAMING, + img->width(), + img->height() + ); + SDL_SetTextureBlendMode(framebufferTexture, SDL_BLENDMODE_BLEND); + int pitch = 0; + void * pixels = nullptr; + SDL_LockTexture(framebufferTexture, nullptr, &pixels, &pitch); + assert(pitch == sizeof(Uint32) * img->width()); + fillRGBABufferWithImage(static_cast(pixels), img); + SDL_UnlockTexture(framebufferTexture); + SDL_Rect rect; + getKeyRectangle(sHighlightedKeyIndex, &rect); + SDL_RenderCopy(renderer, framebufferTexture, nullptr, &rect); + SDL_DestroyTexture(framebufferTexture); +} + } } } diff --git a/ion/src/simulator/shared/layout.h b/ion/src/simulator/shared/layout.h index 9e0f10ec6..6430d50b4 100644 --- a/ion/src/simulator/shared/layout.h +++ b/ion/src/simulator/shared/layout.h @@ -13,7 +13,8 @@ void recompute(int width, int height); void getScreenRect(SDL_Rect * rect); void getBackgroundRect(SDL_Rect * rect); -Ion::Keyboard::Key keyAt(SDL_Point * p); +Ion::Keyboard::Key highlightKeyAt(SDL_Point * p); +void drawHighlightedKey(SDL_Renderer * renderer); } } diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index cc3b9c68e..6fb3e3e54 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -147,6 +147,7 @@ void refresh() { SDL_RenderClear(sRenderer); SDL_RenderCopy(sRenderer, sBackgroundTexture, nullptr, &backgroundRect); Display::draw(sRenderer, &screenRect); + Layout::drawHighlightedKey(sRenderer); #endif SDL_RenderPresent(sRenderer); sNeedsRefresh = false; From a3ef7c92343e0f5f19d49f428ce98f130e56b9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 3 Jul 2020 16:55:47 +0200 Subject: [PATCH 22/67] [ion] simulator: fix magic number in display --- ion/src/simulator/shared/display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/display.cpp b/ion/src/simulator/shared/display.cpp index 28069012d..28994c8ab 100644 --- a/ion/src/simulator/shared/display.cpp +++ b/ion/src/simulator/shared/display.cpp @@ -32,7 +32,7 @@ void draw(SDL_Renderer * renderer, SDL_Rect * rect) { int pitch = 0; void * pixels = nullptr; SDL_LockTexture(sFramebufferTexture, nullptr, &pixels, &pitch); - assert(pitch == 2*Ion::Display::Width); + assert(pitch == sizeof(KDColor)*Ion::Display::Width); memcpy(pixels, Framebuffer::address(), sizeof(KDColor)*Ion::Display::Width*Ion::Display::Height); SDL_UnlockTexture(sFramebufferTexture); From a8c318f90f89b95855f1543fa036f17f3c024c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 3 Jul 2020 17:05:14 +0200 Subject: [PATCH 23/67] [ion] simulator: move background render methods to layout --- ion/src/simulator/shared/layout.cpp | 23 +++++++++++++++++++++++ ion/src/simulator/shared/layout.h | 4 +++- ion/src/simulator/shared/main_sdl.cpp | 14 +++----------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 4266b6853..c1cf7c01f 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -1,5 +1,6 @@ #include "layout.h" #include "main.h" +#include "platform.h" #include #include #include "key_layouts/horizontal_arrow.h" @@ -281,6 +282,28 @@ void drawHighlightedKey(SDL_Renderer * renderer) { SDL_DestroyTexture(framebufferTexture); } +#if !EPSILON_SDL_SCREEN_ONLY +static SDL_Texture * sBackgroundTexture = nullptr; +#endif + +void init(SDL_Renderer * renderer) { +#if !EPSILON_SDL_SCREEN_ONLY + sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); +#endif +} + +void draw(SDL_Renderer * renderer) { + SDL_Rect backgroundRect; + getBackgroundRect(&backgroundRect); + SDL_RenderCopy(renderer, sBackgroundTexture, nullptr, &backgroundRect); + drawHighlightedKey(renderer); +} + +void quit() { + SDL_DestroyTexture(sBackgroundTexture); + sBackgroundTexture = nullptr; +} + } } } diff --git a/ion/src/simulator/shared/layout.h b/ion/src/simulator/shared/layout.h index 6430d50b4..ed4a3f83d 100644 --- a/ion/src/simulator/shared/layout.h +++ b/ion/src/simulator/shared/layout.h @@ -14,7 +14,9 @@ void getScreenRect(SDL_Rect * rect); void getBackgroundRect(SDL_Rect * rect); Ion::Keyboard::Key highlightKeyAt(SDL_Point * p); -void drawHighlightedKey(SDL_Renderer * renderer); +void init(SDL_Renderer * renderer); +void draw(SDL_Renderer * renderer); +void quit(); } } diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index 6fb3e3e54..825089be9 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -53,9 +53,6 @@ namespace Main { static SDL_Window * sWindow = nullptr; static SDL_Renderer * sRenderer = nullptr; -#if !EPSILON_SDL_SCREEN_ONLY -static SDL_Texture * sBackgroundTexture = nullptr; -#endif static bool sNeedsRefresh = false; #if EPSILON_SDL_SCREEN_ONLY static SDL_Rect sScreenRect; @@ -101,10 +98,7 @@ void init() { assert(sRenderer); Display::init(sRenderer); - -#if !EPSILON_SDL_SCREEN_ONLY - sBackgroundTexture = IonSimulatorLoadImage(sRenderer, "background.jpg"); -#endif + Layout::init(sRenderer); relayout(); } @@ -140,14 +134,11 @@ void refresh() { #else SDL_Rect screenRect; Layout::getScreenRect(&screenRect); - SDL_Rect backgroundRect; - Layout::getBackgroundRect(&backgroundRect); SDL_SetRenderDrawColor(sRenderer, 194, 194, 194, 255); SDL_RenderClear(sRenderer); - SDL_RenderCopy(sRenderer, sBackgroundTexture, nullptr, &backgroundRect); + Layout::draw(sRenderer); Display::draw(sRenderer, &screenRect); - Layout::drawHighlightedKey(sRenderer); #endif SDL_RenderPresent(sRenderer); sNeedsRefresh = false; @@ -156,6 +147,7 @@ void refresh() { } void quit() { + Layout::quit(); Display::quit(); SDL_DestroyWindow(sWindow); SDL_Quit(); From 23a542e639d094c3df64a8a66c5133b7dfea66ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 10:23:19 +0200 Subject: [PATCH 24/67] [ion/simulator/shared/layout] Round instead of truncate when converting relative to absolute coordinates --- ion/src/simulator/shared/layout.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index c1cf7c01f..356679c83 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -3,6 +3,7 @@ #include "platform.h" #include #include +#include #include "key_layouts/horizontal_arrow.h" #include "key_layouts/large_squircle.h" #include "key_layouts/round.h" @@ -25,10 +26,10 @@ static constexpr SDL_FRect screenRect = {X(192), Y(191), X(776), Y(582)}; static SDL_Rect sFrame; static void makeAbsolute(const SDL_FRect f, SDL_Rect * r) { - r->x = f.x * sFrame.w + sFrame.x; - r->y = f.y * sFrame.h + sFrame.y; - r->w = f.w * sFrame.w; - r->h = f.h * sFrame.h; + r->x = std::round(f.x * static_cast(sFrame.w) + static_cast(sFrame.x)); + r->y = std::round(f.y * static_cast(sFrame.h) + static_cast(sFrame.y)); + r->w = std::round(f.w * static_cast(sFrame.w)); + r->h = std::round(f.h * static_cast(sFrame.h)); } static void makeAbsolute(const SDL_FPoint f, SDL_Point * p) { From db1874fdac3891cad1624ba4d7942c0582861ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 10:24:08 +0200 Subject: [PATCH 25/67] [ion] simulator/shared/layout: update the center of keys to centered the highlight keys --- ion/src/simulator/shared/layout.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 356679c83..009c0ded4 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -115,10 +115,10 @@ private: }; static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { - KeyLayout(185, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left - KeyLayout(273, 941, KeyLayout::Shape::VerticalArrow), // A2, Up - KeyLayout(273, 1117, KeyLayout::Shape::VerticalArrow), // A3, Down - KeyLayout(361, 1029, KeyLayout::Shape::HorizontalArrow), // A4, Right + KeyLayout(191, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left + KeyLayout(273, 945, KeyLayout::Shape::VerticalArrow), // A2, Up + KeyLayout(273, 1110, KeyLayout::Shape::VerticalArrow), // A3, Down + KeyLayout(355, 1029, KeyLayout::Shape::HorizontalArrow), // A4, Right KeyLayout(810, 1029, KeyLayout::Shape::Round), // A5, OK KeyLayout(963, 1029, KeyLayout::Shape::Round), // A6, Back From 1ad55139b493f33c1bc667397b57f8785e321699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 10:27:55 +0200 Subject: [PATCH 26/67] [ion] simulator/shared/key_layouts: udpate png files --- .../shared/key_layouts/horizontal_arrow.png | Bin 8616 -> 1652 bytes .../shared/key_layouts/large_squircle.png | Bin 8583 -> 2031 bytes .../simulator/shared/key_layouts/round.png | Bin 8906 -> 2429 bytes .../shared/key_layouts/small_squircle.png | Bin 8637 -> 1543 bytes .../shared/key_layouts/vertical_arrow.png | Bin 8616 -> 2403 bytes ion/src/simulator/shared/layout.cpp | 7 +++---- 6 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ion/src/simulator/shared/key_layouts/horizontal_arrow.png b/ion/src/simulator/shared/key_layouts/horizontal_arrow.png index a25d82e100dffee4d5f114cfe3643cb2afe935a9..4a75fabeea88d1e3e6720e658bd8226ad1711f76 100644 GIT binary patch literal 1652 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

kqlWe~HEB;YnXK+&_a`~9Vd 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 UYs2pqzn)4}l{6L0 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 N002ovPDHLkV1hit>e&DQ literal 8583 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 diff --git a/ion/src/simulator/shared/key_layouts/round.png b/ion/src/simulator/shared/key_layouts/round.png index a7911d540630f70478523cc054724f1b238d8960..cb94b177ef9cb802400a24a1d0614650cda7c546 100644 GIT binary patch literal 2429 zcmV-@34->CP) z%TE(s7{*_Q7Vj4j3Yr)|kjO#{E<}TFOiU<=h%4PH2LA}%8P{UG0-6{BfsIDN&}wvJ zP(fMfM%}a^#0a?X0wNR`^PG?N6DZKj%yeen^GhCBXu_Q5edcuL^juO@RaMDm%+AhI ze}6xH`0#-Ofq>A!zyJ*o57W1A-)LlHgns}2O*1ny6buGM;~fqMIh{^gvt|vsTrSGW z$`ZeGb8{&_KVPW0xR~q>yIDrI2*T&jpQ*dMTj=%c*Yxh)JDQ%J*5z*@FmAV- z%F4>9yu6$a9XdqYw{O?wZyAXtgD^HWM$ex=C$HB_40>4Wh0 z?OV0BwpL9`OEYcpmy*-z6xWV6m>p9FAs7sbyT7EQ#B#-7N@xpu3AD|Om?{X9lauPz zt5?;nTesRK01Jh-p^bn3{4s&!|6__E;4W!sXi&4WvuzuIg+g1=X0!AVNe_b0=TnP{ zir55Tq0n}(*PHAzBrgd4{r#%P<6#?sg{r8i5S}Cp!-Ft0Go#kk)u|4LgMINADm67# zcr!aYYj_+C4FVEhR#wKo;429p`Tc$)OJiIRnwy&~ITnyCVGN?FsmZAF=pO{UCY?ER zhJ9hFE(qvfK-iY+-s_Z*OlxQHVPT zLqkJqW@aY)BA7)kmrK;d&>&#Q6Qf<+%{QAcii=rZh6MrR-`q_%t56$5f`Gvc?q-`^ zEiEnj1|bj#h_6Sv8*O%>MzPLziaH3>)6;TNiG_q(VK$iNLBJF$?&exesF|igKsO`5 zO|+g+vuHmeq6$J~WhHx4tt!+`lOQ~K@`SyaRu|TeBoqX!!YM2)WN)O^RZvi{;)<$e zgV5O6$lgS|2+Ql@3Ic|ab8>Rn8)z3{t>tpFp_kkY=+>=U^!4jkbMNvYi5l!5b^G@1 zrT=@Rbl~$u_6GJ7YKnousC$G54<5+gz=oiv_wV12+^|ULaJ${??;g8q&z?QeJuJR? z^M<`yc2}f5ofdsGG&VMJ??G)(Lqo%&CeGVuaeRE7HgDdn+h&y#bAU}lzkmNu>(;Fc zU(dWAp{uJ)hJXR6@#Dvj=QVU*5boW($K4|~w6?a+YijtT0o&Sc+O$b*Xf3fnDJd!R z^XJd-cERBu;n}liG6amk{g3^uLxh9i^ZB@Y!wx(Xh6%k#+IhEAsmMaj=M5+g3=BxK zm?|`tO?))qzzYdaUcP)O+AV_c;>8OY1Ez#(FM`n9+soY#wuEY*f<+K%X=&o1D+xbR zQ&VYbYKjz`fF?u07t|gZP(FS7#N7+_;8;Y(@AtDef;%`PQ^5}jZ^TCfen@yDdITJi zE8)%P=qM>TCt1Q9WI(~f2MKSGffSsof&m2yUofDk*x;~uNrGTPQp()duM*}gxd>Lm z3&rVl%91k&>l_r!W|i;;-#{w=u6B{|WbN9uq-14f$?`MD<#Lgdlas^U2lixVXOohd znaSP=?rhnzg_QjKeC|H5CoeBg20=o!W5*6sa&vQ~70f|rpcEGubN7KgyLRpRZ#w&` zRjcF%8BD>b;^gEcDT<^OGpSh&gIAXHRTaQB5Bm6es@CWp7z!>+3v zH*SejRaI3u1hjDS>WLF4WcitJ^5n^R4PCS;cY1m{ z1%pBEKG~WTH|54ZoH})iyHB>~^y$;IXs)U}xPzY}9o!M|D4*TCce6Lku8R67AJE?3 z&fYM)s;#Xp`XI>T{_LrkkNd+@<+*d`xIa_bj`Qcwi&ae#i5L%6RVODW#WAwn?;^V> zD=RC$BZ0Ab!i5VLxZgy!;o`-Mgei$jvSdW&o)roU3)$aIR#!nmfvDwjEE@#i^?KPG zX?3B7E3sk_KxJhmdo!&n)GivL3Ie7xIUEl5rdmy?S!gPnCOrbUeEBkaW38sTy1Hn~ z8+{Nk5K&fE#@<}32(?;j@F<}m0KealLq6D>Y<8hWfj}UZ(#949(A3n#-e|L{rKKg- zvc?_+P*YRG-fXi9wTXwgf`FmQl9CekrkhQrrKQUbBQHl>K>$NTLuz_@I(zfYqKu3T zQImLyI|!hsr$({SqRX**4pvo{9s`~nREla0u5LEdwVThWWo3$>R_CWxhot^S+ zLI=T9c&i7x1OfE*^~rGt4FxaZsb1(71TZ)_C`TYQ5Jn8)rJm>-1TZl%A-|hQK=3Z! zVdGfPJqRK2fa7#J*%vli!7F^zYZQhBL7kh=ASbu1EO-R3jKt6&sB<%&Fzrpwfm|%S z6DW;i4T48t)LLl)TkB5EnL#n8#h=GxT3cI1+sue5gYYld)9LKlvzC0zz%|#_)<)d3$ZVKC2qDJC#zZesU0tmz zielR0=VRhYNWqE-0y>lZzJ`joo5x~QY0gNBEPlX~SiK=bI)qvY{;=*W>H zv|+;rE7>WqWDpk8-``K&-QD!+)hnU*@89coMiurjE-5LY{rmS*d3iY2UJsCw{7S}x(W!=L7G595=!Vziqa8KY6t-$B_V{~5kUb#LI;%&A_xkCD7{OU zjtB?>(wlTcx%d_D_wM`eegC-QWsKzPv-jL<&9&CtduN>U$Usk%mYR(k007WxYpENZ zUju)ADap@&<3d~n008D(l$x4>I?4m%0`~;~%v)Zid72D2y=z<~Oxb_Vuc@g~`*0Jc zac|jyiV>u&s%ogCW2h{wp+Utctin%9blJp&;j)1VDHWA(wBq)`bnxa=@BXySpgsk# z_eQ{GkN5XJ;zr5kE1aA(LjVRQHjQ%7;DS5WYqqJDfpth&5rdlI#?%y1IT*n?K_XZ$*kXKPbG%`y(=nRWFJd5Px%YhbxNu>w<$M z_{m2)ye}r@&|5D4J4K7)5`29NJfR4gwqzv~EY|6%d^CsF)t)APut zvv=X7nrG{34=uM&`4JgYryn<6c!hdg;?(N)Elw8zn@Wo7<-u>?oY-8uvGV>HiPjIS zsH5d6xZHptp2BKl@m}BaTl$lM1l0b1_#N6pZ^gML|6SfZ75|LN08P zFmhAPhcFpXOf&*giEr9cJPl>%CJtsG)|z2>NI}I-W*<_)O@ArW$bjx;qo)CVy0YjJ zDSZ>bhxmD@nJskyAhU_roGgQZ6wXboPX9)A@(DLTU8jo3lWX>XG&QLw;CZ4Wm23mZ zCHiOKl36YQ+0j&Yi*`}EC0 z7}Bd0f=!My#MLzQ#C+a?D10{LGutSn5yw{Efpmaw|Vkhe3 z(bi`tE$k#jDm*N7UAR`5&e-12#Ne#~jd4KMcbDkC)KPqgAp55CjJ#;$05+G1daz@;6i<=>wa)Oja_0lX& zcKTMxR>W&P4SfL(r0RmM=hK&|FXQheu82a(D!D7^D%m}+dMfU@uE1Bw*r?ff*c90G z(-Dw1h&QSoW$LtyLZZY_YEEF3vlGE-aS4c1#gXHtaoV^A9Q~5R665aiuHUZu&xGB} zy9&Epte|%i5A#LqkCBJTH_4O9PsvBuUaf7f9jHz5jr5T|3O`yqVmfl$s{7utUA#s3 zvG~J#TY?&M!JM-7!Z3C1r91``#$6V9iE+{DYy8*eg%U-E`5}Tmye_;~1uaG8g;f~_ zm>w|*U-T7;V!q8t%T&kxQ=Nv{_|guGC^I{AWLr`zNvp)eIIS#x69H9$F8+3bZ9EJQ z!dK#ljBXiyH+p2GYh;s8mS>gMuYI6%hBr*4N-lchD{xcLStLW~3vUg-;5E%e(PV!S z{U_3iO5$IypINC`Hfhf0G>7xnuu8sv&zFP^A?<-kH=W4QX2t|dH<9XGZ+yc@gXxx}~ocz0+ol4gl!jW&vA zn)Sz(pI1iN#My+|I6y1F8&@)}WP&-RXCXZ4CK=b$MKU;{>ryQlpVKd=)qz>Sf@$x} z8<`83WtkHQWCxKO=yKBJYss?7Wv{|t@s@1d$$j;zB>I)o#K(!&FS`?iUp{;p9WR|& zo=S0u7$a~W8SZnV;g6br$gyW zqe_KK`{}8KS&2=X8_vKyG26x`*tQ0m8&P&`j*&7}-{%A~87%@0M zL_HLg?_YS}_|bme(FMr_Cg^uR7niB?Xg}yNNH!X6t5~YYcFfEif|(`hW^kk^Bp~94 zQo9o}o)h@`70M)Rcp7%)Hv)E#iimf@zN>~dS3I!LIMwKn@{C3&ViNO`O02f(fqsSr zJ1b#F)Q#o#{E(X$Qyz;;CsQRYq~vxZx|B?1Om?je-x~!ct>0@OS?~JPRV0$}a^v2@ z$_iU-ddb~tsSjSK>8q83V|!)NW7?xu^-Vawxog!|0(XYQ;w<`Cdsk}QiYH5J_Znh{C`6s=qebshwWz(^8b^<@)H8D3~25XjM zhMmA1KTOtGD`5P#-hTPu|DvO0aX_sllHSkL?+X`7{@%LrYR#9j!LN`k$S0W>GUHq3 z+wLt-1`Xz3|G?~A91#@bBixv{QF%kIW4D7-5$X%uayhGrU)=8(Y`5wtyjCmt-I}!F z@Ko~q!uR5f2`@T##~@`pUoo}9{-Qtk+Y<+ePf#bzP2N?T*1ZgliVblA4SPJxe6xtt z>jy4-PbV!`YiExW^eGer53ip3dhjenM#ihj@=n64tjaKHiel?osxh1;Iqg$^OV~Wa^D#%XzwUEnJ|Au5DMXD z86RT_$=3l;_5nUA5dpks`mg+KAX8BcXsjvp`B9WbB6+{Q$(NEP@0t#g>%;9P>-^M$ z;YJoCm0=#p=5p<_5|}Bi;7;wGudgjI5Ue{k!RM||%HeymL^99tnD9OIVfD*x>aFkj zodn|X&PE=C5=e67mxYyu?#tzuFD5l7(Z6IZ7J2DjlsnR}!9KS1sboN8uy0U%On0dJ zQ@`X#sUA=lv=7_~?uE)Z2DS4d&V;+|ixWl#UGXmZ69S(FW%*&(WH`JPzcKl94?=fs zLo(jU4YYKCKZ>TO&IGm>j=vk+tO~Yh%x#R%N@)V~T*xk-eMkwz+veR%y#3auXFzSy zPL%##D=kM_tPLzeM$aQmEL1T5jNgVyUs99$rbvtM3d9m787PNrI(|Zy^~AvtU9Y<%h1rr&0t zQMwMRLS%6=KOr~xhZw)pn%Y#cTupyUW|`@G$>-##{IHmejWpvydN}%#$)n_`g=j~u zs|hhre&DN8K=|7!35koQS+~P4r6d+4WxjY@H2PwpICGpO4_;Q3FHq21pq3v!JT+=D za^?VZlyQZ?+ScAiaSuySVHMV zYC1ne`#x2!;iHO5QzjtNV zyitoRoHP9f(!3M*h|^3lJH_{ij9?Ve(z&EeLL)sI@$HfugM^M&)=w?OO+u=5 z#;jBVsRxAET=H=X2_oAs`JpzRDS^yv6Y13ROFOHWW$cpsiU->k>krm#R*y7HdU-l1 zZODA)0C64O%~342s+&RkMXW}QAQq|Hk*QbQcT?PkzNe;M>2P#Ci4fl5(eGZy(mFOh zqzJC(I(9f&B>qmac>jBtqnd--Vsm|X8Gc6VgHWhbK5K#+g`FB*M+cHq_)(ecg!;5D zL63%iSHLT?HpL{I>*8n7-gK*b2e%CZmpSG8d8^9309AD~G$kPxeUN-Nc+v)C;cT;NzG}I7H~95*-E_r1rqX+10Q>de z6is47uB31(0|CqL*<aPdh2Bz)7_Id~2#Ty3=^=;mA zFqys(m~}>}DBh#jZVOhjd6GPq@8!5FFyW8;fFs%6-!-Pm zrfFd9m!y)cl#~NXRM=ODyso#EYZC8L>~arJ$n*#ijmn6w$y@}Mv2HroUk&mgicx-N zi+>Udyiu*suQse2bHn9UtwFP~kt)_|B_5Z3>tRX#)xyb$FgjOd;?=(Yi0**H z?3sY2JpKIp{Th{xdz_%>wARJb_Y|q>Nltr+f%0Gi(pE=h1;r*Xm97S3r7_l48;s}fJyYuy1JAEGZdb($M^f32Emm<{+&b7vMEoy zNxOnAea-I}aTq)MTb+{c^gjD}BzN&KNiY4~4|n~)w8I=ejQTtI3lOf8^K!D#9mE{_ zi2Rrg%-LGl8KXhF7F0O4D@yji4}N)P{Lo>$&d%3y#I+7#>_eE`+@zpp__)1Do}?dXsh!{xB!mXFL_1v zc*Z94p4M?lZID{1F(iV{<*rgW@hxAN1h7DTMsP*K7%6WP*eur1`;wlHH{ey z^$IJH!%s(Lc6ME~q%`zApWit>2y5oO|83#ILjHcr8{~m#PN|M*pPAVhxm8M$MZ;&S z%)pKs!@9JijnTRX1dsKvH4IZ%=|?!ur4Mg~;-CyL)s-KX5 z=eci2zp%|jgX{I8(}y;JTM4h1%dKiyg0D4_w1)70B6nz{@*!c8q3a7_!qC1AQ`uq* zxNrNAk?8@1$3lofCnPFM?i27WV9wTkocLOk28O;iW=2znTauBU;fvN~dzx~__pwHr z0$D#l?K*kydhSZKQ+tqAJn(FsF38G#n3LLw`c`VdB!mzu+;TW{2xG_&iNcG--@6Zs z@JqWE<&d;b$#E-1uV2}wi@WJlvs3$FvT%A@)cc6GROItLNQV3u(S3e8RIJ(4BFiYt z&G$RLJwEVh(|YqD--*Cyhp2Vvs;6tHLuOI?Tf&m07rg!zEoZ$vb5{@6OV*3 z69$7kLscPH5%dd}qNbHZf;l7{I`X#UEeQ_qDSG~}!;$vQ4ZC)SYdi&xz3DxL`*UI= zuT)FAU92ZS6JKESFhy?%cUjC#&Z?X=;{hhlRO0Hu2%$h?w7D_NC9c9BDtW~G@VP;J+s=*25v%)hrRWrQ5Hm8gSf1BM=cxI;e8Dp_ zPH0}t&lK-&c*cZ#Um<@KhOWrEp{08uw9~Hdy-$LW{+x zo*(HxmvVNXa1>00NOFB>FGIt3{?wWZqjkpv01!WKi}Mlrk~qJ8%E$mf+?71A=o_;8pqM zklS3>fLjfXMRG&MrNj^*2$Wk|RvaV+hRDiZ=avM5WF&wx5+JY`ND2l4!XzcR|N8Kr zd&Am0z>L&y{N?NXPJ!16hr_@mB)q-7#l6AeXsn|INLE%>0w^gVDJgc25cBYL!@+&T z+&uXHwxEvmKwwcA9188m{mUZU4(*9k;5}#hX9%vC-)7xB{z}t%!X$j)7zvO#@K;EG z0PPXKaTrgm%OB3|5fVrjq$|=5=W&h&{l;RP&^WY*6Z*d~{d@TTaB!YlUESY4{v{Vz z*WWHYa2j6cVf>Yle~I=m@x>q|jF28^Pb>nd;dM@v?^iS!m>L!d$Dy$%Xtc}Ug);b? zGPj!AufoW33+ciUD7Rm62>)XiQXP&%D)9a)ofr@-29z-Y0b%FW14@emK`<)ivlJ86MC*2dpHjM{{-73U=C=kEBrialq=j3DS>fw zS%=Luj>Dr|K~!m zMb_N=ybUD|^mL%1%ty!9hw4gp`$n+RK2>$DqFy|A&ZRS}2e6^7{U@ zhK!N!f3;jt+<)i>21oo_4+^}9UqV2hGx)O`^)K@AZ+ZS}zPA(d-01(HjX&LZpdE1D za4b^A@jQ3`o&Y8Oy9hksUjH+Hh%6K#E$!eS2C~44+eV7uSz!D@xm(-0Ec~Kq=c+L3$>5OT4ntyLma-~LfK2t_9G6Cj zs(9>rrMF{!e1S@9ja-A*VmY|tj>AJeef|Ef@?(#HfB*%y#~X36v8SfTI1FaXFlF!T z=d+-)%_aYzZsZz7Th&5Q(bQQr^8J})=6j2_C7Wh*^7rH)7jLU%Q^3xTpfazS_1W3k zhLe+%EN(R7ahb1^VmiwbF2LT|`!;dV6{dODk|Wgf7h%kr-ue246Wad%{#>B@bv@~2 z-(MZ&60d|R6vcM?<|}37N&T|MH_?1 z{H8bwNp1ef1y(e6&H1EsQk+alKCG?>f8W=+K`1KicHPvCuQ$c7(Lx_c6Zy`S21_EA zl90a3x4oqY_V8va9Y?zNnvz(<{`N2 z=cqoD$BlG{%>3Z5znPvD!4Y9m8)5aDl#=vnWzeP+qnfaueQj%I=n!fq;%OBQyYXRS z!Hg2Z;r0w!{DOYe4?h~P5txK>z9dd!K8w}+Nbc< z?pjrMiq{0h&fHqtmt~eqKE4|r7}a>|&X1v2S;d~@&E+i@H8e ziJhs(Qw6yvWISo|-)u&$GkLj(SU`)#0Xr7qhzZArwtEL|ZQY@QYb(6h74t`-UkI3`xW-4BpSzmOVN_DPg@yCu_S^)XZUY^2imwW9&}Apd&| zoyXz}Yj<-pHG!8c)Mk^=NH_K@5*W=AFsmhm?jMaw!KKz|j`r#INDrUx_P`G<1e~Pf zRfoPOe2YNMSQqoi&#&j}ybEIe&g}*VDXG=N$SI$M{UhWv43$yZaQx z^@uR#5wT%Z<%{$#YGc&rlFiU0tSDlu-SjLgkvY;O3eywp%%aneM(S_v`b&E!eW?2? zCn1;m!M5n^20OYsb-Q=9Znk`Uy>`@|Zw=Pz#e-d>nQva#sclT%6-VzA&o$!(QIaF` z9R=Y6=t(;p&DzZ8lWjUR2SdTF^F?9W`F{DLMr+J0;z2B~>w73;ktn+eOm1Ir29oC? z_1HsEL$&w-rII6|S9{vb_^$HW`96i`HQ3qsJ(Z->YLz9!rR|IoeYVFp=qZCfjsH{| zl@?yP50`#AI|^GFZGYXpb9|ZGPfRs{)~$MkY7!7o7LthsF*M4NT%CDFOt+;+bcULQ zk9gn9*Dt@MwLuyb;I9+_d{^Q+ANyo|BYR|;&y~k+&vJ0ML4H{*Irh0`aHRZ(qy5Zr zpAS_sfHA>Bvv0~<&tiAOJSDG0x9o%U>c(|vXp@zasa<`F4%;i^Ro%hM7vM`p`&}>V z$3DDOOo+IvIn)2@D&cPAD9QL<+)2@0$2{iAfdDAFHxp6&tTLFzzXsf^zk@?QKCs`; zEZ)iX8vOLrX3m*2U4Jo2;FI6wtkU)gs}%(2&urpAn>i=>$K3-Q@gkqt1+dZfVxouH qTLpmP?w`RYdJSXVy9&;l`@wd8N_As4JUzeuzH4jfsh6tShW!V^A5%vF diff --git a/ion/src/simulator/shared/key_layouts/small_squircle.png b/ion/src/simulator/shared/key_layouts/small_squircle.png index 9dfbf335fecdad1cb22c9bd799c39a29169308b7..32dd991a1d1778bef90938b9e13efd79dbbc31d3 100644 GIT binary patch literal 1543 zcmV+i2Kf1jP) z%S#(k6voettwC(-8?*>r#F|=bq8k^2F-pOWkfK(xxG1RYI=ZcomHr1pKoMVyY(xx{ z)I zYPDKOOiYaIF-8i$)9Hk%sVSJ5nStr)X;@xfj>KU^R8O8ffu~QOLS+6pO`kikDWIYEoXf`x^Jhgcm-$&9}C;k`F#w+|<-0F$p>R-Q8WR zsHh+xc(l2^yc|6qPw3!pZ*QYkt0fbBzlMgml{QUWIJUu-P9zMH@*wsWm&{%V4XJ_#6TUuJk2N|n=|Ned8@COD4 z$Ojn9X0L((vu0mi!7Pw6*;e|&t5#l^+sgHwvxudIHK4EeAgvH?#7Kcw* z96n)j_=F{fz0)&SR46_^9>mntR8E7Sl9Q7`%I+}vCcbvhmO97w95pa6LFE>W@SU0%0OxNcv1$@A>lGwKV0l-NYCxVSjr@CmA_ zs_OSi^If|1{N>A+^If{4D1ymkqP`$VsI06Eva_>)pQ^7*1N+ubeMM04@bED2$wSes z9qa!7KJUgw(N}ikVtVo71^JMa=xh7%F|pB|yaT85>{ka)CN?0EdRR(wdV0Ely?2p! z-Bz5fR_nho^M3nEcU@iG{}5k2X``>Nk9>Hs?8L;xz$R_@)ZE-mKEzn{+qZA8WXq>( zDbyVu9oM1dbM=(ynwlDU=IZ&xCTiy9=8_LK+Rg6L7u%}K0w2$2=@t|ekPkRo&1UJc zLD&9@PtjJ0*%a-6y~w#r0ng7jnM~vZj~2gt`O@!O{Y3=?-=~q05fnv{eE5+x8-o|9 zf`ZRvx7$&t(~*Y|Zl|QA;JbJ4f?^jmd_4OYU_S;4fM;E^8-Cc{-Y!*Chf-mI?~~K%#NOUsEG;c15BRcK z>0EVnwKVQLkZ1NlB(T4U#AXGj(+M9wd;pux22)d0V7J?eZJz+h&(D|k%~e)bg27;b zgoK2!{{In!e_0-n2iV9Lhr=OFfnY-**4Nj8%>&ro-371L8x4nfBQcn)_f2MICaBeF t&}cM}lanJgM$l@taOcjQ$Q>L2`~rtN(?CJ$^nd^W002ovPDHLkV1kX`4n_a~ literal 8637 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 diff --git a/ion/src/simulator/shared/key_layouts/vertical_arrow.png b/ion/src/simulator/shared/key_layouts/vertical_arrow.png index a25d82e100dffee4d5f114cfe3643cb2afe935a9..ef86bfaf2fa9735789e1d08d16059bef2c1573f2 100644 GIT binary patch literal 2403 zcmcImi9b~98$U=i3|Y#)#4RN=L-uWGEMqK_EY}*1U6>JLZ>&iYB}uYPmzyTCWZ$=o z5=kO^x@Cz8g|RpHjQVwdzdzu2&gY!xd7tO|e4qDupZC0InHWh%;wkAOaQ`h9}}eVfaAl zA0+?b(RUAY4e<6SdK2)H3|^cIA&96gCB;be>-sTIqPNGtnec%>%K{5TFgyqaxIE$) zHyEnLuxgwO@OB3?Gx(7TTKmZVvHi@Wg&sV)m zi6_M>pMU+>&AmXS62+UEn$~Dv9JasBaFBLr$tO;nnBQ1_k9p@m)02KTGZPaQ7FOvu zt}<2^DD!2kDv9=Gd`@flsY3=)sM`6k9Va!4{{1`}%~n-i{U#rMOVn)~3OjY`;gct_ z8l-Kd^z`)eO2Sn+XCk*+XK$30z=QU-gVNK|;+nh%#Gl_(jOO(lm5CrB^od~cvIy~$#G!CJ?%s^Nh7<4|<%}q*K z@7f-}u3O&S)%7-6MD^bBZ;oqYWA@5Or1;~oizEHH+Lqh?r(tBM*4XOq)@o}jOB>$H z%RJTSx2I&N+St&@h#-5Y@kmSLo`nFy$<)Lx&M(*J(xtbbKDimsHnhU`l2b0Tehb1xn4;R_~_nsgg< zMf^8I6AMPp-MD-|OYiiB;h z;1+h0IARqzvPL&@`cd~f2WXevc= znaxpjtbYn^ZH=8R4#>_Ykw~1B-&nI*vnX5#vJMG*ucUUIMEab>Ig?vj@}zv;m00KJ z=hG_UwK}%f7f(HN>4Z3vIo0F$WXma%4?k5)*x zs=lZQk$-fj=cunCQ$#CiZ$1N6!jxz@J6AMRM8(`HN?o1d z@Gq+&ycY+pfEs$epj8yCP_O?X=Dc<(ZvL1WAUek_fqcGEX1^=`d($zo_qQZ;uq0>{ z7Ml|7aG<+aoPF-*?EFB!XoQ7Vpp~_?x?e~cxaoxK*;B4bXa@VrLVI?=UhX$(I_Z9H z;iKYW(^gn?$I#WmWO7%3o=$%`Iv(CFXOmyj(&YG=N0+h$6p7^O>FHeyUGO?E^N5>X zP|q^840o*69UUE2G|BOJr$Y=5Hg;#T9i5z49A5b}EhHqg z(3hoJUVT|d=C7`z<*BCABjWXZYR8ibMr?J1f~ulrfR=z6<1?OfbZs1cYZaZN9%$Hl zq4fi^1meRX5e{Jq37h+4_S?&u-$g77J34*9Z-+S{Az^5$DT39}rncE0>7(Dzu^C%d zuFEMTuI4ZKcG@e7`kEr()#%YSvYDTn%5!9x=`^Q2j(dZzxh$2P0KsV*Qz(?LyB_n^ z3RhgS;+0iZ3-=PavD60-9+;+)bP8`K_o@v}v_wVO(>{7-WEM=-e4|GRva8&S$N$;f z?DW*WN*4BdfUB~5QK2#e$|G2fdE~ifHTNpVVR(A`MlDJ+Hl?+^5AwHhXMm=QdgN|x zKtsFB*xiK+W#0-Hgj1vWw(RPiCc&6-Xjx?Kh@WP$Yu3T(=DQ9xc*>svbJk&Db%Qdl zO|??7`r(an%JP=0dpjGS+v3>XPEBFAeS50*>@bDc9I43*=1xwz>)C;PauC`? zdMN$7c2H2z0C>t7akr*5pUqqAyRM|BW)x0NSmSA%&`I2>5pOIkE|zOYPgmn7jagY) ot!!2Q*w=zxNO&AxLzRFj|5bGZK6$L8=005v;RZ-L- zoc#%h)@3rnKeWnc003YtwpCEjQnYn-c0hRp07f09>9$b)-O27Fgs0oAebefC#+vu- zeMtMVt-K^YT{<=1WZ?o@&aenFeuh_C{P7$LIZRUP7n0jKH92RieiNSG z=Gg|N?X#;7i5A$tKZ%S~GzR47FRyDdc0p+#fLRwW0fUIh7D%OAgX)>N$jMCsL9LS) z5R!+xq(A`R!OEGXCFkOWbxE+C3lSjV65t1U7pWo

kqlWe~HEB;YnXK+&_a`~9Vd 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 UYs2pqzn)4}l{6L0compressedPixelData(), reinterpret_cast(pixelBuffer), From c5add52e61800ba60fea1731aaff1c7eb8bfbcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 10:40:44 +0200 Subject: [PATCH 27/67] [ion] simulator: move key_layouts to ion/src/simulator/assets --- ion/src/simulator/Makefile | 10 +++++----- .../key_layouts/horizontal_arrow.png | Bin .../key_layouts/large_squircle.png | Bin .../{shared => assets}/key_layouts/round.png | Bin .../key_layouts/small_squircle.png | Bin .../key_layouts/vertical_arrow.png | Bin ion/src/simulator/shared/layout.cpp | 10 +++++----- 7 files changed, 10 insertions(+), 10 deletions(-) rename ion/src/simulator/{shared => assets}/key_layouts/horizontal_arrow.png (100%) rename ion/src/simulator/{shared => assets}/key_layouts/large_squircle.png (100%) rename ion/src/simulator/{shared => assets}/key_layouts/round.png (100%) rename ion/src/simulator/{shared => assets}/key_layouts/small_squircle.png (100%) rename ion/src/simulator/{shared => assets}/key_layouts/vertical_arrow.png (100%) diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index 2d16076e3..364a709dc 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -26,11 +26,11 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ timing.cpp \ ) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/horizontal_arrow.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/large_squircle.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/round.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/small_squircle.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/shared/key_layouts/vertical_arrow.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/horizontal_arrow.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/large_squircle.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/round.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/small_squircle.png)) +$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/vertical_arrow.png)) include ion/src/simulator/$(TARGET)/Makefile include ion/src/simulator/external/Makefile diff --git a/ion/src/simulator/shared/key_layouts/horizontal_arrow.png b/ion/src/simulator/assets/key_layouts/horizontal_arrow.png similarity index 100% rename from ion/src/simulator/shared/key_layouts/horizontal_arrow.png rename to ion/src/simulator/assets/key_layouts/horizontal_arrow.png diff --git a/ion/src/simulator/shared/key_layouts/large_squircle.png b/ion/src/simulator/assets/key_layouts/large_squircle.png similarity index 100% rename from ion/src/simulator/shared/key_layouts/large_squircle.png rename to ion/src/simulator/assets/key_layouts/large_squircle.png diff --git a/ion/src/simulator/shared/key_layouts/round.png b/ion/src/simulator/assets/key_layouts/round.png similarity index 100% rename from ion/src/simulator/shared/key_layouts/round.png rename to ion/src/simulator/assets/key_layouts/round.png diff --git a/ion/src/simulator/shared/key_layouts/small_squircle.png b/ion/src/simulator/assets/key_layouts/small_squircle.png similarity index 100% rename from ion/src/simulator/shared/key_layouts/small_squircle.png rename to ion/src/simulator/assets/key_layouts/small_squircle.png diff --git a/ion/src/simulator/shared/key_layouts/vertical_arrow.png b/ion/src/simulator/assets/key_layouts/vertical_arrow.png similarity index 100% rename from ion/src/simulator/shared/key_layouts/vertical_arrow.png rename to ion/src/simulator/assets/key_layouts/vertical_arrow.png diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 2e88849ee..9d4afe31d 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -4,11 +4,11 @@ #include #include #include -#include "key_layouts/horizontal_arrow.h" -#include "key_layouts/large_squircle.h" -#include "key_layouts/round.h" -#include "key_layouts/small_squircle.h" -#include "key_layouts/vertical_arrow.h" +#include "../assets/key_layouts/horizontal_arrow.h" +#include "../assets/key_layouts/large_squircle.h" +#include "../assets/key_layouts/round.h" +#include "../assets/key_layouts/small_squircle.h" +#include "../assets/key_layouts/vertical_arrow.h" namespace Ion { namespace Simulator { From ded2174578a78a7e8b82a7fa88584bd5f5901e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 14:54:03 +0200 Subject: [PATCH 28/67] [ion] simulator/shared: fix macro EPSILON_SDL_SCREEN_ONLY --- ion/src/simulator/shared/layout.cpp | 8 ++++---- ion/src/simulator/shared/layout.h | 4 ++++ ion/src/simulator/shared/main_sdl.cpp | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 9d4afe31d..8d66a1ea4 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -14,6 +14,8 @@ namespace Ion { namespace Simulator { namespace Layout { +#if !EPSILON_SDL_SCREEN_ONLY + static constexpr int backgroundWidth = 1160; static constexpr int backgroundHeight = 2220; @@ -282,14 +284,10 @@ void drawHighlightedKey(SDL_Renderer * renderer) { SDL_DestroyTexture(framebufferTexture); } -#if !EPSILON_SDL_SCREEN_ONLY static SDL_Texture * sBackgroundTexture = nullptr; -#endif void init(SDL_Renderer * renderer) { -#if !EPSILON_SDL_SCREEN_ONLY sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); -#endif } void draw(SDL_Renderer * renderer) { @@ -304,6 +302,8 @@ void quit() { sBackgroundTexture = nullptr; } +#endif + } } } diff --git a/ion/src/simulator/shared/layout.h b/ion/src/simulator/shared/layout.h index ed4a3f83d..86c599b01 100644 --- a/ion/src/simulator/shared/layout.h +++ b/ion/src/simulator/shared/layout.h @@ -8,6 +8,8 @@ namespace Ion { namespace Simulator { namespace Layout { +#if !EPSILON_SDL_SCREEN_ONLY + void recompute(int width, int height); void getScreenRect(SDL_Rect * rect); @@ -18,6 +20,8 @@ void init(SDL_Renderer * renderer); void draw(SDL_Renderer * renderer); void quit(); +#endif + } } } diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index 825089be9..0be773c18 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -1,9 +1,7 @@ #include "main.h" #include "display.h" #include "haptics.h" -#if !EPSILON_SDL_SCREEN_ONLY #include "layout.h" -#endif #include "platform.h" #include "telemetry.h" #include "random.h" @@ -98,7 +96,9 @@ void init() { assert(sRenderer); Display::init(sRenderer); +#if !EPSILON_SDL_SCREEN_ONLY Layout::init(sRenderer); +#endif relayout(); } @@ -147,7 +147,9 @@ void refresh() { } void quit() { +#if !EPSILON_SDL_SCREEN_ONLY Layout::quit(); +#endif Display::quit(); SDL_DestroyWindow(sWindow); SDL_Quit(); From ef363c57acec285f8e58a5e2a0909133fc85e4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 15:36:54 +0200 Subject: [PATCH 29/67] [ion] Reset the highlighted key once it has been drawn --- ion/src/simulator/shared/layout.cpp | 4 ++++ ion/src/simulator/shared/main_sdl.cpp | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 8d66a1ea4..4de6e6e43 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -282,6 +282,10 @@ void drawHighlightedKey(SDL_Renderer * renderer) { getKeyRectangle(sHighlightedKeyIndex, &rect); SDL_RenderCopy(renderer, framebufferTexture, nullptr, &rect); SDL_DestroyTexture(framebufferTexture); + + // Reset highlighted key + sHighlightedKeyIndex = -1; + Main::setNeedsRefresh(); } static SDL_Texture * sBackgroundTexture = nullptr; diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index 0be773c18..c4085c17c 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -129,6 +129,8 @@ void refresh() { if (!sNeedsRefresh) { return; } + sNeedsRefresh = false; + #if EPSILON_SDL_SCREEN_ONLY Display::draw(sRenderer, &sScreenRect); #else @@ -137,11 +139,11 @@ void refresh() { SDL_SetRenderDrawColor(sRenderer, 194, 194, 194, 255); SDL_RenderClear(sRenderer); + // Can change sNeedsRefresh state if a key is highlighted and needs to be reset Layout::draw(sRenderer); Display::draw(sRenderer, &screenRect); #endif SDL_RenderPresent(sRenderer); - sNeedsRefresh = false; IonSimulatorCallbackDidRefresh(); } From 50031c48366694c3b2eb8e4fb617af813ebbdbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 28 Jul 2020 18:07:04 +0200 Subject: [PATCH 30/67] [ion] Unhighlight the key on mouse up event (when you clicked on the key on a smartphone, you want the key to be deselected) --- ion/src/simulator/shared/events_keyboard.cpp | 6 ++++++ ion/src/simulator/shared/layout.cpp | 4 ++++ ion/src/simulator/shared/layout.h | 1 + 3 files changed, 11 insertions(+) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index 806372e19..68cda4c51 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -1,5 +1,6 @@ #include "main.h" #include "platform.h" +#include "layout.h" #include #include @@ -193,6 +194,11 @@ Event getPlatformEvent() { result = eventFromSDLTextInputEvent(event.text); break; } +#if !EPSILON_SDL_SCREEN_ONLY + if (event.type == SDL_MOUSEBUTTONUP) { + Simulator::Layout::unhighlightKey(); + } +#endif } if (result != None) { /* When events are not being processed - for instance when a Python script diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 4de6e6e43..5d137258c 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -284,6 +284,10 @@ void drawHighlightedKey(SDL_Renderer * renderer) { SDL_DestroyTexture(framebufferTexture); // Reset highlighted key + unhighlightKey(); +} + +void unhighlightKey() { sHighlightedKeyIndex = -1; Main::setNeedsRefresh(); } diff --git a/ion/src/simulator/shared/layout.h b/ion/src/simulator/shared/layout.h index 86c599b01..3999e6b7f 100644 --- a/ion/src/simulator/shared/layout.h +++ b/ion/src/simulator/shared/layout.h @@ -16,6 +16,7 @@ void getScreenRect(SDL_Rect * rect); void getBackgroundRect(SDL_Rect * rect); Ion::Keyboard::Key highlightKeyAt(SDL_Point * p); +void unhighlightKey(); void init(SDL_Renderer * renderer); void draw(SDL_Renderer * renderer); void quit(); From 652d2e6bacfbf9098d3e59679e5b9119580a35ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 29 Jul 2020 09:44:28 +0200 Subject: [PATCH 31/67] TODOs --- ion/src/simulator/shared/events_keyboard.cpp | 1 + ion/src/simulator/shared/layout.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index 68cda4c51..87555116d 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -195,6 +195,7 @@ Event getPlatformEvent() { break; } #if !EPSILON_SDL_SCREEN_ONLY + // This works but it is selected right after. We should highlight on mouse movement? if (event.type == SDL_MOUSEBUTTONUP) { Simulator::Layout::unhighlightKey(); } diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 5d137258c..bbdf97d10 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -246,6 +246,7 @@ SDL_PixelFormat * sRgbaPixelFormat = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); static constexpr size_t k_maxKeyLayoutSize = 130*130; static constexpr uint8_t k_blendingRatio = 0x44; +// TODO: use a "native" image decompressor instead of LZ4 void fillRGBABufferWithImage(Uint32 * buffer, const Image * img) { KDColor pixelBuffer[k_maxKeyLayoutSize]; Ion::decompress( From 4ae39f56fd6d5e0e9f4802634129467fcbb2bd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 4 Sep 2020 14:56:54 +0200 Subject: [PATCH 32/67] [ion] Simulator: Layout uses jpg images instead of image built by the inliner --- ion/src/simulator/assets/horizontal_arrow.jpg | Bin 0 -> 1530 bytes .../assets/key_layouts/horizontal_arrow.png | Bin 1652 -> 0 bytes .../assets/key_layouts/large_squircle.png | Bin 2031 -> 0 bytes .../simulator/assets/key_layouts/round.png | Bin 2429 -> 0 bytes .../assets/key_layouts/small_squircle.png | Bin 1543 -> 0 bytes .../assets/key_layouts/vertical_arrow.png | Bin 2403 -> 0 bytes ion/src/simulator/assets/large_squircle.jpg | Bin 0 -> 1569 bytes ion/src/simulator/assets/round.jpg | Bin 0 -> 2086 bytes ion/src/simulator/assets/small_squircle.jpg | Bin 0 -> 1456 bytes ion/src/simulator/assets/vertical_arrow.jpg | Bin 0 -> 1527 bytes ion/src/simulator/shared/layout.cpp | 120 ++++++------------ 11 files changed, 40 insertions(+), 80 deletions(-) create mode 100644 ion/src/simulator/assets/horizontal_arrow.jpg delete mode 100644 ion/src/simulator/assets/key_layouts/horizontal_arrow.png delete mode 100644 ion/src/simulator/assets/key_layouts/large_squircle.png delete mode 100644 ion/src/simulator/assets/key_layouts/round.png delete mode 100644 ion/src/simulator/assets/key_layouts/small_squircle.png delete mode 100644 ion/src/simulator/assets/key_layouts/vertical_arrow.png create mode 100644 ion/src/simulator/assets/large_squircle.jpg create mode 100644 ion/src/simulator/assets/round.jpg create mode 100644 ion/src/simulator/assets/small_squircle.jpg create mode 100644 ion/src/simulator/assets/vertical_arrow.jpg diff --git a/ion/src/simulator/assets/horizontal_arrow.jpg b/ion/src/simulator/assets/horizontal_arrow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..248143985dc5137e0e97a8c286376ad6e7a58bf2 GIT binary patch literal 1530 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$iWcJ(7?>7#K0uT$SlbC{|JLD&<9LR%plLe00$=vJ2NXA6XX9| z3>*-N`DbgH7l(KKtll2wEKQl$0Ca^2 zvi%TufGuYdfH{f*k1A#qRSp6Sj4X^yOss62Jj`J2j7-cdtZeLp3>=CEhK`9s!XgSr zNdlru%1(jFMU4(Fg~d(HEt3~*+_d@Np^H#YGuku!Fy#(2sMTvwdphl;^E-uACTt94 zG5tI4UwyBg@^S9x`Ez4+f9&uriQ=$T)ZaBxFH?T|C*h;dmS*vJJ?`FnHg6g0w{1_Z z*Ia*aLs7X^(>pNeO330{o_Bs-jm!&mc@($gWNw1Rt zPf7s$mNQ>i?6>du?I8OnO!U~4&+HAa7DYG4tA1M;N-!diTYyPqnmX_yFyydfN@5Z{)p5@&ACnjv#y5>K_gLkqG zr8#zg!f#HNd@fl#b?LFYxvtz!m&&<3WfhulR6N~2)AZ@<&W7ik+>hU836EI6!0)$u z-SYno=O0{~>({yDhsvLh7R!{EU%Ym_dQ%qNykLGIgQ#|Qb+-0v%kSpGGSd@HzwTH3 z?)SRz=-iXG+5G!Ozoi_j%-MJ9(qlifMN=1MTDPk7O>lh4-WR_$R%&^}(fO8F{?taf zXor-qywX46$A^-_v;PYA^at}9%3jLexZ;t4?6K$v7N7nzY_j?nWE^f-8{X>k`iR4c z88Pg^cb{M4?$Q4@CrzCDgwdIKUyn}JI(98(Q+>><>DM2XonCmVaQC(=LA!o?6dja3 z+8(t3M2_OlSi2JTwsUS1UUSQR&eF84;pp?9%g3!;u3~pvL+*1Ir%bVM=g#)mY{~p* zMcn4=+&XG|Dy#h5+&w~@g0H_|(|H!V`i5&k&dQF<7n9Xr@f`g5iNC4Ksp8phZ`sp@ z8b9~iB<+qVRG!hR-69OUVig; z_hZFlMlyZ}Ppz3YEj8te@a~{l!6}XR=Kd0yFL>optI@Gwf$T@NTG6LCGTWqs`z#jU znX*L4U&{UDKgX&s(R+P9z0z{z7VS{JyrjbN&Z&wO%U2#>tr&FiKpJm}S+kFOqM}AF z^FRN}uX!spK3Qb1JE8e7(m6a^Si<@3zq!-+rCu+xitTeOI@T|oCDXcK&9o&aIoJ=f zo>wU}++wQv?6$P3wgP{&L&;&bvpGt}lLLQciMTz!6rcUq+h5`wYipv9SYb}mu8k@= sTaPDdmY(1DeB1sbzvhF=4nd?64qPe(KuQ=!U`}_&qX1Ymf)v~Y09WZ(egFUf literal 0 HcmV?d00001 diff --git a/ion/src/simulator/assets/key_layouts/horizontal_arrow.png b/ion/src/simulator/assets/key_layouts/horizontal_arrow.png deleted file mode 100644 index 4a75fabeea88d1e3e6720e658bd8226ad1711f76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1652 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 N002ovPDHLkV1hit>e&DQ diff --git a/ion/src/simulator/assets/key_layouts/round.png b/ion/src/simulator/assets/key_layouts/round.png deleted file mode 100644 index cb94b177ef9cb802400a24a1d0614650cda7c546..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2429 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 diff --git a/ion/src/simulator/assets/key_layouts/vertical_arrow.png b/ion/src/simulator/assets/key_layouts/vertical_arrow.png deleted file mode 100644 index ef86bfaf2fa9735789e1d08d16059bef2c1573f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2403 zcmcImi9b~98$U=i3|Y#)#4RN=L-uWGEMqK_EY}*1U6>JLZ>&iYB}uYPmzyTCWZ$=o z5=kO^x@Cz8g|RpHjQVwdzdzu2&gY!xd7tO|e4qDupZC0InHWh%;wkAOaQ`h9}}eVfaAl zA0+?b(RUAY4e<6SdK2)H3|^cIA&96gCB;be>-sTIqPNGtnec%>%K{5TFgyqaxIE$) zHyEnLuxgwO@OB3?Gx(7TTKmZVvHi@Wg&sV)m zi6_M>pMU+>&AmXS62+UEn$~Dv9JasBaFBLr$tO;nnBQ1_k9p@m)02KTGZPaQ7FOvu zt}<2^DD!2kDv9=Gd`@flsY3=)sM`6k9Va!4{{1`}%~n-i{U#rMOVn)~3OjY`;gct_ z8l-Kd^z`)eO2Sn+XCk*+XK$30z=QU-gVNK|;+nh%#Gl_(jOO(lm5CrB^od~cvIy~$#G!CJ?%s^Nh7<4|<%}q*K z@7f-}u3O&S)%7-6MD^bBZ;oqYWA@5Or1;~oizEHH+Lqh?r(tBM*4XOq)@o}jOB>$H z%RJTSx2I&N+St&@h#-5Y@kmSLo`nFy$<)Lx&M(*J(xtbbKDimsHnhU`l2b0Tehb1xn4;R_~_nsgg< zMf^8I6AMPp-MD-|OYiiB;h z;1+h0IARqzvPL&@`cd~f2WXevc= znaxpjtbYn^ZH=8R4#>_Ykw~1B-&nI*vnX5#vJMG*ucUUIMEab>Ig?vj@}zv;m00KJ z=hG_UwK}%f7f(HN>4Z3vIo0F$WXma%4?k5)*x zs=lZQk$-fj=cunCQ$#CiZ$1N6!jxz@J6AMRM8(`HN?o1d z@Gq+&ycY+pfEs$epj8yCP_O?X=Dc<(ZvL1WAUek_fqcGEX1^=`d($zo_qQZ;uq0>{ z7Ml|7aG<+aoPF-*?EFB!XoQ7Vpp~_?x?e~cxaoxK*;B4bXa@VrLVI?=UhX$(I_Z9H z;iKYW(^gn?$I#WmWO7%3o=$%`Iv(CFXOmyj(&YG=N0+h$6p7^O>FHeyUGO?E^N5>X zP|q^840o*69UUE2G|BOJr$Y=5Hg;#T9i5z49A5b}EhHqg z(3hoJUVT|d=C7`z<*BCABjWXZYR8ibMr?J1f~ulrfR=z6<1?OfbZs1cYZaZN9%$Hl zq4fi^1meRX5e{Jq37h+4_S?&u-$g77J34*9Z-+S{Az^5$DT39}rncE0>7(Dzu^C%d zuFEMTuI4ZKcG@e7`kEr()#%YSvYDTn%5!9x=`^Q2j(dZzxh$2P0KsV*Qz(?LyB_n^ z3RhgS;+0iZ3-=PavD60-9+;+)bP8`K_o@v}v_wVO(>{7-WEM=-e4|GRva8&S$N$;f z?DW*WN*4BdfUB~5QK2#e$|G2fdE~ifHTNpVVR(A`MlDJ+Hl?+^5AwHhXMm=QdgN|x zKtsFB*xiK+W#0-Hgj1vWw(RPiCc&6-Xjx?Kh@WP$Yu3T(=DQ9xc*>svbJk&Db%Qdl zO|??7`r(an%JP=0dpjGS+v3>XPEBFAeS50*>@bDc9I43*=1xwz>)C;PauC`? zdMN$7c2H2z0C>t7akr*5pUqqAyRM|BW)x0NSmSA%&`I2>5pOIkE|zOYPgmn7jagY) ot!!ma3|jiIItm zOAI4iKMQ#V{6EAX$ia}oFprs0iGfLwky()O{}BdRpbwasm_eR_0S-0k-ap(g@0MO+VZ*_tI#uE4E%m)RBuLHc)`Gz%NOfj)ycr{RqxZrWDf?W z+JpZY1Q-~nKH9^;V4XUngMqm$=SK$v!^WT7-v1d4806MIdNTDbQv*Y!&Gpi?FFY7z zmw)-kahK&w{sYE$?|Eeb? zmZnehERG*t&NZRzp!t>4tF9gK`8Lb;jGfn19*)iIU)Sd_hZfZOHKjf>@7?!GNaT;X z?UZfyg^T|)EIewL+u9>FL+H(#rJIF#!lr!byV;GeQ@p#ZB?WSnb#!M6-qGFB{7Z0K<9k4~yWioJX1Deo!k-E!Efd8LFYTP%kvTe6JGCc9_e zb&J>j5iz^B->6vg>g^2PT|QE56T=St+LiJ3p6y>F_uxD2COTecHe~iU9J-nJNF(xr z&=P^e_tkFdE_!cd{d>7c$Zl@Upexnh@22zr{c2;cHMb*je=1{^_qt;tMeYxSF72P5 z#GxYiQr1^9=sZiGdd|P870-%i1ng*%)Mf>?bO_5yXuq{ z-mHK1+2iN8`^ucLY)cKN8k`g;vdAb|{b$DllNbLP;u>F7_dn9^_MD#fDN6bDldaLh z-yT=JoA)Su+rG>RS9NVRE$h1Fd&KzZ*DU*)8S?3OUU|BmH+oYZoA_KWwEG?n>8-(pwK)JZW{F9aXczY*b?XHwRFtMKqe*~44jHY6Ds@R-XV$?nhzJL2tK z!Y60>__fvh&ynf}_bzMQ%@?vZ^vFYD!OxfW_FVX~<^8h*f4G02Tf8f;b7$XCx8G}q zp*_`QQ{2q36IxaLSu9pC39_UO{>46UdsLQ|F$s?2lX-Clb)^TQjS=OGGTq}M#N`KQA5yz!DUs;3fdJWovT) literal 0 HcmV?d00001 diff --git a/ion/src/simulator/assets/round.jpg b/ion/src/simulator/assets/round.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9311aaceec92e54d0275a658d768358e61b8eaf4 GIT binary patch literal 2086 zcmbVNeKgbiAOFtSRtan97~3%15NU<$DUWw*b+EC7w3s!`LmtA}5?$1dvdJu#sEJZy z>@q}M7Rt)=^)L^&61O}=$x~6T9`619&iS2l|MvWhAky;V(ZD_R$&iPAOF*EiaY+wJ0V81&D5`TuXhngoEu zfFvLZ4AKSU;2=q_F(xwwYo{EUhzj#KDDy#QNZ2-NBtiUV- z0RP1r-~gcFx}F0-|Kur;2GH<~-e%5GpmWn|E2|r@u@kMBMkA@$q22wm`Y0cJC@{Tn zEDHLkfb?Tr;T*3b>*u6CHcGZuY%=N{xM(MiCZU+eFlUmN>}VNAsHdBNmhOY4rrSm@ZL6&Ba%fK2%bU#EYpUi4CE zEvi|F64DPeTW5&&yFI{!J@3hzXi$O56vF+I}u`RoPnM3((9T8@1gU=X~3R5Gs zY%XY_0^0R&U25KhzBti2kx}$J1DBhwWNge_8u1`#hJl#`qAh^z`0)8g7GsA;u<3pE zwzQ>Y^MS+TH+(Yi+Ml)P&N(sajRa`=831Su&AuU_c>@%*!xGUKTTQ%Y{?4s`?D z(f$)&8?n7Fl_%@2Ijn>+cIUjOy7J*;cBsXSDL1iGhPcl%v+z|S@Zuwa9`j8W?&~idoNBKjT&`A+%PK-Nar>jJT0?3|H5S!K z`@B}-p*lP?uV=)5S=jm{eE#rIrmTW$%}fjRBxYUH*s{;b?zIzZRe@R7B3134 z3PI4tsh+Rj(&{r=&KxsdF{5NB8JUN_E+ULcnG1-~QR#z#{Zbw>2AAjVJd=V?d$IxC zSSdfbe^a`F3mwv2IXdLASU7EOmLBX?*uv2liv@k1qlr=HMraJ&o7Lv9eR2VnD=%~AP}!YRv=>otm7jpohc z4HbV7SBhaw;XABwjL7bzl#NM~{@aTqi4&*0#>aP^*J`Q0QN{2gnWVieNziau zz}h!IezwT2#tigcO^+po8EFPR&Y5koCK0gZ`Ppd7^mRtB__#$(t!vfmi|@TYh6cHY zyfLO9XT0-mH6qSEi?su55BAy|e8Kis^0n4-mx?A&DO;SnoP77%#WGuR^xo=_X&$yH z;-)}vmkIk7e6@AP&L7S36836$1(zS;6LThgIH;^yljf3|5a7kOL+6H#bKCabGurEP zhs15HXu=17N?}<+QNDbm5pMX13bW59bPwmC3%xFnc3RqZq@ka!e`Tsi!+aAnM20R! zL9Z<8ylcK?ovVo%;Uh&OO5KHZI7dU%T3MjAau2f|=2o6BUv0LE-APk3E^zh}-66O- zAGh^kD+i#j=2YB##L^phW3Mr$K0^ClL0q{l)R08(bnM$d7I}O)B;oeacGV42`p1xs zv2q=+=-&Bq(nX^YvTrv}7ioX9=V!haXN&Kk$DU=Rc9NqeY~I9{o=7p(Q7^hhEY_hM zIY+>9>gSgHkdNxqu9@z%H}Lj2w}Q{!Z{u~E{UGrLQe5iCkQVL2i??;%F87@lNi5Vb z;;KmB4p!E_$=NB3&QcP+?XGVYb1p+>$U3Z!iNL5773R?h{4?R{=0V@hB zmK}Np$lX_RO0 fYrvPdfd3~9zh%kMZ^;5$|IxAkLRe4HF9g|NCma3|jiIItm zOAI4iKMQ#V{6EAX$iWcGP|wV$#K0uT$SlbC{|JLD&<9LR%plLe00$=*-N`DguE7H?BLkg|!dI*&;@Z2CEdcbhG{7p!9cz+k;DYscdo461v=e=sn<{n5Z+ zv?qW;TFb@h0z(ALouz9O7}CFozIt#qaEXI-*A3&ty^K7Irbj$;KU=9+4|ISCveghL zfbC@xfVqbOk1A#qRptT=jLgi8Y)l-?oDkiNOw25-Z0rmifCbJp)T>O*V|JM;v{cCR zmH%FkJ@2`gChU^E8oqqW+{ZW7_MHEvk#*|UXRT;GldGjauFEi9-MNC-gY&n1_P;}0 zyFUMIp4y}o^h}GjK)JJI_0P`LylFH)vhex5$im}d>r%afwomdeet2xMl1!WGk=kP_Mq74VUaPY(Bg*f? zl9e_G*%wOP+?{E9ue&?({-z*3hCUAA^NKe7%5&#`$yJ?pPIr^D$n>T>tBXaZ_PaTl zzi+Bv>}T=#oW$kAh)XKY6OQcY-*Ki*#(S;Rt*)@v1@FBk zxf?h>VKfx zTz$W}*PhzGeB=C-lV*?KvpDWhuxh^H(KhLq_}|8=Rp-lA2Codcud>@H_>!3aQNJ~7 zetdhI<-0qwJ)&dN^u@o<3E6!Sk^fNN@cMH2UC!HSUw7+%pRHl?Ys-lPIxk-Q)!Q$h{4^ws&#E{<));w87(^|aQE2E%c$JHlRc8_z>dPT z=dG^qSS(_9@=@vo{#BC>+BZJyKdSYyaGllql;5j(H#%iBJ>;IQv1Hn#l zHJtrU@N@XL;MMtc|Acqf@0z%$@y=_}H-F|#+2-`_vY*#)$FKhxR`o{CR(n((_-tv& zBhMYnlxLWlHcQAa=53f{*Zt^9#l=SkXCl^^o#s_g+&0_8Nnqjy6+Q=ke^9m;L@JlS WIXVDRI4}Zpt1})2zyb}V;3fbY{5oL( literal 0 HcmV?d00001 diff --git a/ion/src/simulator/assets/vertical_arrow.jpg b/ion/src/simulator/assets/vertical_arrow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b6e34cf1dd4f397eba4c6d236c50789ab285d2a2 GIT binary patch literal 1527 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$idLS5Y5b}#K0uT$SlbC{|JLD&<9LR%plLe00$>4I}*-N`Dat(FP?sGJG(94Th1kY)tm~|vy19p-wHq4u+j2*m*4ZiW zhfjeT^>pq51~oP&eTJ&_!Lh;d0h}9CPUJ@Zb(m;&|4!x#e)$5qy_=2F>eYen5J7eU zL_64eCIOhU81SfKMp0!gz`)4D$OsH4c20=D7=iv_Wn*XH5Hu7JQZxuGQgBL2ZagF+ zs^aL9xJg({TuC{guzAuVsE--#8D3nun|0vJA_+^@#j zwja21X6Et5kKOFdV=6fg*lYCXKCg2y+i}L|PGfe_clE6sgLcj=maYD};*0Z!`Ed`b zSY8Uwk(qXQRj%`+NoGgBcV6RhJhrcW#_9Qz?Avr#8b<|uc=TR$Nu|ea@A7>|ik>H5 zel9q%@74|{L6tS@c3ysNyJo)K$)K<)Yq_|*8Wft32-NaE{NB>N^uqqKjgzkQ?25K4 zdtkE8Z|9@UuavW+v#n1{PWvuAf3{t8>-8sgo3_hD{yu3p?|t>jn+Zk_9!1XCp87~z zaamlXcG>2WkB)blC4N&iIJ4rs;x##^n9O5WL|PXncLaI7QRs7D99GK5H$CCn&6o}C z@;Ph@?&|HA)T}si<>DjbPTYF;>az0ojeB>^c(PjZ{Mw)VJF3IB&a-EHV99vrUFjOW z)469g_fHFZ%)8E2dYxRZ`qmr!t*p$?pPRGm-0KZ9*8lBl(u%2No;X{F^*_VIeXk`> zANCs@X5-mrfFwvMp?j&83(`TFVMA@W6$;2 z9p|}1a_OTx+QNFvb~V52UZPMVeD}q)T?dbs>=s_Ksg!#n=SrSQn_qEqzv6Ey3+lSE zbj75lLY^Phcjx6S3y{uzs;WGJ{r3Z>jz>aer?tYJr#zXGr?Ry4+hird2N9(n@4oDI zTwAOo)~%A*D)x+1)&A9lZTn)kZ$C8W=-0U1(>@cKl@f(-g|Gfy(s295&)+KomPYz^ z#IJ{uaMFJzJMgy6aTGV&k)L;cXvg zg_wpjPG#cG{Pk*`xb%9et)O$_4}`Oi|H&k`@-YeUU$`^k89$U zXJ_(7D^^b`TfThNu~!L!mkwpA)~E#?$#~1h=_LFjYu)=9e5={j{kyXByXq%h6JWer z!G8JV#ckQU+*Ut&XYc3_QgWDUd*F;j{)vK5&blj)$~QUQ7P|}7v+23C=na=2XS6?>Y5d|=sA&4n5VE4O z?6U9K42`+HxhuYD?3&Rerk(V4BCGoC$OqfYI>mHr!W12Qu2ifsn|3pG3fpZbAq$qJ jnhzLc?LozcAX4!LE>8j=g$pAvr#s_O04x_l3T^@b^L9~^ literal 0 HcmV?d00001 diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index bbdf97d10..038362fa5 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -4,11 +4,6 @@ #include #include #include -#include "../assets/key_layouts/horizontal_arrow.h" -#include "../assets/key_layouts/large_squircle.h" -#include "../assets/key_layouts/round.h" -#include "../assets/key_layouts/small_squircle.h" -#include "../assets/key_layouts/vertical_arrow.h" namespace Ion { namespace Simulator { @@ -98,13 +93,23 @@ void getBackgroundRect(SDL_Rect * rect) { class KeyLayout { public: - enum class Shape { + enum class Shape : uint8_t { HorizontalArrow, VerticalArrow, Round, SmallSquircle, - LargeSquircle + LargeSquircle, + NumberOfShapes }; + static constexpr size_t NumberOfShapes = (size_t)Shape::NumberOfShapes; + static constexpr const char * imagePathForKey[KeyLayout::NumberOfShapes] = { + "horizontal_arrow.jpg", + "vertical_arrow.jpg", + "round.jpg", + "small_squircle.jpg", + "large_squircle.jpg" + }; + constexpr KeyLayout(float x, float y, Shape shape) : m_center{X(x), Y(y)}, m_shape(shape) {} @@ -116,6 +121,8 @@ private: Shape m_shape; }; +constexpr const char * const KeyLayout::imagePathForKey[KeyLayout::NumberOfShapes]; + static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { KeyLayout(191, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left KeyLayout(273, 945, KeyLayout::Shape::VerticalArrow), // A2, Up @@ -173,44 +180,38 @@ static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { KeyLayout(950, 2040, KeyLayout::Shape::LargeSquircle), // I5, EXE }; -const Image * imageForKey(int keyIndex) { - if (keyIndex == -1) { - return nullptr; - } - assert(keyIndex >= 0 && keyIndex < Keyboard::NumberOfValidKeys); - switch (sKeyLayouts[keyIndex].shape()) { - case KeyLayout::Shape::Round: - return ImageStore::Round; - case KeyLayout::Shape::LargeSquircle: - return ImageStore::LargeSquircle; - case KeyLayout::Shape::SmallSquircle: - return ImageStore::SmallSquircle; - case KeyLayout::Shape::VerticalArrow: - return ImageStore::VerticalArrow; - default: - assert(sKeyLayouts[keyIndex].shape() == KeyLayout::Shape::HorizontalArrow); - return ImageStore::HorizontalArrow; - } -} - static void getKeyCenter(int validKeyIndex, SDL_Point * point) { assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys); makeAbsolute(sKeyLayouts[validKeyIndex].center(), point); } -static void getKeyRectangle(int validKeyIndex, SDL_Rect * rect) { +static void getKeyRectangle(int validKeyIndex, SDL_Texture * texture, SDL_Rect * rect) { assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys); SDL_FPoint point = sKeyLayouts[validKeyIndex].center(); - const Image * img = imageForKey(validKeyIndex); + int w, h; + SDL_QueryTexture(texture, NULL, NULL, &w, &h); SDL_FRect fRect; - fRect.w = X(img->width()); - fRect.h = Y(img->height()); + fRect.w = X(w); + fRect.h = Y(h); fRect.x = point.x - fRect.w/2.0f; fRect.y = point.y - fRect.h/2.0f; makeAbsolute(fRect, rect); } -int sHighlightedKeyIndex; +static constexpr uint8_t k_blendingRatio = 0x44; +static SDL_Texture * sBackgroundTexture = nullptr; +static SDL_Texture * sKeyLayoutTextures[KeyLayout::NumberOfShapes]; + +void init(SDL_Renderer * renderer) { + sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); + for (size_t i = 0; i < KeyLayout::NumberOfShapes; i++) { + sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i]); + SDL_SetTextureBlendMode(sKeyLayoutTextures[i], SDL_BLENDMODE_BLEND); + SDL_SetTextureAlphaMod(sKeyLayoutTextures[i], k_blendingRatio); + } +} + +static int sHighlightedKeyIndex; Keyboard::Key highlightKeyAt(SDL_Point * p) { int newHighlightedKeyIndex = -1; @@ -240,65 +241,24 @@ Keyboard::Key highlightKeyAt(SDL_Point * p) { return nearestKey; } -SDL_PixelFormat * sRgbaPixelFormat = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); - -// round.png file is 130 x 130 and is the largest key layout -static constexpr size_t k_maxKeyLayoutSize = 130*130; -static constexpr uint8_t k_blendingRatio = 0x44; - -// TODO: use a "native" image decompressor instead of LZ4 -void fillRGBABufferWithImage(Uint32 * buffer, const Image * img) { - KDColor pixelBuffer[k_maxKeyLayoutSize]; - Ion::decompress( - img->compressedPixelData(), - reinterpret_cast(pixelBuffer), - img->compressedPixelDataSize(), - img->width() * img->height() * sizeof(KDColor) - ); - for (int i = 0; i < img->width() * img->height(); i++) { - buffer[i] = SDL_MapRGBA(sRgbaPixelFormat, pixelBuffer[i].red(), pixelBuffer[i].green(), pixelBuffer[i].blue(), k_blendingRatio); - } +void unhighlightKey() { + sHighlightedKeyIndex = -1; + Main::setNeedsRefresh(); } void drawHighlightedKey(SDL_Renderer * renderer) { if (sHighlightedKeyIndex < 0) { return; } - const Image * img = imageForKey(sHighlightedKeyIndex); - SDL_Texture * framebufferTexture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - img->width(), - img->height() - ); - SDL_SetTextureBlendMode(framebufferTexture, SDL_BLENDMODE_BLEND); - int pitch = 0; - void * pixels = nullptr; - SDL_LockTexture(framebufferTexture, nullptr, &pixels, &pitch); - assert(pitch == sizeof(Uint32) * img->width()); - fillRGBABufferWithImage(static_cast(pixels), img); - SDL_UnlockTexture(framebufferTexture); + int shape = static_cast(sKeyLayouts[sHighlightedKeyIndex].shape()); + SDL_Texture * keyTexture = sKeyLayoutTextures[shape]; SDL_Rect rect; - getKeyRectangle(sHighlightedKeyIndex, &rect); - SDL_RenderCopy(renderer, framebufferTexture, nullptr, &rect); - SDL_DestroyTexture(framebufferTexture); - + getKeyRectangle(sHighlightedKeyIndex, keyTexture, &rect); + SDL_RenderCopy(renderer, keyTexture, nullptr, &rect); // Reset highlighted key unhighlightKey(); } -void unhighlightKey() { - sHighlightedKeyIndex = -1; - Main::setNeedsRefresh(); -} - -static SDL_Texture * sBackgroundTexture = nullptr; - -void init(SDL_Renderer * renderer) { - sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); -} - void draw(SDL_Renderer * renderer) { SDL_Rect backgroundRect; getBackgroundRect(&backgroundRect); From ad1ef783ceb8e7c1caf1e96e609005d25919e65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 4 Sep 2020 15:05:35 +0200 Subject: [PATCH 33/67] [ion][escher] Revert previous commit: move inliner back to escher --- escher/Makefile | 31 +++++++++++++++++++++++++++++++ {ion => escher}/image/inliner.c | 0 ion/Makefile | 32 -------------------------------- ion/src/simulator/Makefile | 6 ------ 4 files changed, 31 insertions(+), 38 deletions(-) rename {ion => escher}/image/inliner.c (100%) diff --git a/escher/Makefile b/escher/Makefile index 65244a136..ce108c712 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -91,3 +91,34 @@ escher_src += $(addprefix escher/src/,\ tests_src += $(addprefix escher/test/,\ layout_field.cpp\ ) + +$(eval $(call rule_for, \ + HOSTCC, \ + escher/image/inliner, \ + escher/image/inliner.c $(addprefix ion/src/external/lz4/, lz4.c lz4hc.c), \ + $$(HOSTCC) -std=c99 `libpng-config --cflags` $$^ `libpng-config --ldflags` -o $$@, \ + global \ +)) + +INLINER := $(BUILD_DIR)/escher/image/inliner + +.PRECIOUS: $(BUILD_DIR)/%.h $(BUILD_DIR)/%.cpp +$(eval $(call rule_for, \ + INLINER, \ + %.h %.cpp, \ + %.png $$(INLINER), \ + $$(INLINER) $$< $$(basename $$@).h $$(basename $$@).cpp, \ + global \ +)) + +# Mark a .cpp file as depending on a .png one +# This is called with $1 = code.cpp and $2 = image.png +# First, we mark code.o as requiring image.o. Rules will take care of inlining +# the PNG and building the inlined cpp file. Second, we add the directory +# corresponding to the one of code.cpp in the output dir as a header search +# path. Since $1 can be a list, we have to map with foreach. +define depends_on_image +$(call object_for,$(1)): $(call object_for,$(2)) +$(call object_for,$(1)): SFLAGS += $(foreach d,$(sort $(dir $(call object_for,$(1)))),-I$(d)) +escher_src += $(2) +endef diff --git a/ion/image/inliner.c b/escher/image/inliner.c similarity index 100% rename from ion/image/inliner.c rename to escher/image/inliner.c diff --git a/ion/Makefile b/ion/Makefile index 72ec4a6cd..1c325ebc6 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -11,38 +11,6 @@ ifndef ION_KEYBOARD_LAYOUT endif SFLAGS += -Iion/include/ion/keyboard/$(ION_KEYBOARD_LAYOUT) -# Image inliner -$(eval $(call rule_for, \ - HOSTCC, \ - ion/image/inliner, \ - ion/image/inliner.c $(addprefix ion/src/external/lz4/, lz4.c lz4hc.c), \ - $$(HOSTCC) -std=c99 `libpng-config --cflags` $$^ `libpng-config --ldflags` -o $$@, \ - global \ -)) - -INLINER := $(BUILD_DIR)/ion/image/inliner - -.PRECIOUS: $(BUILD_DIR)/%.h $(BUILD_DIR)/%.cpp -$(eval $(call rule_for, \ - INLINER, \ - %.h %.cpp, \ - %.png $$(INLINER), \ - $$(INLINER) $$< $$(basename $$@).h $$(basename $$@).cpp, \ - global \ -)) - -# Mark a .cpp file as depending on a .png one -# This is called with $1 = code.cpp and $2 = image.png -# First, we mark code.o as requiring image.o. Rules will take care of inlining -# the PNG and building the inlined cpp file. Second, we add the directory -# corresponding to the one of code.cpp in the output dir as a header search -# path. Since $1 can be a list, we have to map with foreach. -define depends_on_image -$(call object_for,$(1)): $(call object_for,$(2)) -$(call object_for,$(1)): SFLAGS += $(foreach d,$(sort $(dir $(call object_for,$(1)))),-I$(d)) -ion_src += $(2) -endef - include ion/src/$(PLATFORM)/Makefile -include ion/test/$(PLATFORM)/Makefile include ion/src/shared/tools/Makefile diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index 364a709dc..cab9b80e5 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -26,11 +26,5 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ timing.cpp \ ) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/horizontal_arrow.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/large_squircle.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/round.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/small_squircle.png)) -$(eval $(call depends_on_image,ion/src/simulator/shared/layout.cpp,ion/src/simulator/assets/key_layouts/vertical_arrow.png)) - include ion/src/simulator/$(TARGET)/Makefile include ion/src/simulator/external/Makefile From a27122802dc4da27ccc51f77ce318463eb507c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 9 Sep 2020 13:14:08 +0200 Subject: [PATCH 34/67] [ion] Macos Makefile: add depencies on keys layouts jpg images --- ion/src/simulator/shared/apple/helpers.mak | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index dbbf8fc17..eaec0ac4e 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -14,11 +14,17 @@ $(simulator_app_binary): $(foreach arch,$(ARCHS),$(BUILD_DIR)/$(arch)/%.bin) | $ $(call rule_label,LIPO) $(Q) $(LIPO) -create $^ -output $@ -# Background image +# Background & Keys images -$(call simulator_app_resource,background.jpg): ion/src/simulator/assets/background.jpg | $$(@D)/. +define rule_for_jpg_asset +simulator_app_deps += $(call simulator_app_resource,$(1).jpg) +$(call simulator_app_resource,$(1).jpg): ion/src/simulator/assets/$(1).jpg | $$$$(@D)/. $(call rule_label,COPY) - $(Q) cp $^ $@ + $(Q) cp $$^ $$@ +endef + +JPG_ASSETS = background horizontal_arrow large_squircle round small_squircle vertical_arrow +$(foreach ASSET,$(JPG_ASSETS),$(eval $(call rule_for_jpg_asset,$(ASSET)))) # Process icons @@ -39,5 +45,4 @@ $(addprefix $(SIMULATOR_ICONSET)/,icon_%.png): ion/src/simulator/assets/logo.svg # Export simulator app dependencies simulator_app_deps += $(simulator_app_binary) -simulator_app_deps += $(call simulator_app_plist,Info.plist) -simulator_app_deps += $(call simulator_app_resource,background.jpg) +simulator_app_deps += $(call simulator_app_plist,Info.plist) \ No newline at end of file From 22250b42347a3165824ef23021084187827a48ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 9 Sep 2020 14:02:33 +0200 Subject: [PATCH 35/67] [ion] linux: enable to use key layouts jpg files in C/C++ code --- build/rules.mk | 2 +- ion/src/simulator/linux/Makefile | 20 ++++++++++++++++++- ion/src/simulator/linux/assets.py | 29 ++++++++++++++++++++++++++++ ion/src/simulator/linux/background.s | 5 ----- ion/src/simulator/linux/images.cpp | 27 ++++++++++++++++++++++---- 5 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 ion/src/simulator/linux/assets.py delete mode 100644 ion/src/simulator/linux/background.s diff --git a/build/rules.mk b/build/rules.mk index 6698e3783..d8a6ccb13 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -3,7 +3,7 @@ $(eval $(call rule_for, \ AS, %.o, %.s, \ $$(CC) $$(SFLAGS) -c $$< -o $$@, \ - global \ + global local \ )) $(eval $(call rule_for, \ diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index a7bd38c3a..957600ac1 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -10,7 +10,7 @@ SFLAGS += -Iion/src/simulator/linux/include ion_src += $(addprefix ion/src/simulator/linux/, \ images.cpp \ language.cpp \ - background.s \ + assets.s \ ) ion_src += $(addprefix ion/src/simulator/shared/, \ @@ -26,3 +26,21 @@ ion_src += ion/src/shared/telemetry_console.cpp endif LDFLAGS += -ljpeg + +jpg_assets = background horizontal_arrow vertical_arrow round small_squircle large_squircle + +jpg_images = $(foreach i,$(jpg_assets),ion/src/simulator/assets/$(i).jpg) + +$(eval $(call rule_for, \ + LINUX_ASSETS, \ + ion/src/simulator/linux/assets.s, \ + $(jpg_images), \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(jpg_assets) --implementation $$@, \ + global \ +)) + +assets_address_ranges_declaration = $(foreach i,$(jpg_assets),extern unsigned char _ion_simulator_$(i)_start;) +assets_address_ranges_declaration += $(foreach i,$(jpg_assets),extern unsigned char _ion_simulator_$(i)_end;) +assets_address_ranges_definition = $(foreach i,$(jpg_assets), {"$(i).jpg", _ion_simulator_$(i)_start, _ion_simulator_$(i)_end},) + +$(call object_for,ion/src/simulator/linux/images.cpp): CXXFLAGS += -DASSETS_ADDRESS_RANGES_DECLARATION='$(assets_address_ranges_declaration)' -DASSETS_ADDRESS_RANGES_DEFINITION='$(assets_address_ranges_definition)' diff --git a/ion/src/simulator/linux/assets.py b/ion/src/simulator/linux/assets.py new file mode 100644 index 000000000..31fd20f68 --- /dev/null +++ b/ion/src/simulator/linux/assets.py @@ -0,0 +1,29 @@ +# This script generates a .s file representing jpg assets in order to access +# them from C code + +import sys +import re +import argparse +import io + +parser = argparse.ArgumentParser(description="Process some jpg files.") +parser.add_argument('--files', nargs='+', help='a list of jpg file names') +parser.add_argument('--implementation', help='the .s file to generate') +args = parser.parse_args() + +def print_jpg(f, jpg): + f.write(".global _ion_simulator_" + jpg + "_start\n") + f.write(".global _ion_simulator_" + jpg + "_end\n") + f.write("_ion_simulator_" + jpg + "_start:\n") + f.write(" .incbin \"ion/src/simulator/assets/" + jpg + ".jpg\"\n") + f.write("_ion_simulator_" + jpg + "_end:\n\n") + +def print(files, path): + f = open(path, "w") + for jpg in files: + print_jpg(f, jpg) + + f.close() + +print(args.files, args.implementation) + diff --git a/ion/src/simulator/linux/background.s b/ion/src/simulator/linux/background.s deleted file mode 100644 index 48382bb15..000000000 --- a/ion/src/simulator/linux/background.s +++ /dev/null @@ -1,5 +0,0 @@ -.global _ion_simulator_background_start -.global _ion_simulator_background_end -_ion_simulator_background_start: - .incbin "ion/src/simulator/assets/background.jpg" -_ion_simulator_background_end: diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index b9143a821..66a73856d 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -4,8 +4,18 @@ #include #include -extern unsigned char _ion_simulator_background_start; -extern unsigned char _ion_simulator_background_end; +#ifndef ASSETS_ADDRESS_RANGES_DECLARATION +#error Missing assets adress range declarations +#endif + +ASSETS_ADDRESS_RANGES_DECLARATION +static struct { + const char * identifier; + unsigned char start; + unsigned char end; +} resources_addresses[] = { + ASSETS_ADDRESS_RANGES_DEFINITION +}; SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { struct jpeg_decompress_struct info; @@ -14,8 +24,17 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi jpeg_create_decompress(&info); - unsigned char * jpegStart = &_ion_simulator_background_start; - unsigned long jpegSize = &_ion_simulator_background_end - &_ion_simulator_background_start; + unsigned char * jpegStart = nullptr; + unsigned long jpegSize = 0; + + for (size_t i = 0; i < sizeof(resources_addresses)/sizeof(resources_addresses[0]); i++) { + if (strcmp(identifier, resources_addresses[i].identifier) == 0) { + jpegStart = &resources_addresses[i].start; + jpegSize = &resources_addresses[i].end - &resources_addresses[i].start; + break; + } + } + assert(jpegStart); jpeg_mem_src(&info, jpegStart, jpegSize); if (jpeg_read_header(&info, TRUE) != 1) { From af544a95d16739dffd5cd74089cc6fb0e8deb874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Sep 2020 10:09:51 +0200 Subject: [PATCH 36/67] [ion] Windows: IonSimulatorLoadImage can now load any resource. Enable to use key layouts jpg files in C/C++ code. --- ion/src/simulator/windows/Makefile | 14 +++++++++ ion/src/simulator/windows/images.cpp | 10 ++++++- ion/src/simulator/windows/resources.py | 41 ++++++++++++++++++++++++++ ion/src/simulator/windows/resources.rc | 5 ++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 ion/src/simulator/windows/resources.py diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 0a1913c6f..00cc6c9f9 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -17,3 +17,17 @@ ion_src += ion/src/shared/telemetry_console.cpp endif LDFLAGS += -lgdiplus + +# The header is refered to as so make sure it's findable this way +SFLAGS += -I$(BUILD_DIR) + +$(eval $(call rule_for, \ + WINDOWS_RESOURCES, \ + ion/src/simulator/windows/resources.h, \ + ion/src/simulator/windows/resources.rc, \ + $$(PYTHON) ion/src/simulator/windows/resources.py --resource $$^ --header $$@, \ + global \ +)) + +$(BUILD_DIR)/ion/src/simulator/windows/images.o: $(BUILD_DIR)/ion/src/simulator/windows/resources.h + diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 60a6c7cdb..3536c2edc 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -1,4 +1,5 @@ #include "../shared/platform.h" +#include #include #include @@ -40,7 +41,14 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); LPSTREAM stream; - const char * resname = MAKEINTRESOURCE(300); + int resourceID = -1; + for (size_t i = 0; i < sizeof(resourcesIdentifiers)/sizeof(resourcesIdentifiers[0]); i++) { + if (strcmp(identifier, resourcesIdentifiers[i].identifier) == 0) { + resourceID = resourcesIdentifiers[i].id; + } + } + assert(resourceID >= 0); + const char * resname = MAKEINTRESOURCE(resourceID); CreateStreamOnResource(resname, &stream); Gdiplus::Bitmap * image = Gdiplus::Bitmap::FromStream(stream); diff --git a/ion/src/simulator/windows/resources.py b/ion/src/simulator/windows/resources.py new file mode 100644 index 000000000..47298ed20 --- /dev/null +++ b/ion/src/simulator/windows/resources.py @@ -0,0 +1,41 @@ +# This script generates a .h file representing available resources to access +# them from C code + +import sys +import re +import argparse +import io + +parser = argparse.ArgumentParser(description="Process some jpg files.") +parser.add_argument('--resource', help='the resources.rc file') +parser.add_argument('--header', help='the .h file to generate') +args = parser.parse_args() + +def process_line(f, line): + rc_re = re.compile('^(\d{1,4}) RCDATA "\.\./assets/(.*)"') + rc_match = rc_re.match(line) + if rc_match: + f.write('{"' + rc_match.groups()[1] + '", ' + rc_match.groups()[0] + '},\n') + return True + return False + +def process_resource(resource, path): + rc = open(resource, "r") + f = open(path, "w") + + f.write("#ifndef ION_SIMULATOR_WINDOWS_RESOURCES_H\n") + f.write("#define ION_SIMULATOR_WINDOWS_RESOURCES_H\n\n") + f.write("// This file is auto-generated by resources.py\n\n") + f.write("constexpr struct {const char * identifier; int id; } resourcesIdentifiers[] = {\n") + + while (process_line(f, rc.readline())): + pass + + f.write("};\n\n") + f.write("#endif\n") + rc.close() + f.close() + +process_resource(args.resource, args.header) + + diff --git a/ion/src/simulator/windows/resources.rc b/ion/src/simulator/windows/resources.rc index 024eb2d13..0dd6b9368 100644 --- a/ion/src/simulator/windows/resources.rc +++ b/ion/src/simulator/windows/resources.rc @@ -1,4 +1,9 @@ 300 RCDATA "../assets/background.jpg" +301 RCDATA "../assets/horizontal_arrow.jpg" +302 RCDATA "../assets/large_squircle.jpg" +303 RCDATA "../assets/round.jpg" +304 RCDATA "../assets/small_squircle.jpg" +305 RCDATA "../assets/vertical_arrow.jpg" 1 VERSIONINFO FILEVERSION 1,0,0,0 From 2236dcdbed29d3b1b803804b828372340e94856b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Sep 2020 13:32:02 +0200 Subject: [PATCH 37/67] [ion] Fix: unhighlight the key on mouse up event (when you clicked on the key on a smartphone, you want the key to be deselected) --- ion/src/simulator/shared/events_keyboard.cpp | 6 +++++- ion/src/simulator/shared/keyboard_sdl.cpp | 6 ++---- ion/src/simulator/shared/layout.cpp | 17 ++++++++++------- ion/src/simulator/shared/layout.h | 4 +++- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index 87555116d..cec2a2081 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -195,7 +195,11 @@ Event getPlatformEvent() { break; } #if !EPSILON_SDL_SCREEN_ONLY - // This works but it is selected right after. We should highlight on mouse movement? + if (event.type == SDL_MOUSEMOTION) { + SDL_Point p; + SDL_GetMouseState(&p.x, &p.y); + Simulator::Layout::highlightKeyAt(&p); + } if (event.type == SDL_MOUSEBUTTONUP) { Simulator::Layout::unhighlightKey(); } diff --git a/ion/src/simulator/shared/keyboard_sdl.cpp b/ion/src/simulator/shared/keyboard_sdl.cpp index 5ea6b8bdc..64e659df6 100644 --- a/ion/src/simulator/shared/keyboard_sdl.cpp +++ b/ion/src/simulator/shared/keyboard_sdl.cpp @@ -67,10 +67,8 @@ State scan() { state = sKeyboardState; #else // Register a key for the mouse, if any - SDL_Point p; - Uint32 mouseState = SDL_GetMouseState(&p.x, &p.y); - Key k = Simulator::Layout::highlightKeyAt(&p); - if (mouseState && SDL_BUTTON(SDL_BUTTON_LEFT)) { + Key k = Simulator::Layout::getHighlightedKey(); + if (SDL_GetMouseState(nullptr, nullptr) && SDL_BUTTON(SDL_BUTTON_LEFT)) { state.setKey(k); } #endif diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 038362fa5..fbc265c93 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -211,12 +211,19 @@ void init(SDL_Renderer * renderer) { } } -static int sHighlightedKeyIndex; +static int sHighlightedKeyIndex = -1; -Keyboard::Key highlightKeyAt(SDL_Point * p) { +Keyboard::Key getHighlightedKey() { + Keyboard::Key k = Keyboard::Key::None; + if (sHighlightedKeyIndex >= 0) { + k = Keyboard::ValidKeys[sHighlightedKeyIndex]; + } + return k; +} + +void highlightKeyAt(SDL_Point * p) { int newHighlightedKeyIndex = -1; int minSquaredDistance = INT_MAX; - Keyboard::Key nearestKey = Keyboard::Key::None; /* The closenessThreshold is apportioned to the size of the frame. As the * width and the height have a constant ratio, we can compute the * closenessThreshold from the frame width exclusively. */ @@ -230,7 +237,6 @@ Keyboard::Key highlightKeyAt(SDL_Point * p) { int squaredDistance = dx*dx + dy*dy; if (squaredDistance < squaredClosenessThreshold && squaredDistance < minSquaredDistance) { minSquaredDistance = squaredDistance; - nearestKey = Keyboard::ValidKeys[i]; newHighlightedKeyIndex = i; } } @@ -238,7 +244,6 @@ Keyboard::Key highlightKeyAt(SDL_Point * p) { sHighlightedKeyIndex = newHighlightedKeyIndex; Main::setNeedsRefresh(); } - return nearestKey; } void unhighlightKey() { @@ -255,8 +260,6 @@ void drawHighlightedKey(SDL_Renderer * renderer) { SDL_Rect rect; getKeyRectangle(sHighlightedKeyIndex, keyTexture, &rect); SDL_RenderCopy(renderer, keyTexture, nullptr, &rect); - // Reset highlighted key - unhighlightKey(); } void draw(SDL_Renderer * renderer) { diff --git a/ion/src/simulator/shared/layout.h b/ion/src/simulator/shared/layout.h index 3999e6b7f..91a990e1e 100644 --- a/ion/src/simulator/shared/layout.h +++ b/ion/src/simulator/shared/layout.h @@ -15,8 +15,10 @@ void recompute(int width, int height); void getScreenRect(SDL_Rect * rect); void getBackgroundRect(SDL_Rect * rect); -Ion::Keyboard::Key highlightKeyAt(SDL_Point * p); +Ion::Keyboard::Key getHighlightedKey(); +void highlightKeyAt(SDL_Point * p); void unhighlightKey(); + void init(SDL_Renderer * renderer); void draw(SDL_Renderer * renderer); void quit(); From 64bcbd708a544a89466e5982efe61e09fd39ca99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Sep 2020 13:54:10 +0200 Subject: [PATCH 38/67] [ion] Linux: fix images: get the asset addresses instead of consecutive table addresses --- ion/src/simulator/linux/Makefile | 2 +- ion/src/simulator/linux/images.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 957600ac1..50387ed01 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -41,6 +41,6 @@ $(eval $(call rule_for, \ assets_address_ranges_declaration = $(foreach i,$(jpg_assets),extern unsigned char _ion_simulator_$(i)_start;) assets_address_ranges_declaration += $(foreach i,$(jpg_assets),extern unsigned char _ion_simulator_$(i)_end;) -assets_address_ranges_definition = $(foreach i,$(jpg_assets), {"$(i).jpg", _ion_simulator_$(i)_start, _ion_simulator_$(i)_end},) +assets_address_ranges_definition = $(foreach i,$(jpg_assets), {"$(i).jpg", &_ion_simulator_$(i)_start, &_ion_simulator_$(i)_end},) $(call object_for,ion/src/simulator/linux/images.cpp): CXXFLAGS += -DASSETS_ADDRESS_RANGES_DECLARATION='$(assets_address_ranges_declaration)' -DASSETS_ADDRESS_RANGES_DEFINITION='$(assets_address_ranges_definition)' diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index 66a73856d..48ac055c5 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -11,8 +11,8 @@ ASSETS_ADDRESS_RANGES_DECLARATION static struct { const char * identifier; - unsigned char start; - unsigned char end; + unsigned char * start; + unsigned char * end; } resources_addresses[] = { ASSETS_ADDRESS_RANGES_DEFINITION }; @@ -29,8 +29,8 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi for (size_t i = 0; i < sizeof(resources_addresses)/sizeof(resources_addresses[0]); i++) { if (strcmp(identifier, resources_addresses[i].identifier) == 0) { - jpegStart = &resources_addresses[i].start; - jpegSize = &resources_addresses[i].end - &resources_addresses[i].start; + jpegStart = resources_addresses[i].start; + jpegSize = resources_addresses[i].end - resources_addresses[i].start; break; } } From 674703f4dc4d7381a1bd64117af34778f46c69a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Sep 2020 16:19:04 +0200 Subject: [PATCH 39/67] [ion] Missing include --- ion/src/simulator/windows/images.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 3536c2edc..73feaf813 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -5,6 +5,7 @@ #include #include #include +#include /* Loading images using GDI+ * On Windows, we decompress JPEG images using GDI+ which is widely available. From 02b648e36d18232e343125f60c5f9ca9e12e1dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 14 Sep 2020 11:51:36 +0200 Subject: [PATCH 40/67] [ion] IonSimulatorLoadImage returns a surface instead of a texture: this way we can set a transparent color key before turning it into a texture. --- ion/src/simulator/android/src/cpp/images.cpp | 25 ++++++++---------- ion/src/simulator/ios/images.m | 24 +++++++---------- ion/src/simulator/linux/images.cpp | 26 ++++++++----------- ion/src/simulator/macos/images.m | 27 ++++++++------------ ion/src/simulator/shared/layout.cpp | 11 ++++++-- ion/src/simulator/shared/platform.h | 2 +- ion/src/simulator/windows/images.cpp | 25 ++++++++---------- 7 files changed, 59 insertions(+), 81 deletions(-) diff --git a/ion/src/simulator/android/src/cpp/images.cpp b/ion/src/simulator/android/src/cpp/images.cpp index 45a1f5c5f..c9a71ccfa 100644 --- a/ion/src/simulator/android/src/cpp/images.cpp +++ b/ion/src/simulator/android/src/cpp/images.cpp @@ -3,7 +3,7 @@ #include #include -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); jobject activity = static_cast(SDL_AndroidGetActivity()); @@ -25,22 +25,17 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi AndroidBitmap_lockPixels(env, j_bitmap, &bitmapPixels); // TODO: Handle the case where lockPixels fails - SDL_Texture * texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_ABGR8888, - SDL_TEXTUREACCESS_STATIC, + size_t bytesPerPixel = 4; + size_t bitsPerPixel = bytesPerPixel*8; + SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( + bitmapPixels, bitmapInfo.width, - bitmapInfo.height - ); - - SDL_UpdateTexture( - texture, - nullptr, - bitmapPixels, - 4 * bitmapInfo.width - ); + bitmapInfo.height, + bitsPerPixel, + bytesPerPixel * bitmapInfo.width, + SDL_PIXELFORMAT_ABGR8888); AndroidBitmap_unlockPixels(env, j_bitmap); - return texture; + return surface; } diff --git a/ion/src/simulator/ios/images.m b/ion/src/simulator/ios/images.m index c255aec61..673fdc9d2 100644 --- a/ion/src/simulator/ios/images.m +++ b/ion/src/simulator/ios/images.m @@ -3,7 +3,7 @@ #include #include -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { CGImageRef cgImage = [[UIImage imageNamed:[NSString stringWithUTF8String:identifier]] CGImage]; if (cgImage == NULL) { return NULL; @@ -13,6 +13,7 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi size_t bytesPerPixel = 4; + size_t bitsPerPixel = bytesPerPixel*8; size_t bytesPerRow = bytesPerPixel * width; size_t bitsPerComponent = 8; @@ -30,22 +31,15 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi CGContextRelease(context); CGColorSpaceRelease(colorSpace); - SDL_Texture * texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_ABGR8888, - SDL_TEXTUREACCESS_STATIC, - width, - height - ); - - SDL_UpdateTexture( - texture, - NULL, + SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( bitmapData, - 4 * width - ); + width, + height, + bitsPerPixel, + bytesPerRow, + SDL_PIXELFORMAT_ABGR8888); free(bitmapData); - return texture; + return surface; } diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index 48ac055c5..a4ce45b19 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -46,6 +46,7 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi int width = info.output_width; int height = info.output_height; int bytesPerPixel = info.output_components; + int bitsPerPixel = bytesPerPixel*8; unsigned char * bitmapData = new unsigned char[height * width * bytesPerPixel]; @@ -58,23 +59,16 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi jpeg_finish_decompress(&info); jpeg_destroy_decompress(&info); - Uint32 texturePixelFormat = SDL_PIXELFORMAT_RGB24; - assert(bytesPerPixel == SDL_BYTESPERPIXEL(texturePixelFormat)); + Uint32 surfacePixelFormat = SDL_PIXELFORMAT_RGB24; + assert(bytesPerPixel == SDL_BYTESPERPIXEL(surfacePixelFormat)); - SDL_Texture * texture = SDL_CreateTexture( - renderer, - texturePixelFormat, - SDL_TEXTUREACCESS_STATIC, - width, - height - ); - - SDL_UpdateTexture( - texture, - NULL, - bitmapData, - width * bytesPerPixel - ); + SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( + bitmapData, + width, + height, + bitsPerPixel, + width * bytesPerPixel, + surfacePixelFormat); delete[] bitmapData; diff --git a/ion/src/simulator/macos/images.m b/ion/src/simulator/macos/images.m index 8e19b3b49..030b8e4d0 100644 --- a/ion/src/simulator/macos/images.m +++ b/ion/src/simulator/macos/images.m @@ -3,7 +3,7 @@ #include #include -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { NSImage * nsImage = [NSImage imageNamed:[NSString stringWithUTF8String:identifier]]; CGImageRef cgImage = [nsImage CGImageForProposedRect:NULL context:NULL @@ -14,8 +14,8 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); - size_t bytesPerPixel = 4; + size_t bitsPerPixel = bytesPerPixel*8; size_t bytesPerRow = bytesPerPixel * width; size_t bitsPerComponent = 8; @@ -33,22 +33,15 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi CGContextRelease(context); CGColorSpaceRelease(colorSpace); - SDL_Texture * texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_ABGR8888, - SDL_TEXTUREACCESS_STATIC, - width, - height - ); - - SDL_UpdateTexture( - texture, - NULL, - bitmapData, - 4 * width - ); + SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( + bitmapData, + width, + height, + bitsPerPixel, + bytesPerRow, + SDL_PIXELFORMAT_ABGR8888); free(bitmapData); - return texture; + return surface; } diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index fbc265c93..b4cce47f6 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -202,10 +202,17 @@ static constexpr uint8_t k_blendingRatio = 0x44; static SDL_Texture * sBackgroundTexture = nullptr; static SDL_Texture * sKeyLayoutTextures[KeyLayout::NumberOfShapes]; +SDL_Texture * textureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface, bool transparencyFlag, KDColor transparentColor) { + SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_SetColorKey(surface, transparencyFlag, SDL_MapRGB(surface->format, transparentColor.red(), transparentColor.green(), transparentColor.blue())); + SDL_FreeSurface(surface); + return texture; +} + void init(SDL_Renderer * renderer) { - sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); + sBackgroundTexture = textureFromSurface(renderer, IonSimulatorLoadImage(renderer, "background.jpg"), false, KDColorWhite); for (size_t i = 0; i < KeyLayout::NumberOfShapes; i++) { - sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i]); + sKeyLayoutTextures[i] = textureFromSurface(renderer, IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i]), true, KDColorWhite); SDL_SetTextureBlendMode(sKeyLayoutTextures[i], SDL_BLENDMODE_BLEND); SDL_SetTextureAlphaMod(sKeyLayoutTextures[i], k_blendingRatio); } diff --git a/ion/src/simulator/shared/platform.h b/ion/src/simulator/shared/platform.h index 494c83c23..545594267 100644 --- a/ion/src/simulator/shared/platform.h +++ b/ion/src/simulator/shared/platform.h @@ -11,7 +11,7 @@ extern "C" { /* Those functions should be implemented per-platform. * They are defined as C function for easier interop. */ -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier); +SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier); char * IonSimulatorGetLanguageCode(); #if EPSILON_SDL_SCREEN_ONLY diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 73feaf813..47a7b3b3f 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -36,7 +36,7 @@ HRESULT CreateStreamOnResource(const char * name, LPSTREAM * stream) { return hr; } -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); @@ -61,25 +61,20 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi Gdiplus::BitmapData * bitmapData = new Gdiplus::BitmapData; image->LockBits(&rc, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, bitmapData); - SDL_Texture * texture = SDL_CreateTexture( - renderer, - SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STATIC, - width, - height - ); - - SDL_UpdateTexture( - texture, - NULL, + size_t bytesPerPixel = 4; + size_t bitsPerPixel = bytesPerPixel*8; + SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( bitmapData->Scan0, - 4 * width - ); + width, + height, + bitsPerPixel, + bytesPerPixel*width, + SDL_PIXELFORMAT_ABGR8888); image->UnlockBits(bitmapData); delete bitmapData; delete image; Gdiplus::GdiplusShutdown(gdiplusToken); - return texture; + return surface; } From 8b5caeb39474777557e19929091c0005f9fbc9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 14 Sep 2020 14:24:34 +0200 Subject: [PATCH 41/67] [ion] IonSimulatorLoadImage returns a Texture (otherwise the pixels data which needs to be deleted when freeing the surface is hard to retrieve) --- ion/src/simulator/android/src/cpp/images.cpp | 12 +++++++++--- ion/src/simulator/ios/images.m | 12 +++++++++--- ion/src/simulator/linux/images.cpp | 8 +++++++- ion/src/simulator/macos/images.m | 12 +++++++++--- ion/src/simulator/shared/layout.cpp | 13 ++----------- ion/src/simulator/shared/platform.h | 2 +- ion/src/simulator/windows/images.cpp | 10 ++++++++-- 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/ion/src/simulator/android/src/cpp/images.cpp b/ion/src/simulator/android/src/cpp/images.cpp index c9a71ccfa..02fd9f5e7 100644 --- a/ion/src/simulator/android/src/cpp/images.cpp +++ b/ion/src/simulator/android/src/cpp/images.cpp @@ -3,7 +3,7 @@ #include #include -SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); jobject activity = static_cast(SDL_AndroidGetActivity()); @@ -35,7 +35,13 @@ SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi bytesPerPixel * bitmapInfo.width, SDL_PIXELFORMAT_ABGR8888); - AndroidBitmap_unlockPixels(env, j_bitmap); + SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); + SDL_SetSurfaceAlphaMod(surface, alpha); - return surface; + SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + + AndroidBitmap_unlockPixels(env, j_bitmap); + SDL_FreeSurface(surface); + + return texture; } diff --git a/ion/src/simulator/ios/images.m b/ion/src/simulator/ios/images.m index 673fdc9d2..1830fc617 100644 --- a/ion/src/simulator/ios/images.m +++ b/ion/src/simulator/ios/images.m @@ -3,7 +3,7 @@ #include #include -SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { CGImageRef cgImage = [[UIImage imageNamed:[NSString stringWithUTF8String:identifier]] CGImage]; if (cgImage == NULL) { return NULL; @@ -39,7 +39,13 @@ SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi bytesPerRow, SDL_PIXELFORMAT_ABGR8888); - free(bitmapData); + SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); + SDL_SetSurfaceAlphaMod(surface, alpha); - return surface; + SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + + free(bitmapData); + SDL_FreeSurface(surface); + + return texture; } diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index a4ce45b19..632a0bcbf 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -17,7 +17,7 @@ static struct { ASSETS_ADDRESS_RANGES_DEFINITION }; -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { struct jpeg_decompress_struct info; struct jpeg_error_mgr err; info.err = jpeg_std_error(&err); @@ -70,7 +70,13 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi width * bytesPerPixel, surfacePixelFormat); + SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); + SDL_SetSurfaceAlphaMod(surface, alpha); + + SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + delete[] bitmapData; + SDL_FreeSurface(surface); return texture; } diff --git a/ion/src/simulator/macos/images.m b/ion/src/simulator/macos/images.m index 030b8e4d0..57f839e3e 100644 --- a/ion/src/simulator/macos/images.m +++ b/ion/src/simulator/macos/images.m @@ -3,7 +3,7 @@ #include #include -SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { NSImage * nsImage = [NSImage imageNamed:[NSString stringWithUTF8String:identifier]]; CGImageRef cgImage = [nsImage CGImageForProposedRect:NULL context:NULL @@ -41,7 +41,13 @@ SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi bytesPerRow, SDL_PIXELFORMAT_ABGR8888); - free(bitmapData); + SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); + SDL_SetSurfaceAlphaMod(surface, alpha); - return surface; + SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + + free(bitmapData); + SDL_FreeSurface(surface); + + return texture; } diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index b4cce47f6..1adb138d7 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -202,19 +202,10 @@ static constexpr uint8_t k_blendingRatio = 0x44; static SDL_Texture * sBackgroundTexture = nullptr; static SDL_Texture * sKeyLayoutTextures[KeyLayout::NumberOfShapes]; -SDL_Texture * textureFromSurface(SDL_Renderer * renderer, SDL_Surface * surface, bool transparencyFlag, KDColor transparentColor) { - SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); - SDL_SetColorKey(surface, transparencyFlag, SDL_MapRGB(surface->format, transparentColor.red(), transparentColor.green(), transparentColor.blue())); - SDL_FreeSurface(surface); - return texture; -} - void init(SDL_Renderer * renderer) { - sBackgroundTexture = textureFromSurface(renderer, IonSimulatorLoadImage(renderer, "background.jpg"), false, KDColorWhite); + sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg", false, 0xFF); for (size_t i = 0; i < KeyLayout::NumberOfShapes; i++) { - sKeyLayoutTextures[i] = textureFromSurface(renderer, IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i]), true, KDColorWhite); - SDL_SetTextureBlendMode(sKeyLayoutTextures[i], SDL_BLENDMODE_BLEND); - SDL_SetTextureAlphaMod(sKeyLayoutTextures[i], k_blendingRatio); + sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i], true, k_blendingRatio); } } diff --git a/ion/src/simulator/shared/platform.h b/ion/src/simulator/shared/platform.h index 545594267..ce566a4a9 100644 --- a/ion/src/simulator/shared/platform.h +++ b/ion/src/simulator/shared/platform.h @@ -11,7 +11,7 @@ extern "C" { /* Those functions should be implemented per-platform. * They are defined as C function for easier interop. */ -SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier); +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha); char * IonSimulatorGetLanguageCode(); #if EPSILON_SDL_SCREEN_ONLY diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 47a7b3b3f..aade41f1f 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -36,7 +36,7 @@ HRESULT CreateStreamOnResource(const char * name, LPSTREAM * stream) { return hr; } -SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); @@ -71,10 +71,16 @@ SDL_Surface * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi bytesPerPixel*width, SDL_PIXELFORMAT_ABGR8888); + SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); + SDL_SetSurfaceAlphaMod(surface, alpha); + + SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + image->UnlockBits(bitmapData); delete bitmapData; delete image; Gdiplus::GdiplusShutdown(gdiplusToken); + SDL_FreeSurface(surface); - return surface; + return texture; } From 8000ff733e92f571b789e839081c6e6dacaf05cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 14 Sep 2020 14:46:12 +0200 Subject: [PATCH 42/67] [ion] Simulator: improve key center positions --- ion/src/simulator/shared/layout.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 1adb138d7..5cd7660f1 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -124,10 +124,10 @@ private: constexpr const char * const KeyLayout::imagePathForKey[KeyLayout::NumberOfShapes]; static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { - KeyLayout(191, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left - KeyLayout(273, 945, KeyLayout::Shape::VerticalArrow), // A2, Up - KeyLayout(273, 1110, KeyLayout::Shape::VerticalArrow), // A3, Down - KeyLayout(355, 1029, KeyLayout::Shape::HorizontalArrow), // A4, Right + KeyLayout(195, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left + KeyLayout(273, 948, KeyLayout::Shape::VerticalArrow), // A2, Up + KeyLayout(273, 1108, KeyLayout::Shape::VerticalArrow), // A3, Down + KeyLayout(353, 1029, KeyLayout::Shape::HorizontalArrow), // A4, Right KeyLayout(810, 1029, KeyLayout::Shape::Round), // A5, OK KeyLayout(963, 1029, KeyLayout::Shape::Round), // A6, Back From 0b114abb95b196a989fd162cf58991a49b9349f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 16 Sep 2020 11:19:51 +0200 Subject: [PATCH 43/67] [ion] Simulator windows: fix pixel format typo --- ion/src/simulator/windows/images.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index aade41f1f..923b16d9a 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -69,7 +69,7 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi height, bitsPerPixel, bytesPerPixel*width, - SDL_PIXELFORMAT_ABGR8888); + SDL_PIXELFORMAT_ARGB8888); SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); SDL_SetSurfaceAlphaMod(surface, alpha); From 11236c67cb524dc3e13f24454775360df011a01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 11:49:11 +0200 Subject: [PATCH 44/67] [ion] Improve key positions --- ion/src/simulator/shared/layout.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 5cd7660f1..890ce13a6 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -134,12 +134,12 @@ static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { KeyLayout(580, 958, KeyLayout::Shape::LargeSquircle), // B1, Home KeyLayout(580, 1094, KeyLayout::Shape::LargeSquircle), // B2, Power - KeyLayout(198, 1252, KeyLayout::Shape::SmallSquircle), // C1, Shift - KeyLayout(352, 1252, KeyLayout::Shape::SmallSquircle), // C2, Alpha - KeyLayout(506, 1252, KeyLayout::Shape::SmallSquircle), // C3, xnt - KeyLayout(656, 1252, KeyLayout::Shape::SmallSquircle), // C4, var - KeyLayout(810, 1252, KeyLayout::Shape::SmallSquircle), // C5, toolbox - KeyLayout(963, 1252, KeyLayout::Shape::SmallSquircle), // C6, Delete + KeyLayout(198, 1253, KeyLayout::Shape::SmallSquircle), // C1, Shift + KeyLayout(352, 1253, KeyLayout::Shape::SmallSquircle), // C2, Alpha + KeyLayout(506, 1253, KeyLayout::Shape::SmallSquircle), // C3, xnt + KeyLayout(656, 1253, KeyLayout::Shape::SmallSquircle), // C4, var + KeyLayout(810, 1253, KeyLayout::Shape::SmallSquircle), // C5, toolbox + KeyLayout(963, 1253, KeyLayout::Shape::SmallSquircle), // C6, Delete KeyLayout(198, 1375, KeyLayout::Shape::SmallSquircle), // D1, exp KeyLayout(352, 1375, KeyLayout::Shape::SmallSquircle), // D2, ln From d4b7b6baf00eeb1cc2bcdc2ea4869c3653b485a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 11:53:27 +0200 Subject: [PATCH 45/67] [ion] Simulator: key layouts assets are PNG instead of JPG I (fix macos) --- ion/src/simulator/assets/horizontal_arrow.jpg | Bin 1530 -> 0 bytes ion/src/simulator/assets/horizontal_arrow.png | Bin 0 -> 11397 bytes ion/src/simulator/assets/large_squircle.jpg | Bin 1569 -> 0 bytes ion/src/simulator/assets/large_squircle.png | Bin 0 -> 11630 bytes ion/src/simulator/assets/round.jpg | Bin 2086 -> 0 bytes ion/src/simulator/assets/round.png | Bin 0 -> 12416 bytes ion/src/simulator/assets/small_squircle.jpg | Bin 1456 -> 0 bytes ion/src/simulator/assets/small_squircle.png | Bin 0 -> 11028 bytes ion/src/simulator/assets/vertical_arrow.jpg | Bin 1527 -> 0 bytes ion/src/simulator/assets/vertical_arrow.png | Bin 0 -> 11157 bytes ion/src/simulator/macos/images.m | 32 ++++++++++-------- ion/src/simulator/shared/apple/helpers.mak | 12 +++---- ion/src/simulator/shared/layout.cpp | 15 ++++---- ion/src/simulator/shared/platform.h | 2 +- 14 files changed, 32 insertions(+), 29 deletions(-) delete mode 100644 ion/src/simulator/assets/horizontal_arrow.jpg create mode 100644 ion/src/simulator/assets/horizontal_arrow.png delete mode 100644 ion/src/simulator/assets/large_squircle.jpg create mode 100644 ion/src/simulator/assets/large_squircle.png delete mode 100644 ion/src/simulator/assets/round.jpg create mode 100644 ion/src/simulator/assets/round.png delete mode 100644 ion/src/simulator/assets/small_squircle.jpg create mode 100644 ion/src/simulator/assets/small_squircle.png delete mode 100644 ion/src/simulator/assets/vertical_arrow.jpg create mode 100644 ion/src/simulator/assets/vertical_arrow.png diff --git a/ion/src/simulator/assets/horizontal_arrow.jpg b/ion/src/simulator/assets/horizontal_arrow.jpg deleted file mode 100644 index 248143985dc5137e0e97a8c286376ad6e7a58bf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1530 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$iWcJ(7?>7#K0uT$SlbC{|JLD&<9LR%plLe00$=vJ2NXA6XX9| z3>*-N`DbgH7l(KKtll2wEKQl$0Ca^2 zvi%TufGuYdfH{f*k1A#qRSp6Sj4X^yOss62Jj`J2j7-cdtZeLp3>=CEhK`9s!XgSr zNdlru%1(jFMU4(Fg~d(HEt3~*+_d@Np^H#YGuku!Fy#(2sMTvwdphl;^E-uACTt94 zG5tI4UwyBg@^S9x`Ez4+f9&uriQ=$T)ZaBxFH?T|C*h;dmS*vJJ?`FnHg6g0w{1_Z z*Ia*aLs7X^(>pNeO330{o_Bs-jm!&mc@($gWNw1Rt zPf7s$mNQ>i?6>du?I8OnO!U~4&+HAa7DYG4tA1M;N-!diTYyPqnmX_yFyydfN@5Z{)p5@&ACnjv#y5>K_gLkqG zr8#zg!f#HNd@fl#b?LFYxvtz!m&&<3WfhulR6N~2)AZ@<&W7ik+>hU836EI6!0)$u z-SYno=O0{~>({yDhsvLh7R!{EU%Ym_dQ%qNykLGIgQ#|Qb+-0v%kSpGGSd@HzwTH3 z?)SRz=-iXG+5G!Ozoi_j%-MJ9(qlifMN=1MTDPk7O>lh4-WR_$R%&^}(fO8F{?taf zXor-qywX46$A^-_v;PYA^at}9%3jLexZ;t4?6K$v7N7nzY_j?nWE^f-8{X>k`iR4c z88Pg^cb{M4?$Q4@CrzCDgwdIKUyn}JI(98(Q+>><>DM2XonCmVaQC(=LA!o?6dja3 z+8(t3M2_OlSi2JTwsUS1UUSQR&eF84;pp?9%g3!;u3~pvL+*1Ir%bVM=g#)mY{~p* zMcn4=+&XG|Dy#h5+&w~@g0H_|(|H!V`i5&k&dQF<7n9Xr@f`g5iNC4Ksp8phZ`sp@ z8b9~iB<+qVRG!hR-69OUVig; z_hZFlMlyZ}Ppz3YEj8te@a~{l!6}XR=Kd0yFL>optI@Gwf$T@NTG6LCGTWqs`z#jU znX*L4U&{UDKgX&s(R+P9z0z{z7VS{JyrjbN&Z&wO%U2#>tr&FiKpJm}S+kFOqM}AF z^FRN}uX!spK3Qb1JE8e7(m6a^Si<@3zq!-+rCu+xitTeOI@T|oCDXcK&9o&aIoJ=f zo>wU}++wQv?6$P3wgP{&L&;&bvpGt}lLLQciMTz!6rcUq+h5`wYipv9SYb}mu8k@= sTaPDdmY(1DeB1sbzvhF=4nd?64qPe(KuQ=!U`}_&qX1Ymf)v~Y09WZ(egFUf diff --git a/ion/src/simulator/assets/horizontal_arrow.png b/ion/src/simulator/assets/horizontal_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..37c38df24526e1cdf765ed227e510f9368a55a64 GIT binary patch literal 11397 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 G!T$>%!9@lD literal 0 HcmV?d00001 diff --git a/ion/src/simulator/assets/large_squircle.jpg b/ion/src/simulator/assets/large_squircle.jpg deleted file mode 100644 index cb729b6b9fa7a0a0eeafd99a8d481699fa2454d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1569 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$ia}oFprs0iGfLwky()O{}BdRpbwasm_eR_0S-0k-ap(g@0MO+VZ*_tI#uE4E%m)RBuLHc)`Gz%NOfj)ycr{RqxZrWDf?W z+JpZY1Q-~nKH9^;V4XUngMqm$=SK$v!^WT7-v1d4806MIdNTDbQv*Y!&Gpi?FFY7z zmw)-kahK&w{sYE$?|Eeb? zmZnehERG*t&NZRzp!t>4tF9gK`8Lb;jGfn19*)iIU)Sd_hZfZOHKjf>@7?!GNaT;X z?UZfyg^T|)EIewL+u9>FL+H(#rJIF#!lr!byV;GeQ@p#ZB?WSnb#!M6-qGFB{7Z0K<9k4~yWioJX1Deo!k-E!Efd8LFYTP%kvTe6JGCc9_e zb&J>j5iz^B->6vg>g^2PT|QE56T=St+LiJ3p6y>F_uxD2COTecHe~iU9J-nJNF(xr z&=P^e_tkFdE_!cd{d>7c$Zl@Upexnh@22zr{c2;cHMb*je=1{^_qt;tMeYxSF72P5 z#GxYiQr1^9=sZiGdd|P870-%i1ng*%)Mf>?bO_5yXuq{ z-mHK1+2iN8`^ucLY)cKN8k`g;vdAb|{b$DllNbLP;u>F7_dn9^_MD#fDN6bDldaLh z-yT=JoA)Su+rG>RS9NVRE$h1Fd&KzZ*DU*)8S?3OUU|BmH+oYZoA_KWwEG?n>8-(pwK)JZW{F9aXczY*b?XHwRFtMKqe*~44jHY6Ds@R-XV$?nhzJL2tK z!Y60>__fvh&ynf}_bzMQ%@?vZ^vFYD!OxfW_FVX~<^8h*f4G02Tf8f;b7$XCx8G}q zp*_`QQ{2q36IxaLSu9pC39_UO{>46UdsLQ|F$s?2lX-Clb)^TQjS=OGGTq}M#N`KQA5yz!DUs;3fdJWovT) diff --git a/ion/src/simulator/assets/large_squircle.png b/ion/src/simulator/assets/large_squircle.png new file mode 100644 index 0000000000000000000000000000000000000000..b83f4e2237c058227b9ec962820e21742f28bb35 GIT binary patch literal 11630 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 literal 0 HcmV?d00001 diff --git a/ion/src/simulator/assets/round.jpg b/ion/src/simulator/assets/round.jpg deleted file mode 100644 index 9311aaceec92e54d0275a658d768358e61b8eaf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2086 zcmbVNeKgbiAOFtSRtan97~3%15NU<$DUWw*b+EC7w3s!`LmtA}5?$1dvdJu#sEJZy z>@q}M7Rt)=^)L^&61O}=$x~6T9`619&iS2l|MvWhAky;V(ZD_R$&iPAOF*EiaY+wJ0V81&D5`TuXhngoEu zfFvLZ4AKSU;2=q_F(xwwYo{EUhzj#KDDy#QNZ2-NBtiUV- z0RP1r-~gcFx}F0-|Kur;2GH<~-e%5GpmWn|E2|r@u@kMBMkA@$q22wm`Y0cJC@{Tn zEDHLkfb?Tr;T*3b>*u6CHcGZuY%=N{xM(MiCZU+eFlUmN>}VNAsHdBNmhOY4rrSm@ZL6&Ba%fK2%bU#EYpUi4CE zEvi|F64DPeTW5&&yFI{!J@3hzXi$O56vF+I}u`RoPnM3((9T8@1gU=X~3R5Gs zY%XY_0^0R&U25KhzBti2kx}$J1DBhwWNge_8u1`#hJl#`qAh^z`0)8g7GsA;u<3pE zwzQ>Y^MS+TH+(Yi+Ml)P&N(sajRa`=831Su&AuU_c>@%*!xGUKTTQ%Y{?4s`?D z(f$)&8?n7Fl_%@2Ijn>+cIUjOy7J*;cBsXSDL1iGhPcl%v+z|S@Zuwa9`j8W?&~idoNBKjT&`A+%PK-Nar>jJT0?3|H5S!K z`@B}-p*lP?uV=)5S=jm{eE#rIrmTW$%}fjRBxYUH*s{;b?zIzZRe@R7B3134 z3PI4tsh+Rj(&{r=&KxsdF{5NB8JUN_E+ULcnG1-~QR#z#{Zbw>2AAjVJd=V?d$IxC zSSdfbe^a`F3mwv2IXdLASU7EOmLBX?*uv2liv@k1qlr=HMraJ&o7Lv9eR2VnD=%~AP}!YRv=>otm7jpohc z4HbV7SBhaw;XABwjL7bzl#NM~{@aTqi4&*0#>aP^*J`Q0QN{2gnWVieNziau zz}h!IezwT2#tigcO^+po8EFPR&Y5koCK0gZ`Ppd7^mRtB__#$(t!vfmi|@TYh6cHY zyfLO9XT0-mH6qSEi?su55BAy|e8Kis^0n4-mx?A&DO;SnoP77%#WGuR^xo=_X&$yH z;-)}vmkIk7e6@AP&L7S36836$1(zS;6LThgIH;^yljf3|5a7kOL+6H#bKCabGurEP zhs15HXu=17N?}<+QNDbm5pMX13bW59bPwmC3%xFnc3RqZq@ka!e`Tsi!+aAnM20R! zL9Z<8ylcK?ovVo%;Uh&OO5KHZI7dU%T3MjAau2f|=2o6BUv0LE-APk3E^zh}-66O- zAGh^kD+i#j=2YB##L^phW3Mr$K0^ClL0q{l)R08(bnM$d7I}O)B;oeacGV42`p1xs zv2q=+=-&Bq(nX^YvTrv}7ioX9=V!haXN&Kk$DU=Rc9NqeY~I9{o=7p(Q7^hhEY_hM zIY+>9>gSgHkdNxqu9@z%H}Lj2w}Q{!Z{u~E{UGrLQe5iCkQVL2i??;%F87@lNi5Vb z;;KmB4p!E_$=NB3&QcP+?XGVYb1p+>$U3Z!iNL5773R?h{4?R{=0V@hB zmK}Np$lX_RO0 fYrvPdfd3~9zh%kMZ^;5$|IxAkLRe4HF9g|NC-#)w&EC=ay6U9F^uz!FfK*dM#Q^ggc6|`y zW6m)+z+V6W^-F}Zvc3w!8|i8j005Y^zRvV98fmF*UOk_&pDe7at5g1T8>Z^K?m$E# zqM)R7M@#FD0E*l7QcnO>01!9hPsm0mEx=4nR?q?=Ay>*EfJyGI z92RZLw`#mg52elRo0=Ti@7Su+{WwIz`~14#F|@id*np(lV+SlT#B<9IqL9Qcvg80{ z`mf8q5uu=83oK2W6O(IL=r(M0dr)BV1)v#{cqAjDB45c=j}378q`!;S2du?)munuL z9O9#wZ+(nc(a@L>N(lzASzg(6?uJYpPF1b1uh({lbo?-{_=%OY(DFR8<*GIuSN&>R zIm%-1lEW^0=CW(om5ry*H9@)I*!*%Cuq!XOT^aKJ-7hOHwT+J#a1XuUs)m}D^DkGG z9_Sa{&!6$!tZUl3iLgFT*R&B23O-9dL|s;LuUj<@Djo*~hOnf_o_k*U#)n98HQQWS z#gkNuotvtA=;_7OOdiZ}k7%4&HAMv3kw;%7?Bi!_RX_Pr3z=o8Ta~{GR`R)=j6+Qw ze3@3Co3~;*Z}_=z=TlJdV*>N-(Gw!^sbTewiJj^vYF%nz8+{ zZBzAJN8TI++AKtBVm-XPBLiD9UT(Fc)JjdBPfAHj!Ls&ya+!(UE8aBD+y=eSqtUD> zQL|+Ex+ecjT?X*w%~Iiu3%$++cD-C8z|O(Vmzp9WsID0T;(36WHJ}`QV-JS{M6`sa z)F+r~24rC0wkF64WdLD^0I@aZfl&lRAUu0?6^Q&+sDVCNX|s<$d6okI8ZLPYz#sd0 zsHrva13+#In;Bj<5Z4BTtwR1z>02U*gREPTFOkb0kf|)52wB29Q+%Nhy+!^kTr|(M znD7&cv2tx*oj&&n!4n1HJcJYTy)bcW+_*?OYh5$KrxC{1OcNwEkH|kiMB*|MaJM+l zV}BrN#FzN|Y!7fj@;pqwm7frurvhg6r${EG3TOUIxe&Ienh5L!u17skq01*53|mu; zV|@j@5tXX}O=j+Xl&Vt7{v_#GgH04WfFv_oK*@xaA!(*dWC`j;adtyh!85Pzjx*2A z)SErzK7?suJ?i&W#qt962lQF=%bcKDM1pZ9Y78%*4Ti3KNcNo%FIyINjE*$N>Id!_Ad%)RT05 z)#Fr!D=q8z@?4a#XKST$@ z#AzJ?M+hR6ogfGor*o&(H84sEg^!v=X`+@<WumpCi>2Fz=!&prglA}Hgk@G{Y-B!%xPiSzH^JxNB9U>C z1ktzeDyp2SPAdwkzF7x!RJQcAIycj|1;$rLABiy}>%@J@+6GRm)-c!L`QrQf__77T zf@IHclVvE!wMHxws!Mf(I*GYyiYOc<9a&d#`tgLggxuaJys>yA{wBaZ&|PN-uw${~ zyQ3!)Cj*mdk-?Lhk%_Hmtna8FtWOV!^p`jbKifK^JagY`Sn1p^+dKcU`onKum>7A( zjIiy-2yy+b0-zDaA&rdi1pfyv4uK_}RK5`oD0d&5D;p!X1-}fh5^#_*8pwMyfG>vH zn1YnD0rXRagxc`d0S!Mj19fD3S{qKAa8!au9)}U95@!!b2j_k=ELkMEI(gVY+hD~Y z+Cay^st~W>e!+m|iPlx}om8TyC5Zu?x4B*TvU#T2>NvQ$)KmGN2Jz`7N~FpQ%?VuH zSF~tRU&#L)&eo9s*tXO*{d-MvC8cJGF2eGz!ld#?l>m#!itSpQhRaEOTE?bFADqiJ z^u#!;d8z}eJvNdz6gR{+ywAf=&bG=XBwn@@EtGiPwbM#bA67m{UP+~X`?Sa;b4=Rp zp_|8c*;ExQ9~KH5M>THrtR^($&A>XdW5 zlgJ&9O5CShPoHS-?v>+{X=O7?|qKSNO6bqAlvGBgN$}_^#G#-=yOhC0Cba1!nv5ZOup3$QEHqFL8FBdgC<#WKWJ89_K zgj)VNJSiE^P8SOnyPtCO7u?i@bgOk&^BVCMr0yFXjSLxf8DbSh3@r^44?ipnDmroO zvR`s^g;PS#^?IKRNj7+Qob>5GH5hBJTB~~Dn43EcGfmUUW=fY$u}d1x=uOFfe$GB1 z`&QVBwdqi1=fUAw3HCwQic;w3s>kN4m#PCXKCvFD$kc+g^80&@;J`cQw)c4*5o+rl zh3MNi)1L@QJS9q7PJh{J*CTHtX>@3L=c7Sz+O~7Y=yuQ7o)W(7(jDjJjSc$vtnzyw z#6S67W^Gn;j~~627}p%T-`Ij;U*!5g7kn@*m|#Az`DLTdz3khYI(Gk}g3w0R1`21H zZyzR>=Affn-(|Y~QmraY%o$ z;s^5J<|wxy`?=bbTD6*V=V2$aoLB&C&-JP*Y4v!3yW@Um5m!C;iY0E->80q(@=Dpw zl;ZBgap>EFIb=O=5dY8Pj?|%%Ux;7pEq*n-mS2F5a!m;jnvPi4*%$0C1x{R#a=uw? z)-PP7=n=>TpE6zsc(X3w*iKJ=%VxFvVE9t}!`l1P1?lzkeuEX!O|ND0$1L~xP53=b zluabYhsN)Ympv@K*qT9p%b1aQ|9Fybf_ldN41Vn5#cl7%{Viqb?3@P;qL~<{K^JNP z2>StF<*@*M^8<80oA4Cn9yHe#`Tr=%!x4Sd*b+cUQ^2K# zy1QP#E)Sv1e08_}?p%dA66)pYmEwQTKmGJ0UMikXcwG3A%7_Y8yGq+f4kyl}WETVP zAz?T^e0q6fxtFSv>So&KH1bl~GQLvJl9!`RI}GD%U&{yihWdvz$90B#zYd6YiT8>0 zi1kCdAz#F#9UpeE*P>M@a!YZ*xFnhUqKVp}-I@^d=9kS$dFd?>)*CO%7NQ7Y$<_tVsmAZE`UaI(ZTZP-+en!* zy3lDghuf}z|=R~%N9dZOyYxA|ImH=q_U(O_viv4iRLkC5TJDURIzvc1f`bYGah zFm$uXU46)F+Pibd&r@A<^YKb7X`!Ex*nM~YAfwCo-)GW1Ol!-KquOE@)du56Z#T!c zn-);Ri%+eByWEDJDU+(xCx~%Exo33TH0#m_1Aks-)|4M_#n<=ezZoK+nh}%rA@?{E zmvP>y8xIM|bE{bNT-9Gio~Hz{lfE49=z0D;&<72Bf&n6jb;HR!l}qGfR3km# zH_mYJ=EL*Z*Oh2ThY*m8d8XBDBfbYM{E~ITKGYSXt9CNZA1ww zg_8UURpS_@SRpmBs-a=b%E7b94Y=k1>#Xv$438c+`*BvdN3>D&)0pL0M-9f5xWpgH zHR&SB#_1`kt0sBI;kVLLU!~<1zb_dpUM|a>pee9dPzT!q}0SG4s(Y2e6~0 z8`MU|e#mXg)%VLqvg+~Wt=?9Xg)uDg#u^oA;UK@rNAW_s$NghM4g@3?V&r^v-JfEE zqWp=2qJi!8oDT|u;TQJ5;5ha;@UVhMoXTD1ZW%@&Y{SXAVMEelRlp~84 zO}>k$-%W^SHkEskZk^>6FvQo*Hz^7aDLvy&fwrP%NM37K4{JHvS8)u)JiFOV-A&O? z)qk^}1}1C<((f{DGwi$j**A2ZEtq$k^HVPgTwwVd)o0U!w|vIVM(d{8qZ1=AOKJa^ zvi@DQwJ#awotwk%t1}fOQlm%_^B3zkfNqB0Q| z#Z6}UZ3%4&v0D3DypVT0^H-)9b`*RXTDKH%NF>H0zTa{O3TtWP{nW6#eV$>Ny&#@~ z>y5{M!yIS!Z6q~M`Z`))z6LLZTs z9CG^R)~6fgT4Wj|2Ob-F-E$4p?`YBP9fmnBRfP|D<=Ay`E$J`i9{|rlA?FtZVQHV+ z^9R~lv|b6-*Grnf9w7F-Lhgo~DYVb0y(m*oYc*~iZ52y!VXUHN4 zdu{Gaz~o+y=PN|b+3<|8VC>1$dm-Pf5aupcn`WC9yZ1sWW*cU!j*-=V%Y$BXCzl>L zR`~L=my%G3%#pp9y)DYsb#48n>SZxYY^+ zd7K&4PaiSO`=9b%+zRDbMcG7YwUJ1<94I_xmCxt6GgwRpWex@eGvV0efNLv^CakxB zk7ZOT$U<+lHsnL|%R9Zl5AT)XvV4|Iqci5NruV^lBJ+jm9_LgL>Jtj*@c7V>+JSEo~%^#B;TbH{EeoMRS(#ZJG8!Jws);c*c z6s-0^k3)GxDNfB*yI%jZp@EXu{f#8l3+<@#M#iFV5f=rYaoV%B;60D|K9){8OKq&4 z-2YlUQvY)Fwd|X!yhx4#H@GsxOZ`QX6#8Mss$J^x^CLk`ljX6+8YAD1<7l!ICevq} z7Bb{ZACK!4cbu6;o|9UZ%{t2wHR4?MVLc2LY?S{LA{6>g4O~;_aGD zB{F=-^6%l+N~4!?9B7IlZ_& zAr=8Xi*3d~N} zhVy`W1==Wr^*o?asKXv%Js`K4T7`69xhGLdCH#Ay>l*#YjfO~@403Ta&I02H(#x26 z#R+y@_P`{<0y0sG$w&DrlX+VDRZglX(LhG_P^svEC&um6))GA5c$vT3BRNwgd(3(g z{0T0U!}wmrYbBPZWJ%%aNTD(>6Zq(>`NF*<<$Rc}Iu!z7+ zt{8{3V?rkFblm|3s~%9x*UwHJr%!paGGjhQv}eGdA3?KarumOK$Pn?SIVBb`7P}u0 z0{Z-IW=$H+9>OnkvE7!AJcRl*`geO(aoP&nP1>h9hd7xE!wba=BH?d(o+o;zj2i*< z3+^cKxbb-uyOy+Uq@J5W!^KA5m%qnBCHwGW9(FjAzEiX9aNuHn<@hD5ujqJDaP+lO zd9SPGl*rUHYzZdk=in)YoX_8smY_I6CYT5_4vwC`5*{1qZIVrt=3zYzF7}+LCZr#F z(N#P6O61GC{77L&n<#njxF7y%*v7W*l#X6Hr5h!esFS$S;nm85elkb(mC9wG+ytH_ z!9bH_&pTI?sE<`LXE6bs-lOMP9WLuWmJ(tcQtloSeNfU_w~v0m9>Rl+S6>rs%d$6k z|B6zaWo_WbdMTwR{X8Uu^|WW%r(?QfJvx(xg~(ee7e)Q8iA%upZs3Jou+Cc1u)~V) ziTee6@bdEIMK*ezQ!he7TDn&WU|oqd@k`G2>GqaudP>R@O#m$H>f6B8tcw$X2Qg6? z>A9GGbs~Eff@PQH7G*CkRbS8F_!Cjhzb|1+s-ZBHU%!cI%qhKnQzTHd8Sjuntlg?u5_?^nx1( z>KfSvy4XqCv&qYWWc*P8jYAcf;85#<(i-x1UHS50rqsMgezv?&$mv$9f2u(!EI1*70e;aCJM%c4we!W5it^h!oZ?1s3bq;4F1Ds z7)C9^A>hBTN*IZNVPaqyRO(+?F|}%MgR=RL%-8b?)4Yzv6k(5Yl4TQtV66RvsttE{ z{8zI6HS#dMzzbog>5hVXA-yoRu8R-j|DTeEHf~5)xVJvsOCRA6cm17;-zv?ntH#ym z4{i}*kw5wG{(JI2sK)LHOu{kj*O-E-VooM-FYn(r!J?4A=)ZGsh-rJ6REmpQQp}YTO=(-R7hL{Gz8|C2@@MotognRyZ<%$6P?h`N@yFWtl zw(*7Av;9d#{2O43fbAV5z!+dU*h)eKp&}w8*T94b!tEt&#NiT9Nr(+5d4DMNbAn&z z@?Wq3k${2!p5Q;&ygeLHel}ikMMq3Y{nd?sPtJ8)04Xb9cV8(GkB*HU!u`5e@cw1M z&dJ8z5sq03gxQ4uyNK*O+-+Dhh!LN=k}J2uewc+1c1h ziHS*w+5MHC|1a|YA0zsowYqw zT~{MG1M^)KARau0!s$+EO{yxj`HNe{sHCTbX2gf;xJfuRE~#wuj~BuU1>jc1PNdbW z3ZvM3E@&WjX!wm9LK`Y!w8jL*ZQ|3bfp@zXa}T^TTblj>(dK7+9-VQGOmjlDNgz^bX%CW z+GX6td91v|;Pt)gRG7~O&)cTUPeV0s=7oN{vs!_HKQrhc4h6#IH39+SZ|;kv3*C?L zzc}^09mvA}>{-dxnR)_qHMa5_%Z8~p6%`c;`8@2lwzjFSbh7!#B9%G8>CbzOUaFCf zAJAC(w=4z;H=gECyPRg?lhX6}Cv0tP#rbTG1tYI6f9(wo4N*G~MDTk2YLBI1mvEc9 zB_DLW6{=fg2#@bFvgL4>Y9QlM!bX1-9j->}PoC`gVS<7m1!Rl68CfehU!1MxdSp`E zdS!EYcCh*qi31T8RjYLxR7$bom~BD6-c`oyLbtfEz1(MfyWoM|6C-RQ@WEbL9L#P* zjFo4KdM$M&+}6?x{8k@u;8unHy!W&5!w1Qog8})0sQ7pjIu2%-yM&0PLq*>Mt3vbz zee<#o8<3C?{o_i%*8M&wH}~pGx9OTA|GjxdTAUU=z{SzH)oWs6V#@E|zo(j71&?kc zJ?B3pQAK;%I>EnN;!`&W;SdZEq3$)YUkOqC5-7lpGk);OW&BOGSbi$_7T}Fl(`Em~ z*@2-=wF{m5hkze$km;WmoBR7_HsB(37{<`E@v%`nwzTHuhP7Ds$A>97jb<4e-iQ zqYm!|^`S%zKfM=FA;fokYV;986i}{px`hl@CXGnMvL1LYyGzO}b80w<4{9AC0{}}r z7duLeJm;H3-CtmvJTe}hP$jYPqwnUdZLUQF9%j#wtv){ zjpyO>xlkcIwQ*;pB&8DL+(S4V?r{&BaJBzgtdc%nH{;Jl)FOQI9+bM9^F0n=O^V}@ zzv7-)7=o}Qn~qS|9*~lf(n&GAIa*d!e>~;#hFohNSP1xN;|mBS#NVf!&rzw4vL4xA z?35-jFvcaptw5XCxUPSi$rp*5Po#ZIQ}uui@PLT$E-8==fOOyxNtBcK-?MJ6k3plY zfBigSiIT`I*$D4dNXK`)XJj-Y4YdAxlpq^)-1iYvZf$#eNQeA>YfyAdJsyciLrY#6 zhgqe)CNW;rR>2~UW`Xz%%kD9y$D^Mg-#FhS!oPn>XpvK2Uw_LhByZe$Z(@{cBwM8} z6g^m=c@r;Gs-ZbAmcy*^K>(#Dwurp|eYo~0P8cJKUElTFhj#;WAy+e^BMSPYttPMU z1%RI$Ka%6h+}>kaReV0)~J}Kre+_< zqX6pShd+Ov%uVCt5o#pm+u&MaL0V{4YUr4oPQS;JKu>Y$37wmxNN9b@pEwH7hq+Hw zw0JBI3WFG>eaj)Xv-eapwO?@VJ#wugV}kJ%18xuk_3wtSP>QJbkbrdw@A=LVkTFV9 z`+x#*6iHczv-NTB?2y_MlSUuYbDo1(zf!K1#q1FgM$zOAN6n%SlV)PHzW~D`eyLGn ze`#J8Dil&ck}-(8rn=IA_FJ^UCI(t@tH8JVBj0b=J<|&&;BY4lVB`@^pHhIMs?q2( zoM+O0#|kVTpOn-0DVCI!*6v6C3g;;9AAAD5MP_$X~M5Z=1FE+I+ zOGfRh7mfqZwa%Paa?$f`3Xw4@I2F&V`z3LNe#B7mC%-QUbKvxP@SxsdAheup!V;`^ zdX{Etd57iZ&6`xOhs4Q27l*@ovb+voN*r9ElQE@?GjM6Yow(N|gt~`Q@78L$C}=wp zSmZ5C$lVnb-m=j}3y_u*0JNwZ_d6JRUYpm>{vgIR;z%KFrfh-jyfnMied{svNvxj% zn1gw{%)&`en`2lJ->IOe7^vbFUCeBu&}?s*<-|8OaA0$<)vQ2?%?me=YiFj8W(Nxw zHQ57u+#QGp3QNeSNPzMRh(-mzlY1Q{9lfedgys!CPT|(;=Qx_LBrmp$#=lc_ui|M7 z&%w=kg~V=B!Y*UOCCzfL1?Bg;s5`ne=m7xEC~vXC>E-!ukc$os4mP_%a_<7uo4tjx z!@R$J@MND5s^F&O($2_JqcaAfHpjlOyEhLt-_zu#BRPa=Q5b`o?Y@?3IM@wlDFEH@ zf!~SN9E}NVdyF+0ZEMo8N^AjD=WR~;88!AL*1>iZ;rLtx_a>JTMSo%Av@lck7sFB3 zW1%uPGDEF}v&7w|)XAJ3hm#4lk3m#8W+`Q2MUmZdHOiP8q9Pz|{5oacWuG9Ua(=u` zMZlRYS?_9Ppw4O#V;~NsBRpmmlNBUL=9z0&s7QE{M~o8?L>!z#s-B%x`-B(c3*8-_ z$!VB5*Jc^M@y}sf-T-OXqZZ3WAI5ui=@Z@+G&Vo8lr4IAi}En4Kw`yF(X(Ei0+S>p zxIq(yw0R2}dU`C%ik#Tkg7fyEiIU?)NUb9Py`!5k1=@BQeR}cd>mKS{Nov_&VNMN;-?-li%*n z;QVKW3WCs+jDjL;R%vPJN966#q41tmZRY@QYr52@_b_iMei}8Z9?!Y2)%op4xTKYd z+~s!SC=2?u1ii0=r-VQtZt&T{r<(&+YI1XI+-E*jyAYST*!CQ*4@Uw60TD4PU`VDR zTUWPgoN}W2s^sy`%=vRC_6qw7OWY*?+XEq&XRBrjCC(F@6XkayzAS=#q)WjTWjZuKy_FwDd~rrJzm z`0BazGu^_1f&%qI1`Du-%hx=`+!(6c+ACb|44C-CmhY2V*4DFL3iO{jib~i7lYfj) zHOKTL%znuwJ2A02(g{n+5E2kZ8@1pjRxys3cRO83XXVlR_)fHX6Ad~{$g-YR)6OTO z&yDB12rsYZ*>>$*e(8G-3#Ph`NJQZ0)3$(vl|o~Lrex>~yUuta)8shyvn1ZbB&ByE znq`bSef&m5gbWhyTR3wmiJbXynbwSh!dBYv3}4ISY?YG%jlgltvOnby@o$;WJ991i z9G;dg) zZ9;$wm^QK#_JpOJI!kw_TJt% z<}{)rj9(qdl^%T<$SchohW*x?*gWJ!|27kPgid35z_BkQDV|z9Bk1B3xlw}c>hQ?asjOQsi-X5woEYj^skiVQ5wyIMQ1{GU3Q%|mZ z$>zUDr9*-r>`jE~u>A&5e3_r0KRb#Q%B-N@l9-!2mw-y+p_@?qIu1!3xWz~3XhpRM z24cp*emmcwK3KRAT|c1HqTNzm9aW8g!A=`(*7O4;j0O8wv

l%i}3FT-dm%jjxSG$-> zfTM~#1?Pd)yRV%w4@!FCnWSgO%_%XfH6Lxo1DKrL?BhUyqP^Vh$05dOSH14luwUiYM#k;AFtj1mP(L`eqxuqO~8rMNx2#A=y8$gz`{I6N~O zS7Gg_urAA=f`We3y|B>O%!T=sDuWBr6zDw42t_+nz{**oKS%9Q`U)N2!f}l+4{a$y zD6`-$K`suOY8!;MWHeKR!)fo{;selP6WX2}C3D_3!0h3_k!h02EFc%0L)(_wL?4$n z0W?7X<>$zlaa9jMYDPx!JVek-maUcRM+lUw~FbRIt@=w#1^=%jsJsWma3|jiIItm zOAI4iKMQ#V{6EAX$iWcGP|wV$#K0uT$SlbC{|JLD&<9LR%plLe00$=*-N`DguE7H?BLkg|!dI*&;@Z2CEdcbhG{7p!9cz+k;DYscdo461v=e=sn<{n5Z+ zv?qW;TFb@h0z(ALouz9O7}CFozIt#qaEXI-*A3&ty^K7Irbj$;KU=9+4|ISCveghL zfbC@xfVqbOk1A#qRptT=jLgi8Y)l-?oDkiNOw25-Z0rmifCbJp)T>O*V|JM;v{cCR zmH%FkJ@2`gChU^E8oqqW+{ZW7_MHEvk#*|UXRT;GldGjauFEi9-MNC-gY&n1_P;}0 zyFUMIp4y}o^h}GjK)JJI_0P`LylFH)vhex5$im}d>r%afwomdeet2xMl1!WGk=kP_Mq74VUaPY(Bg*f? zl9e_G*%wOP+?{E9ue&?({-z*3hCUAA^NKe7%5&#`$yJ?pPIr^D$n>T>tBXaZ_PaTl zzi+Bv>}T=#oW$kAh)XKY6OQcY-*Ki*#(S;Rt*)@v1@FBk zxf?h>VKfx zTz$W}*PhzGeB=C-lV*?KvpDWhuxh^H(KhLq_}|8=Rp-lA2Codcud>@H_>!3aQNJ~7 zetdhI<-0qwJ)&dN^u@o<3E6!Sk^fNN@cMH2UC!HSUw7+%pRHl?Ys-lPIxk-Q)!Q$h{4^ws&#E{<));w87(^|aQE2E%c$JHlRc8_z>dPT z=dG^qSS(_9@=@vo{#BC>+BZJyKdSYyaGllql;5j(H#%iBJ>;IQv1Hn#l zHJtrU@N@XL;MMtc|Acqf@0z%$@y=_}H-F|#+2-`_vY*#)$FKhxR`o{CR(n((_-tv& zBhMYnlxLWlHcQAa=53f{*Zt^9#l=SkXCl^^o#s_g+&0_8Nnqjy6+Q=ke^9m;L@JlS WIXVDRI4}Zpt1})2zyb}V;3fbY{5oL( diff --git a/ion/src/simulator/assets/small_squircle.png b/ion/src/simulator/assets/small_squircle.png new file mode 100644 index 0000000000000000000000000000000000000000..9e7583a4f9a5c4a633116074193f1c801f4d1c84 GIT binary patch literal 11028 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 M)mth>N>)Mt1?j}q`v3p{ literal 0 HcmV?d00001 diff --git a/ion/src/simulator/assets/vertical_arrow.jpg b/ion/src/simulator/assets/vertical_arrow.jpg deleted file mode 100644 index b6e34cf1dd4f397eba4c6d236c50789ab285d2a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1527 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$idLS5Y5b}#K0uT$SlbC{|JLD&<9LR%plLe00$>4I}*-N`Dat(FP?sGJG(94Th1kY)tm~|vy19p-wHq4u+j2*m*4ZiW zhfjeT^>pq51~oP&eTJ&_!Lh;d0h}9CPUJ@Zb(m;&|4!x#e)$5qy_=2F>eYen5J7eU zL_64eCIOhU81SfKMp0!gz`)4D$OsH4c20=D7=iv_Wn*XH5Hu7JQZxuGQgBL2ZagF+ zs^aL9xJg({TuC{guzAuVsE--#8D3nun|0vJA_+^@#j zwja21X6Et5kKOFdV=6fg*lYCXKCg2y+i}L|PGfe_clE6sgLcj=maYD};*0Z!`Ed`b zSY8Uwk(qXQRj%`+NoGgBcV6RhJhrcW#_9Qz?Avr#8b<|uc=TR$Nu|ea@A7>|ik>H5 zel9q%@74|{L6tS@c3ysNyJo)K$)K<)Yq_|*8Wft32-NaE{NB>N^uqqKjgzkQ?25K4 zdtkE8Z|9@UuavW+v#n1{PWvuAf3{t8>-8sgo3_hD{yu3p?|t>jn+Zk_9!1XCp87~z zaamlXcG>2WkB)blC4N&iIJ4rs;x##^n9O5WL|PXncLaI7QRs7D99GK5H$CCn&6o}C z@;Ph@?&|HA)T}si<>DjbPTYF;>az0ojeB>^c(PjZ{Mw)VJF3IB&a-EHV99vrUFjOW z)469g_fHFZ%)8E2dYxRZ`qmr!t*p$?pPRGm-0KZ9*8lBl(u%2No;X{F^*_VIeXk`> zANCs@X5-mrfFwvMp?j&83(`TFVMA@W6$;2 z9p|}1a_OTx+QNFvb~V52UZPMVeD}q)T?dbs>=s_Ksg!#n=SrSQn_qEqzv6Ey3+lSE zbj75lLY^Phcjx6S3y{uzs;WGJ{r3Z>jz>aer?tYJr#zXGr?Ry4+hird2N9(n@4oDI zTwAOo)~%A*D)x+1)&A9lZTn)kZ$C8W=-0U1(>@cKl@f(-g|Gfy(s295&)+KomPYz^ z#IJ{uaMFJzJMgy6aTGV&k)L;cXvg zg_wpjPG#cG{Pk*`xb%9et)O$_4}`Oi|H&k`@-YeUU$`^k89$U zXJ_(7D^^b`TfThNu~!L!mkwpA)~E#?$#~1h=_LFjYu)=9e5={j{kyXByXq%h6JWer z!G8JV#ckQU+*Ut&XYc3_QgWDUd*F;j{)vK5&blj)$~QUQ7P|}7v+23C=na=2XS6?>Y5d|=sA&4n5VE4O z?6U9K42`+HxhuYD?3&Rerk(V4BCGoC$OqfYI>mHr!W12Qu2ifsn|3pG3fpZbAq$qJ jnhzLc?LozcAX4!LE>8j=g$pAvr#s_O04x_l3T^@b^L9~^ diff --git a/ion/src/simulator/assets/vertical_arrow.png b/ion/src/simulator/assets/vertical_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..05672b9a54894b093fe25da4ef27b87711ee3f7a GIT binary patch literal 11157 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 FzX0Hr5Z?d* literal 0 HcmV?d00001 diff --git a/ion/src/simulator/macos/images.m b/ion/src/simulator/macos/images.m index 57f839e3e..03c82ab51 100644 --- a/ion/src/simulator/macos/images.m +++ b/ion/src/simulator/macos/images.m @@ -3,7 +3,7 @@ #include #include -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { NSImage * nsImage = [NSImage imageNamed:[NSString stringWithUTF8String:identifier]]; CGImageRef cgImage = [nsImage CGImageForProposedRect:NULL context:NULL @@ -15,11 +15,12 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi size_t height = CGImageGetHeight(cgImage); size_t bytesPerPixel = 4; - size_t bitsPerPixel = bytesPerPixel*8; size_t bytesPerRow = bytesPerPixel * width; size_t bitsPerComponent = 8; - void * bitmapData = malloc(height * width * bytesPerPixel); + size_t size = height * width * bytesPerPixel; + void * bitmapData = malloc(size); + memset(bitmapData, 0, size); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate( @@ -33,21 +34,24 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi CGContextRelease(context); CGColorSpaceRelease(colorSpace); - SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( - bitmapData, - width, - height, - bitsPerPixel, - bytesPerRow, - SDL_PIXELFORMAT_ABGR8888); + SDL_Texture * texture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STATIC, + width, + height + ); - SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); - SDL_SetSurfaceAlphaMod(surface, alpha); + SDL_UpdateTexture( + texture, + NULL, + bitmapData, + bytesPerPixel * width + ); - SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); free(bitmapData); - SDL_FreeSurface(surface); return texture; } diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index eaec0ac4e..53442cd27 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -16,15 +16,15 @@ $(simulator_app_binary): $(foreach arch,$(ARCHS),$(BUILD_DIR)/$(arch)/%.bin) | $ # Background & Keys images -define rule_for_jpg_asset -simulator_app_deps += $(call simulator_app_resource,$(1).jpg) -$(call simulator_app_resource,$(1).jpg): ion/src/simulator/assets/$(1).jpg | $$$$(@D)/. +define rule_for_asset +simulator_app_deps += $(call simulator_app_resource,$(1)) +$(call simulator_app_resource,$(1)): ion/src/simulator/assets/$(1) | $$$$(@D)/. $(call rule_label,COPY) $(Q) cp $$^ $$@ endef -JPG_ASSETS = background horizontal_arrow large_squircle round small_squircle vertical_arrow -$(foreach ASSET,$(JPG_ASSETS),$(eval $(call rule_for_jpg_asset,$(ASSET)))) +ASSETS = background.jpg horizontal_arrow.png large_squircle.png round.png small_squircle.png vertical_arrow.png +$(foreach ASSET,$(ASSETS),$(eval $(call rule_for_asset,$(ASSET)))) # Process icons @@ -45,4 +45,4 @@ $(addprefix $(SIMULATOR_ICONSET)/,icon_%.png): ion/src/simulator/assets/logo.svg # Export simulator app dependencies simulator_app_deps += $(simulator_app_binary) -simulator_app_deps += $(call simulator_app_plist,Info.plist) \ No newline at end of file +simulator_app_deps += $(call simulator_app_plist,Info.plist) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index 890ce13a6..a41c39e82 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -103,11 +103,11 @@ public: }; static constexpr size_t NumberOfShapes = (size_t)Shape::NumberOfShapes; static constexpr const char * imagePathForKey[KeyLayout::NumberOfShapes] = { - "horizontal_arrow.jpg", - "vertical_arrow.jpg", - "round.jpg", - "small_squircle.jpg", - "large_squircle.jpg" + "horizontal_arrow.png", + "vertical_arrow.png", + "round.png", + "small_squircle.png", + "large_squircle.png" }; constexpr KeyLayout(float x, float y, Shape shape) : @@ -198,14 +198,13 @@ static void getKeyRectangle(int validKeyIndex, SDL_Texture * texture, SDL_Rect * makeAbsolute(fRect, rect); } -static constexpr uint8_t k_blendingRatio = 0x44; static SDL_Texture * sBackgroundTexture = nullptr; static SDL_Texture * sKeyLayoutTextures[KeyLayout::NumberOfShapes]; void init(SDL_Renderer * renderer) { - sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg", false, 0xFF); + sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); for (size_t i = 0; i < KeyLayout::NumberOfShapes; i++) { - sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i], true, k_blendingRatio); + sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i]); } } diff --git a/ion/src/simulator/shared/platform.h b/ion/src/simulator/shared/platform.h index ce566a4a9..494c83c23 100644 --- a/ion/src/simulator/shared/platform.h +++ b/ion/src/simulator/shared/platform.h @@ -11,7 +11,7 @@ extern "C" { /* Those functions should be implemented per-platform. * They are defined as C function for easier interop. */ -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha); +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier); char * IonSimulatorGetLanguageCode(); #if EPSILON_SDL_SCREEN_ONLY From d2632bff4fda53b90ed1fef905913f036c16a209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 11:54:12 +0200 Subject: [PATCH 46/67] [ion] Simulator: key layouts assets are PNG instead of JPG II (fix android) --- ion/src/simulator/android/src/cpp/images.cpp | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ion/src/simulator/android/src/cpp/images.cpp b/ion/src/simulator/android/src/cpp/images.cpp index 02fd9f5e7..12067cba4 100644 --- a/ion/src/simulator/android/src/cpp/images.cpp +++ b/ion/src/simulator/android/src/cpp/images.cpp @@ -3,7 +3,7 @@ #include #include -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); jobject activity = static_cast(SDL_AndroidGetActivity()); @@ -27,21 +27,25 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi size_t bytesPerPixel = 4; size_t bitsPerPixel = bytesPerPixel*8; - SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( - bitmapPixels, + + SDL_Texture * texture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STATIC, bitmapInfo.width, - bitmapInfo.height, - bitsPerPixel, - bytesPerPixel * bitmapInfo.width, - SDL_PIXELFORMAT_ABGR8888); + bitmapInfo.height + ); - SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); - SDL_SetSurfaceAlphaMod(surface, alpha); + SDL_UpdateTexture( + texture, + nullptr, + bitmapPixels, + bytesPerPixel * bitmapInfo.width + ); - SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); AndroidBitmap_unlockPixels(env, j_bitmap); - SDL_FreeSurface(surface); return texture; } From 1946c68ef87c88e94887435b445b0ff488ed0a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 11:54:33 +0200 Subject: [PATCH 47/67] [ion] Simulator: key layouts assets are PNG instead of JPG III (fix ios) --- ion/src/simulator/ios/images.m | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ion/src/simulator/ios/images.m b/ion/src/simulator/ios/images.m index 1830fc617..8165b0f2b 100644 --- a/ion/src/simulator/ios/images.m +++ b/ion/src/simulator/ios/images.m @@ -3,7 +3,7 @@ #include #include -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { CGImageRef cgImage = [[UIImage imageNamed:[NSString stringWithUTF8String:identifier]] CGImage]; if (cgImage == NULL) { return NULL; @@ -17,7 +17,9 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi size_t bytesPerRow = bytesPerPixel * width; size_t bitsPerComponent = 8; - void * bitmapData = malloc(height * width * bytesPerPixel); + size_t size = height * width * bytesPerPixel; + void * bitmapData = malloc(size); + memset(bitmapData, 0, size); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate( @@ -31,21 +33,24 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi CGContextRelease(context); CGColorSpaceRelease(colorSpace); - SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( - bitmapData, + SDL_Texture * texture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_ABGR8888, + SDL_TEXTUREACCESS_STATIC, width, - height, - bitsPerPixel, - bytesPerRow, - SDL_PIXELFORMAT_ABGR8888); + height + ); - SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); - SDL_SetSurfaceAlphaMod(surface, alpha); + SDL_UpdateTexture( + texture, + NULL, + bitmapData, + bytesPerPixel * width + ); - SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); free(bitmapData); - SDL_FreeSurface(surface); return texture; } From 165b129385114e198c49c8a9c80ff0510e0274d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 11:54:46 +0200 Subject: [PATCH 48/67] [ion] Simulator: key layouts assets are PNG instead of JPG IV (fix linux) --- ion/src/simulator/linux/Makefile | 14 +-- ion/src/simulator/linux/assets.py | 24 +++-- ion/src/simulator/linux/images.cpp | 155 ++++++++++++++++++++++------- 3 files changed, 141 insertions(+), 52 deletions(-) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 50387ed01..4b2917d05 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -27,20 +27,20 @@ endif LDFLAGS += -ljpeg -jpg_assets = background horizontal_arrow vertical_arrow round small_squircle large_squircle +assets = background.jpg horizontal_arrow.png vertical_arrow.png round.png small_squircle.png large_squircle.png -jpg_images = $(foreach i,$(jpg_assets),ion/src/simulator/assets/$(i).jpg) +assets_paths = $(foreach i,$(assets),ion/src/simulator/assets/$(i)) $(eval $(call rule_for, \ LINUX_ASSETS, \ ion/src/simulator/linux/assets.s, \ - $(jpg_images), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(jpg_assets) --implementation $$@, \ + $(assets_paths), \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --implementation $$@, \ global \ )) -assets_address_ranges_declaration = $(foreach i,$(jpg_assets),extern unsigned char _ion_simulator_$(i)_start;) -assets_address_ranges_declaration += $(foreach i,$(jpg_assets),extern unsigned char _ion_simulator_$(i)_end;) -assets_address_ranges_definition = $(foreach i,$(jpg_assets), {"$(i).jpg", &_ion_simulator_$(i)_start, &_ion_simulator_$(i)_end},) +assets_address_ranges_declaration = $(foreach i,$(assets),extern unsigned char _ion_simulator_$(basename $(i))_start;) +assets_address_ranges_declaration += $(foreach i,$(assets),extern unsigned char _ion_simulator_$(basename $(i))_end;) +assets_address_ranges_definition = $(foreach i,$(assets), {"$(i)", &_ion_simulator_$(basename $(i))_start, &_ion_simulator_$(basename $(i))_end},) $(call object_for,ion/src/simulator/linux/images.cpp): CXXFLAGS += -DASSETS_ADDRESS_RANGES_DECLARATION='$(assets_address_ranges_declaration)' -DASSETS_ADDRESS_RANGES_DEFINITION='$(assets_address_ranges_definition)' diff --git a/ion/src/simulator/linux/assets.py b/ion/src/simulator/linux/assets.py index 31fd20f68..dc5b72953 100644 --- a/ion/src/simulator/linux/assets.py +++ b/ion/src/simulator/linux/assets.py @@ -1,27 +1,29 @@ -# This script generates a .s file representing jpg assets in order to access +# This script generates a .s file representing assets in order to access # them from C code import sys import re import argparse import io +import os -parser = argparse.ArgumentParser(description="Process some jpg files.") -parser.add_argument('--files', nargs='+', help='a list of jpg file names') +parser = argparse.ArgumentParser(description="Process some asset files.") +parser.add_argument('--files', nargs='+', help='a list of file names') parser.add_argument('--implementation', help='the .s file to generate') args = parser.parse_args() -def print_jpg(f, jpg): - f.write(".global _ion_simulator_" + jpg + "_start\n") - f.write(".global _ion_simulator_" + jpg + "_end\n") - f.write("_ion_simulator_" + jpg + "_start:\n") - f.write(" .incbin \"ion/src/simulator/assets/" + jpg + ".jpg\"\n") - f.write("_ion_simulator_" + jpg + "_end:\n\n") +def print_asset(f, asset): + asset_basename = os.path.splitext(asset)[0] + f.write(".global _ion_simulator_" + asset_basename + "_start\n") + f.write(".global _ion_simulator_" + asset_basename + "_end\n") + f.write("_ion_simulator_" + asset_basename + "_start:\n") + f.write(" .incbin \"ion/src/simulator/assets/" + asset + "\"\n") + f.write("_ion_simulator_" + asset_basename + "_end:\n\n") def print(files, path): f = open(path, "w") - for jpg in files: - print_jpg(f, jpg) + for asset in files: + print_asset(f, asset) f.close() diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index 632a0bcbf..694767b65 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #ifndef ASSETS_ADDRESS_RANGES_DECLARATION @@ -17,66 +18,152 @@ static struct { ASSETS_ADDRESS_RANGES_DEFINITION }; -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { +enum class AssetFormat { + JPG, + PNG +}; + +png_size_t readSize = 0; + +void ReadDataFromMemory(png_structp png, png_bytep outBytes, png_size_t byteSize) { + png_voidp io = png_get_io_ptr(png); + memcpy((char *)outBytes, (char *)io + readSize, byteSize); + readSize += byteSize; +} + +bool readPNG(unsigned char * start, size_t size, unsigned char ** bitmapData, unsigned int * width, unsigned int * height, unsigned int * bytesPerPixel, Uint32 * pixelFormat) { + readSize = 0; + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) { return false; } // Memory failure + png_infop info = png_create_info_struct(png); + if (!info) { + // Memory failure + png_destroy_read_struct(&png, NULL, NULL); + return false; + } + static constexpr size_t k_pngSignatureLength = 8; + if(png_sig_cmp((png_const_bytep)start, 0, k_pngSignatureLength) != 0) { return false; } // Corrupted PNG + + png_set_read_fn(png, start, ReadDataFromMemory); + + png_read_info(png, info); + + int bitDepth = 0; + int colorType = -1; + png_get_IHDR(png, info, width, height, &bitDepth, &colorType, nullptr, nullptr, nullptr); + *bytesPerPixel = bitDepth*4/8; + + *pixelFormat = SDL_PIXELFORMAT_ARGB8888; + assert(colorType == PNG_COLOR_TYPE_RGB_ALPHA); + + const png_uint_32 bytesPerRow = png_get_rowbytes(png, info); + *bitmapData = new unsigned char[*height * bytesPerRow]; + unsigned char * rowData = *bitmapData; + // read single row at a time + for(unsigned int rowIndex = 0; rowIndex < *height; rowIndex++) { + png_read_row(png, (png_bytep)rowData, nullptr); + rowData += bytesPerRow; + } + + png_destroy_read_struct(&png, &info, nullptr); + return true; +} + +bool readJPG(const unsigned char * start, size_t size, unsigned char ** bitmapData, unsigned int * width, unsigned int * height, unsigned int * bytesPerPixel, Uint32 * pixelFormat) { + *pixelFormat = SDL_PIXELFORMAT_RGB24; struct jpeg_decompress_struct info; struct jpeg_error_mgr err; info.err = jpeg_std_error(&err); jpeg_create_decompress(&info); - unsigned char * jpegStart = nullptr; - unsigned long jpegSize = 0; - - for (size_t i = 0; i < sizeof(resources_addresses)/sizeof(resources_addresses[0]); i++) { - if (strcmp(identifier, resources_addresses[i].identifier) == 0) { - jpegStart = resources_addresses[i].start; - jpegSize = resources_addresses[i].end - resources_addresses[i].start; - break; - } - } - assert(jpegStart); - jpeg_mem_src(&info, jpegStart, jpegSize); + jpeg_mem_src(&info, start, size); if (jpeg_read_header(&info, TRUE) != 1) { - return nullptr; + return false; } jpeg_start_decompress(&info); - int width = info.output_width; - int height = info.output_height; - int bytesPerPixel = info.output_components; - int bitsPerPixel = bytesPerPixel*8; - - unsigned char * bitmapData = new unsigned char[height * width * bytesPerPixel]; + *width = info.output_width; + *height = info.output_height; + *bytesPerPixel = info.output_components; + *bitmapData = new unsigned char[*height * *width * *bytesPerPixel]; while (info.output_scanline < info.output_height) { unsigned char * buffer_array[1]; - buffer_array[0] = bitmapData + info.output_scanline * width * bytesPerPixel; + buffer_array[0] = *bitmapData + info.output_scanline * *width * *bytesPerPixel; jpeg_read_scanlines(&info, buffer_array, 1); } jpeg_finish_decompress(&info); jpeg_destroy_decompress(&info); + return true; +} - Uint32 surfacePixelFormat = SDL_PIXELFORMAT_RGB24; - assert(bytesPerPixel == SDL_BYTESPERPIXEL(surfacePixelFormat)); +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { + static constexpr const char * jpgExtension = ".jpg"; + static constexpr const char * pngExtension = ".png"; - SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( - bitmapData, - width, - height, - bitsPerPixel, - width * bytesPerPixel, - surfacePixelFormat); + unsigned char * assetStart = nullptr; + unsigned long assertSize = 0; + AssetFormat format; - SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); - SDL_SetSurfaceAlphaMod(surface, alpha); + // Find the asset corresponding to identifier + for (size_t i = 0; i < sizeof(resources_addresses)/sizeof(resources_addresses[0]); i++) { + if (strcmp(identifier, resources_addresses[i].identifier) == 0) { + if (strcmp(jpgExtension, identifier + strlen(identifier) - strlen(jpgExtension)) == 0) { + format = AssetFormat::JPG; + } else { + assert(strcmp(pngExtension, identifier + strlen(identifier) - strlen(pngExtension)) == 0); + format = AssetFormat::PNG; + } + assetStart = resources_addresses[i].start; + assertSize = resources_addresses[i].end - resources_addresses[i].start; + break; + } + } + assert(assetStart); - SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + unsigned int width; + unsigned int height; + unsigned int bytesPerPixel; + unsigned char * bitmapData; + Uint32 pixelFormat; + + switch (format) { + case AssetFormat::JPG: + if (!readJPG(assetStart, assertSize, &bitmapData, &width, &height, &bytesPerPixel, &pixelFormat)) { + return nullptr; + } + break; + default: + assert(format == AssetFormat::PNG); + if (!readPNG(assetStart, assertSize, &bitmapData, &width, &height, &bytesPerPixel, &pixelFormat)) { + return nullptr; + } + } + + assert(bytesPerPixel == SDL_BYTESPERPIXEL(pixelFormat)); + + SDL_Texture * texture = SDL_CreateTexture( + renderer, + pixelFormat, + SDL_TEXTUREACCESS_STATIC, + width, + height + ); + + SDL_UpdateTexture( + texture, + nullptr, + bitmapData, + width * bytesPerPixel + ); + + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); delete[] bitmapData; - SDL_FreeSurface(surface); return texture; } From 22b585057d41e4be89fb53b6bf951e1e1029c60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 16:27:22 +0200 Subject: [PATCH 49/67] [ion] Simulator: key layouts assets are PNG instead of JPG V (fix windows) --- ion/src/simulator/windows/images.cpp | 27 ++++++++++++++------------ ion/src/simulator/windows/resources.rc | 10 +++++----- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 923b16d9a..540ee7ead 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -36,7 +36,7 @@ HRESULT CreateStreamOnResource(const char * name, LPSTREAM * stream) { return hr; } -SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier, bool withTransparency, uint8_t alpha) { +SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr); @@ -62,25 +62,28 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi image->LockBits(&rc, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, bitmapData); size_t bytesPerPixel = 4; - size_t bitsPerPixel = bytesPerPixel*8; - SDL_Surface * surface = SDL_CreateRGBSurfaceWithFormatFrom( - bitmapData->Scan0, + + SDL_Texture * texture = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STATIC, width, - height, - bitsPerPixel, - bytesPerPixel*width, - SDL_PIXELFORMAT_ARGB8888); + height + ); - SDL_SetColorKey(surface, withTransparency, SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF)); - SDL_SetSurfaceAlphaMod(surface, alpha); + SDL_UpdateTexture( + texture, + NULL, + bitmapData->Scan0, + bytesPerPixel * width + ); - SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); image->UnlockBits(bitmapData); delete bitmapData; delete image; Gdiplus::GdiplusShutdown(gdiplusToken); - SDL_FreeSurface(surface); return texture; } diff --git a/ion/src/simulator/windows/resources.rc b/ion/src/simulator/windows/resources.rc index 0dd6b9368..3fa2840ce 100644 --- a/ion/src/simulator/windows/resources.rc +++ b/ion/src/simulator/windows/resources.rc @@ -1,9 +1,9 @@ 300 RCDATA "../assets/background.jpg" -301 RCDATA "../assets/horizontal_arrow.jpg" -302 RCDATA "../assets/large_squircle.jpg" -303 RCDATA "../assets/round.jpg" -304 RCDATA "../assets/small_squircle.jpg" -305 RCDATA "../assets/vertical_arrow.jpg" +301 RCDATA "../assets/horizontal_arrow.png" +302 RCDATA "../assets/large_squircle.png" +303 RCDATA "../assets/round.png" +304 RCDATA "../assets/small_squircle.png" +305 RCDATA "../assets/vertical_arrow.png" 1 VERSIONINFO FILEVERSION 1,0,0,0 From 815a06adad1767e1db2c2df07890fe6a7b7acb05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 17 Sep 2020 16:33:10 +0200 Subject: [PATCH 50/67] [ion] Avoid unhighlighting key when the mouse is still on it. Distinguish with smartphones behaviour. --- ion/src/simulator/shared/events_keyboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index cec2a2081..ce6b3c602 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -200,7 +200,7 @@ Event getPlatformEvent() { SDL_GetMouseState(&p.x, &p.y); Simulator::Layout::highlightKeyAt(&p); } - if (event.type == SDL_MOUSEBUTTONUP) { + if (event.type == SDL_FINGERUP) { Simulator::Layout::unhighlightKey(); } #endif From 40392fff9c5e321ee32b816cd0fb9d6c2e2fc1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 15:41:32 +0200 Subject: [PATCH 51/67] [ion] Simulator: assets declaration is common to all targets --- ion/src/simulator/Makefile | 3 +++ ion/src/simulator/linux/Makefile | 4 ---- ion/src/simulator/shared/apple/helpers.mak | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index cab9b80e5..a63c4470a 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -26,5 +26,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ timing.cpp \ ) +assets = background.jpg horizontal_arrow.png vertical_arrow.png round.png small_squircle.png large_squircle.png +assets_paths = $(foreach i,$(assets),ion/src/simulator/assets/$(i)) + include ion/src/simulator/$(TARGET)/Makefile include ion/src/simulator/external/Makefile diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 4b2917d05..763561232 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -27,10 +27,6 @@ endif LDFLAGS += -ljpeg -assets = background.jpg horizontal_arrow.png vertical_arrow.png round.png small_squircle.png large_squircle.png - -assets_paths = $(foreach i,$(assets),ion/src/simulator/assets/$(i)) - $(eval $(call rule_for, \ LINUX_ASSETS, \ ion/src/simulator/linux/assets.s, \ diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index 53442cd27..9162eeb1c 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -23,8 +23,7 @@ $(call simulator_app_resource,$(1)): ion/src/simulator/assets/$(1) | $$$$(@D)/. $(Q) cp $$^ $$@ endef -ASSETS = background.jpg horizontal_arrow.png large_squircle.png round.png small_squircle.png vertical_arrow.png -$(foreach ASSET,$(ASSETS),$(eval $(call rule_for_asset,$(ASSET)))) +$(foreach asset,$(assets),$(eval $(call rule_for_asset,$(asset)))) # Process icons From c96efa76b9887793ab95a1a70cbd6650d0b7c083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 15:43:32 +0200 Subject: [PATCH 52/67] [ion] Windows Makefile: build .rc from the list of assets --- build/rules.mk | 2 +- ion/src/simulator/linux/Makefile | 4 +- ion/src/simulator/windows/Makefile | 18 ++++++-- ion/src/simulator/windows/assets.py | 59 ++++++++++++++++++++++++++ ion/src/simulator/windows/images.cpp | 2 +- ion/src/simulator/windows/resources.py | 41 ------------------ ion/src/simulator/windows/resources.rc | 7 +-- 7 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 ion/src/simulator/windows/assets.py delete mode 100644 ion/src/simulator/windows/resources.py diff --git a/build/rules.mk b/build/rules.mk index d8a6ccb13..a56ad5b6c 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -56,7 +56,7 @@ $(eval $(call rule_for, \ $(eval $(call rule_for, \ WINDRES, %.o, %.rc, \ - $$(WINDRES) $$< -O coff -o $$@, \ + $$(WINDRES) $$< -Ioutput/release/simulator/windows -O coff -o $$@, \ global \ )) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 763561232..f711cb920 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -29,9 +29,9 @@ LDFLAGS += -ljpeg $(eval $(call rule_for, \ LINUX_ASSETS, \ - ion/src/simulator/linux/assets.s, \ + ion/src/simulator/linux/assets.s ion/src/simulator/linux/images.h, \ $(assets_paths), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --implementation $$@, \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --header $$@ --implementation $$@, \ global \ )) diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 00cc6c9f9..97a2001b3 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -22,12 +22,22 @@ LDFLAGS += -lgdiplus SFLAGS += -I$(BUILD_DIR) $(eval $(call rule_for, \ - WINDOWS_RESOURCES, \ + WINDOWS_ASSETS, \ ion/src/simulator/windows/resources.h, \ - ion/src/simulator/windows/resources.rc, \ - $$(PYTHON) ion/src/simulator/windows/resources.py --resource $$^ --header $$@, \ + $(assets_paths), \ + $$(PYTHON) ion/src/simulator/windows/assets.py --files $$^ --header-resource-rc $$@, \ global \ )) -$(BUILD_DIR)/ion/src/simulator/windows/images.o: $(BUILD_DIR)/ion/src/simulator/windows/resources.h +$(eval $(call rule_for, \ + WINDOWS_ASSETS, \ + ion/src/simulator/windows/resource_mapping.h, \ + $(assets_paths), \ + $$(PYTHON) ion/src/simulator/windows/assets.py --files $$^ --header-resource-mapping $$@, \ + global \ +)) + + +$(BUILD_DIR)/ion/src/simulator/windows/images.o: $(BUILD_DIR)/ion/src/simulator/windows/resource_mapping.h +$(BUILD_DIR)/ion/src/simulator/windows/resources.o: $(BUILD_DIR)/ion/src/simulator/windows/resources.h diff --git a/ion/src/simulator/windows/assets.py b/ion/src/simulator/windows/assets.py new file mode 100644 index 000000000..9a7895360 --- /dev/null +++ b/ion/src/simulator/windows/assets.py @@ -0,0 +1,59 @@ +# This script generates two headers: +# - the list of the resources to be included in resources.rc +# - the mapping of the resource names and identifiers + +import sys +import re +import argparse +import io + +parser = argparse.ArgumentParser(description="Process some windows resources.") +parser.add_argument('--files', nargs='+', help='a list of file names') +parser.add_argument('--header-resource-rc', help='the .h file to generate to be included in .rc') +parser.add_argument('--header-resource-mapping', help='the .h file to generate mapping resource names and identifiers') +args = parser.parse_args() + +def process_line(f, line): + rc_re = re.compile('^(\d{1,4}) RCDATA "\.\./assets/(.*)"') + rc_match = rc_re.match(line) + if rc_match: + f.write('{"' + rc_match.groups()[1] + '", ' + rc_match.groups()[0] + '},\n') + return True + return False + +identifier = 300 + +def print_declaration(f, asset, identifier): + f.write(str(identifier) + " RCDATA " + "../assets/" + asset + "\n") + +def print_mapping(f, asset, identifier): + f.write('{"' + asset + '", ' + str(identifier) + '},\n') + +def print_mapping_header(f): + f.write("#ifndef ION_SIMULATOR_WINDOWS_RESOURCES_H\n") + f.write("#define ION_SIMULATOR_WINDOWS_RESOURCES_H\n\n") + f.write("// This file is auto-generated by assets.py\n\n") + f.write("constexpr struct {const char * identifier; int id; } resourcesIdentifiers[] = {\n") + +def print_mapping_footer(f): + f.write("};\n\n") + f.write("#endif\n") + +def print(files, path, print_header, print_footer, process_asset): + f = open(path, "w") + print_header(f) + identifier = 300 + for asset in files: + process_asset(f, asset, identifier) + identifier += 1 + print_footer(f) + f.close() + + +if (args.header_resource_rc): + print(args.files, args.header_resource_rc, lambda f: None, lambda f: None, print_declaration) + +if (args.header_resource_mapping): + print(args.files, args.header_resource_mapping, print_mapping_header, print_mapping_footer, print_mapping) + + diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 540ee7ead..7ef6e5ee6 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -1,5 +1,5 @@ #include "../shared/platform.h" -#include +#include #include #include diff --git a/ion/src/simulator/windows/resources.py b/ion/src/simulator/windows/resources.py deleted file mode 100644 index 47298ed20..000000000 --- a/ion/src/simulator/windows/resources.py +++ /dev/null @@ -1,41 +0,0 @@ -# This script generates a .h file representing available resources to access -# them from C code - -import sys -import re -import argparse -import io - -parser = argparse.ArgumentParser(description="Process some jpg files.") -parser.add_argument('--resource', help='the resources.rc file') -parser.add_argument('--header', help='the .h file to generate') -args = parser.parse_args() - -def process_line(f, line): - rc_re = re.compile('^(\d{1,4}) RCDATA "\.\./assets/(.*)"') - rc_match = rc_re.match(line) - if rc_match: - f.write('{"' + rc_match.groups()[1] + '", ' + rc_match.groups()[0] + '},\n') - return True - return False - -def process_resource(resource, path): - rc = open(resource, "r") - f = open(path, "w") - - f.write("#ifndef ION_SIMULATOR_WINDOWS_RESOURCES_H\n") - f.write("#define ION_SIMULATOR_WINDOWS_RESOURCES_H\n\n") - f.write("// This file is auto-generated by resources.py\n\n") - f.write("constexpr struct {const char * identifier; int id; } resourcesIdentifiers[] = {\n") - - while (process_line(f, rc.readline())): - pass - - f.write("};\n\n") - f.write("#endif\n") - rc.close() - f.close() - -process_resource(args.resource, args.header) - - diff --git a/ion/src/simulator/windows/resources.rc b/ion/src/simulator/windows/resources.rc index 3fa2840ce..0cfd4175d 100644 --- a/ion/src/simulator/windows/resources.rc +++ b/ion/src/simulator/windows/resources.rc @@ -1,9 +1,4 @@ -300 RCDATA "../assets/background.jpg" -301 RCDATA "../assets/horizontal_arrow.png" -302 RCDATA "../assets/large_squircle.png" -303 RCDATA "../assets/round.png" -304 RCDATA "../assets/small_squircle.png" -305 RCDATA "../assets/vertical_arrow.png" +#include 1 VERSIONINFO FILEVERSION 1,0,0,0 From 4620aa8aa89f16be8d94ee99ec1e17364f6c0b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 15:44:42 +0200 Subject: [PATCH 53/67] [ion] Remove useless comment --- ion/include/ion/events.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index a76a80f0c..d3ba86fa6 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -60,7 +60,7 @@ bool isLockActive(); void setLongRepetition(bool longRepetition); bool isLongRepetition(); void updateModifiersFromEvent(Event e); -void didPressNewKey(); // Used for haptic feedback on simulators +void didPressNewKey(); // Plain From 8976ebfc414478052aa387b4a87393859f921813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 16:19:02 +0200 Subject: [PATCH 54/67] [ion] Makefile linux: simplify build system by generating a header --- ion/src/simulator/linux/Makefile | 17 ++++++++----- ion/src/simulator/linux/assets.py | 39 ++++++++++++++++++++++++++---- ion/src/simulator/linux/images.cpp | 13 +--------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index f711cb920..5b05573a8 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -29,14 +29,19 @@ LDFLAGS += -ljpeg $(eval $(call rule_for, \ LINUX_ASSETS, \ - ion/src/simulator/linux/assets.s ion/src/simulator/linux/images.h, \ + ion/src/simulator/linux/assets.s, \ $(assets_paths), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --header $$@ --implementation $$@, \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --implementation $$@, \ global \ )) -assets_address_ranges_declaration = $(foreach i,$(assets),extern unsigned char _ion_simulator_$(basename $(i))_start;) -assets_address_ranges_declaration += $(foreach i,$(assets),extern unsigned char _ion_simulator_$(basename $(i))_end;) -assets_address_ranges_definition = $(foreach i,$(assets), {"$(i)", &_ion_simulator_$(basename $(i))_start, &_ion_simulator_$(basename $(i))_end},) +# The header is refered to as so make sure it's findable this way +SFLAGS += -I$(BUILD_DIR) -$(call object_for,ion/src/simulator/linux/images.cpp): CXXFLAGS += -DASSETS_ADDRESS_RANGES_DECLARATION='$(assets_address_ranges_declaration)' -DASSETS_ADDRESS_RANGES_DEFINITION='$(assets_address_ranges_definition)' +$(eval $(call rule_for, \ + LINUX_ASSETS, \ + ion/src/simulator/linux/images.h, \ + $(assets_paths), \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --header $$@, \ + global \ +)) diff --git a/ion/src/simulator/linux/assets.py b/ion/src/simulator/linux/assets.py index dc5b72953..0f0494ff3 100644 --- a/ion/src/simulator/linux/assets.py +++ b/ion/src/simulator/linux/assets.py @@ -1,5 +1,6 @@ -# This script generates a .s file representing assets in order to access -# them from C code +# This script generates: +# - .s file representing assets in order to access them from C code +# - .h representing the mapping between symbols and assets import sys import re @@ -10,6 +11,7 @@ import os parser = argparse.ArgumentParser(description="Process some asset files.") parser.add_argument('--files', nargs='+', help='a list of file names') parser.add_argument('--implementation', help='the .s file to generate') +parser.add_argument('--header', help='the .h file to generate') args = parser.parse_args() def print_asset(f, asset): @@ -20,12 +22,39 @@ def print_asset(f, asset): f.write(" .incbin \"ion/src/simulator/assets/" + asset + "\"\n") f.write("_ion_simulator_" + asset_basename + "_end:\n\n") -def print(files, path): +def print_assembly(files, path): f = open(path, "w") for asset in files: print_asset(f, asset) - + identifier += 1 f.close() -print(args.files, args.implementation) +def print_declaration(f, asset): + asset_basename = os.path.splitext(asset)[0] + f.write("extern unsigned char _ion_simulator_" + asset_basename + "_start;\n") + f.write("extern unsigned char _ion_simulator_" + asset_basename + "_end;\n") +def print_mapping(f, asset): + f.write('"' + asset + '", &_ion_simulator_' + asset_basename +'_start, &_ion_simulator_' + asset_basename '_end},\n') + +def print_header(files, path): + f = open(path, "w") + f.write("#ifndef ION_SIMULATOR_LINUX_IMAGES_H\n") + f.write("#define ION_SIMULATOR_LINUX_IMAGES_H\n\n") + f.write("// This file is auto-generated by assets.py\n\n") + + for asset in files: + print_declaration(f, asset) + + f.write("\nstatic struct { const char * identifier; unsigned char * start; unsigned char * end; } resources_addresses[] = {\n") + for asset in files: + print_mapping(f, asset) + + f.write("};\n\n") + f.write("#endif\n") + f.close() + +if (args.implementation): + print_assembly(args.files, args.implementation) +if (args.header): + print_header(args.files, args.implementation) diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index 694767b65..43efcbfce 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -5,18 +5,7 @@ #include #include -#ifndef ASSETS_ADDRESS_RANGES_DECLARATION -#error Missing assets adress range declarations -#endif - -ASSETS_ADDRESS_RANGES_DECLARATION -static struct { - const char * identifier; - unsigned char * start; - unsigned char * end; -} resources_addresses[] = { - ASSETS_ADDRESS_RANGES_DEFINITION -}; +#include enum class AssetFormat { JPG, From 308b321ead0079599147a6a29ca35f727f5c4df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 17:18:04 +0200 Subject: [PATCH 55/67] [ion] Clean windows flag --- build/rules.mk | 2 +- ion/src/simulator/windows/Makefile | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/build/rules.mk b/build/rules.mk index a56ad5b6c..9089cebce 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -56,7 +56,7 @@ $(eval $(call rule_for, \ $(eval $(call rule_for, \ WINDRES, %.o, %.rc, \ - $$(WINDRES) $$< -Ioutput/release/simulator/windows -O coff -o $$@, \ + $$(WINDRES) $$(WRFLAGS) $$< -O coff -o $$@, \ global \ )) diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 97a2001b3..12a4124ea 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -16,11 +16,14 @@ ion_src += ion/src/simulator/shared/dummy/telemetry_init.cpp ion_src += ion/src/shared/telemetry_console.cpp endif -LDFLAGS += -lgdiplus +# RC file dependencies +$(call object_for,ion/src/simulator/windows/resources.rc): WRFLAGS += -I $(BUILD_DIR) -# The header is refered to as so make sure it's findable this way +# The header of images is refered to as so make sure it's findable this way SFLAGS += -I$(BUILD_DIR) +LDFLAGS += -lgdiplus + $(eval $(call rule_for, \ WINDOWS_ASSETS, \ ion/src/simulator/windows/resources.h, \ From 7535abc29811482d8a95b0a35f9e335261e60fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 17:30:24 +0200 Subject: [PATCH 56/67] [ion] Change name of variable in Makefile --- ion/src/simulator/Makefile | 4 ++-- ion/src/simulator/linux/Makefile | 8 ++++---- ion/src/simulator/shared/apple/helpers.mak | 2 +- ion/src/simulator/windows/Makefile | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index a63c4470a..10f10fb1e 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -26,8 +26,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ timing.cpp \ ) -assets = background.jpg horizontal_arrow.png vertical_arrow.png round.png small_squircle.png large_squircle.png -assets_paths = $(foreach i,$(assets),ion/src/simulator/assets/$(i)) +ion_simulator_assets = background.jpg horizontal_arrow.png vertical_arrow.png round.png small_squircle.png large_squircle.png +ion_simulator_assets_paths = $(add_prefix ion/src/simulator/assets/,$(ion_simulator_assets)) include ion/src/simulator/$(TARGET)/Makefile include ion/src/simulator/external/Makefile diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 5b05573a8..4eb394f12 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -30,8 +30,8 @@ LDFLAGS += -ljpeg $(eval $(call rule_for, \ LINUX_ASSETS, \ ion/src/simulator/linux/assets.s, \ - $(assets_paths), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --implementation $$@, \ + $(ion_simulator_assets_paths), \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(ion_simulator_assets) --implementation $$@, \ global \ )) @@ -41,7 +41,7 @@ SFLAGS += -I$(BUILD_DIR) $(eval $(call rule_for, \ LINUX_ASSETS, \ ion/src/simulator/linux/images.h, \ - $(assets_paths), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(assets) --header $$@, \ + $(ion_simulator_assets_paths), \ + $$(PYTHON) ion/src/simulator/linux/assets.py --files $(ion_simulator_assets) --header $$@, \ global \ )) diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index 9162eeb1c..85e4b4f86 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -23,7 +23,7 @@ $(call simulator_app_resource,$(1)): ion/src/simulator/assets/$(1) | $$$$(@D)/. $(Q) cp $$^ $$@ endef -$(foreach asset,$(assets),$(eval $(call rule_for_asset,$(asset)))) +$(foreach asset,$(ion_simulator_assets),$(eval $(call rule_for_asset,$(asset)))) # Process icons diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 12a4124ea..fa5e72b70 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -27,16 +27,16 @@ LDFLAGS += -lgdiplus $(eval $(call rule_for, \ WINDOWS_ASSETS, \ ion/src/simulator/windows/resources.h, \ - $(assets_paths), \ - $$(PYTHON) ion/src/simulator/windows/assets.py --files $$^ --header-resource-rc $$@, \ + $(ion_simulator_assets_paths), \ + $$(PYTHON) ion/src/simulator/windows/assets.py --files $(ion_simulator_assets) --header-resource-rc $$@, \ global \ )) $(eval $(call rule_for, \ WINDOWS_ASSETS, \ ion/src/simulator/windows/resource_mapping.h, \ - $(assets_paths), \ - $$(PYTHON) ion/src/simulator/windows/assets.py --files $$^ --header-resource-mapping $$@, \ + $(ion_simulator_assets_paths), \ + $$(PYTHON) ion/src/simulator/windows/assets.py --files $(ion_simulator_assets) --header-resource-mapping $$@, \ global \ )) From 33425e04c02fead8c09891462669605f934992ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 17:59:38 +0200 Subject: [PATCH 57/67] [ion] Coding style --- ion/src/simulator/linux/images.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index 43efcbfce..f1fae5edf 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -23,7 +23,10 @@ void ReadDataFromMemory(png_structp png, png_bytep outBytes, png_size_t byteSize bool readPNG(unsigned char * start, size_t size, unsigned char ** bitmapData, unsigned int * width, unsigned int * height, unsigned int * bytesPerPixel, Uint32 * pixelFormat) { readSize = 0; png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png) { return false; } // Memory failure + if (!png) { + // Memory failure + return false; + } png_infop info = png_create_info_struct(png); if (!info) { // Memory failure @@ -31,7 +34,10 @@ bool readPNG(unsigned char * start, size_t size, unsigned char ** bitmapData, un return false; } static constexpr size_t k_pngSignatureLength = 8; - if(png_sig_cmp((png_const_bytep)start, 0, k_pngSignatureLength) != 0) { return false; } // Corrupted PNG + if (png_sig_cmp((png_const_bytep)start, 0, k_pngSignatureLength) != 0) { + // Corrupted PNG + return false; + } png_set_read_fn(png, start, ReadDataFromMemory); @@ -48,9 +54,8 @@ bool readPNG(unsigned char * start, size_t size, unsigned char ** bitmapData, un const png_uint_32 bytesPerRow = png_get_rowbytes(png, info); *bitmapData = new unsigned char[*height * bytesPerRow]; unsigned char * rowData = *bitmapData; - // read single row at a time - for(unsigned int rowIndex = 0; rowIndex < *height; rowIndex++) { - png_read_row(png, (png_bytep)rowData, nullptr); + for (unsigned int rowIndex = 0; rowIndex < *height; rowIndex++) { + png_read_row(png, static_cast(rowData), nullptr); rowData += bytesPerRow; } From 5da82d84237f2e01d3377509ebde9d5f2c35c227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 18:00:08 +0200 Subject: [PATCH 58/67] [ion] Add comment --- ion/src/simulator/shared/events_keyboard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index ce6b3c602..26d1ba208 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -200,6 +200,8 @@ Event getPlatformEvent() { SDL_GetMouseState(&p.x, &p.y); Simulator::Layout::highlightKeyAt(&p); } + /* On smarphones, don't forget to unhighlight the key when the finger is up. + * (finger up doesn't imply a mouse motion!) */ if (event.type == SDL_FINGERUP) { Simulator::Layout::unhighlightKey(); } From 560c4b3821f87e49e7c4bb3b4f93cec4ec6e4124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 18:00:26 +0200 Subject: [PATCH 59/67] [ion] Linux: improve incbin.py script --- ion/src/simulator/linux/Makefile | 16 +++------- ion/src/simulator/linux/images.cpp | 6 ++-- .../simulator/linux/{assets.py => incbin.py} | 32 ++++++++++++------- 3 files changed, 29 insertions(+), 25 deletions(-) rename ion/src/simulator/linux/{assets.py => incbin.py} (56%) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 4eb394f12..4eec77717 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -28,20 +28,14 @@ endif LDFLAGS += -ljpeg $(eval $(call rule_for, \ - LINUX_ASSETS, \ - ion/src/simulator/linux/assets.s, \ + INCBIN, \ + ion/src/simulator/linux/assets.s ion/src/simulator/linux/images.h, \ $(ion_simulator_assets_paths), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(ion_simulator_assets) --implementation $$@, \ + $$(PYTHON) ion/src/simulator/linux/incbin.py $(ion_simulator_assets) -o $$@, \ global \ )) +$(call object_for,ion/src/simulator/linux/images.cpp): $(BUILD_DIR)/ion/src/simulator/linux/images.h + # The header is refered to as so make sure it's findable this way SFLAGS += -I$(BUILD_DIR) - -$(eval $(call rule_for, \ - LINUX_ASSETS, \ - ion/src/simulator/linux/images.h, \ - $(ion_simulator_assets_paths), \ - $$(PYTHON) ion/src/simulator/linux/assets.py --files $(ion_simulator_assets) --header $$@, \ - global \ -)) diff --git a/ion/src/simulator/linux/images.cpp b/ion/src/simulator/linux/images.cpp index f1fae5edf..dbe94b123 100644 --- a/ion/src/simulator/linux/images.cpp +++ b/ion/src/simulator/linux/images.cpp @@ -105,15 +105,15 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi // Find the asset corresponding to identifier for (size_t i = 0; i < sizeof(resources_addresses)/sizeof(resources_addresses[0]); i++) { - if (strcmp(identifier, resources_addresses[i].identifier) == 0) { + if (strcmp(identifier, resources_addresses[i].identifier()) == 0) { if (strcmp(jpgExtension, identifier + strlen(identifier) - strlen(jpgExtension)) == 0) { format = AssetFormat::JPG; } else { assert(strcmp(pngExtension, identifier + strlen(identifier) - strlen(pngExtension)) == 0); format = AssetFormat::PNG; } - assetStart = resources_addresses[i].start; - assertSize = resources_addresses[i].end - resources_addresses[i].start; + assetStart = resources_addresses[i].start(); + assertSize = resources_addresses[i].end() - resources_addresses[i].start(); break; } } diff --git a/ion/src/simulator/linux/assets.py b/ion/src/simulator/linux/incbin.py similarity index 56% rename from ion/src/simulator/linux/assets.py rename to ion/src/simulator/linux/incbin.py index 0f0494ff3..f92eb03c8 100644 --- a/ion/src/simulator/linux/assets.py +++ b/ion/src/simulator/linux/incbin.py @@ -9,9 +9,8 @@ import io import os parser = argparse.ArgumentParser(description="Process some asset files.") -parser.add_argument('--files', nargs='+', help='a list of file names') -parser.add_argument('--implementation', help='the .s file to generate') -parser.add_argument('--header', help='the .h file to generate') +parser.add_argument('assets', metavar='asset', type=str, nargs='+', help='The list of assets to include') +parser.add_argument('-o', help='The file to generate') args = parser.parse_args() def print_asset(f, asset): @@ -26,7 +25,6 @@ def print_assembly(files, path): f = open(path, "w") for asset in files: print_asset(f, asset) - identifier += 1 f.close() def print_declaration(f, asset): @@ -35,18 +33,30 @@ def print_declaration(f, asset): f.write("extern unsigned char _ion_simulator_" + asset_basename + "_end;\n") def print_mapping(f, asset): - f.write('"' + asset + '", &_ion_simulator_' + asset_basename +'_start, &_ion_simulator_' + asset_basename '_end},\n') + asset_basename = os.path.splitext(asset)[0] + f.write('ResourceMap("' + asset + '", &_ion_simulator_' + asset_basename +'_start, &_ion_simulator_' + asset_basename + '_end),\n') def print_header(files, path): f = open(path, "w") f.write("#ifndef ION_SIMULATOR_LINUX_IMAGES_H\n") f.write("#define ION_SIMULATOR_LINUX_IMAGES_H\n\n") - f.write("// This file is auto-generated by assets.py\n\n") + f.write("// This file is auto-generated by incbin.py\n\n") for asset in files: print_declaration(f, asset) - f.write("\nstatic struct { const char * identifier; unsigned char * start; unsigned char * end; } resources_addresses[] = {\n") + f.write("\nclass ResourceMap {\n") + f.write("public:\n") + f.write(" constexpr ResourceMap(const char * identifier, unsigned char * start, unsigned char * end) : m_identifier(identifier), m_start(start), m_end(end) {}\n") + f.write(" const char * identifier() const { return m_identifier; }\n") + f.write(" unsigned char * start() const { return m_start; }\n") + f.write(" unsigned char * end() const { return m_end; }\n") + f.write("private:\n") + f.write(" const char * m_identifier;\n") + f.write(" unsigned char * m_start;\n") + f.write(" unsigned char * m_end;\n") + f.write("};\n\n") + f.write("static constexpr ResourceMap resources_addresses[] = {\n") for asset in files: print_mapping(f, asset) @@ -54,7 +64,7 @@ def print_header(files, path): f.write("#endif\n") f.close() -if (args.implementation): - print_assembly(args.files, args.implementation) -if (args.header): - print_header(args.files, args.implementation) +if (args.o.endswith(".s")): + print_assembly(args.assets, args.o) +if (args.o.endswith(".h")): + print_header(args.assets, args.o) From f4b9635fee3668d94977297cf779215074977d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 18 Sep 2020 18:01:13 +0200 Subject: [PATCH 60/67] [ion] Change variable name --- ion/src/simulator/shared/layout.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/src/simulator/shared/layout.cpp b/ion/src/simulator/shared/layout.cpp index a41c39e82..ef07451f8 100644 --- a/ion/src/simulator/shared/layout.cpp +++ b/ion/src/simulator/shared/layout.cpp @@ -102,7 +102,7 @@ public: NumberOfShapes }; static constexpr size_t NumberOfShapes = (size_t)Shape::NumberOfShapes; - static constexpr const char * imagePathForKey[KeyLayout::NumberOfShapes] = { + static constexpr const char * assetName[KeyLayout::NumberOfShapes] = { "horizontal_arrow.png", "vertical_arrow.png", "round.png", @@ -121,7 +121,7 @@ private: Shape m_shape; }; -constexpr const char * const KeyLayout::imagePathForKey[KeyLayout::NumberOfShapes]; +constexpr const char * const KeyLayout::assetName[KeyLayout::NumberOfShapes]; static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = { KeyLayout(195, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left @@ -204,7 +204,7 @@ static SDL_Texture * sKeyLayoutTextures[KeyLayout::NumberOfShapes]; void init(SDL_Renderer * renderer) { sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg"); for (size_t i = 0; i < KeyLayout::NumberOfShapes; i++) { - sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::imagePathForKey[i]); + sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::assetName[i]); } } From 42cedf5b1046f303541881273afbc4efbe071db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 21 Sep 2020 11:43:40 +0200 Subject: [PATCH 61/67] [ion] Windows: improve resgen.py script --- ion/src/simulator/windows/Makefile | 19 +++------ ion/src/simulator/windows/assets.py | 59 -------------------------- ion/src/simulator/windows/images.cpp | 6 +-- ion/src/simulator/windows/resgen.py | 57 +++++++++++++++++++++++++ ion/src/simulator/windows/resources.rc | 2 +- 5 files changed, 66 insertions(+), 77 deletions(-) delete mode 100644 ion/src/simulator/windows/assets.py create mode 100644 ion/src/simulator/windows/resgen.py diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index fa5e72b70..3fd21fc20 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -25,22 +25,13 @@ SFLAGS += -I$(BUILD_DIR) LDFLAGS += -lgdiplus $(eval $(call rule_for, \ - WINDOWS_ASSETS, \ - ion/src/simulator/windows/resources.h, \ + RESGEN, \ + ion/src/simulator/windows/resources_gen.rc ion/src/simulator/windows/images.h, \ $(ion_simulator_assets_paths), \ - $$(PYTHON) ion/src/simulator/windows/assets.py --files $(ion_simulator_assets) --header-resource-rc $$@, \ + $$(PYTHON) ion/src/simulator/windows/resgen.py $(ion_simulator_assets) -o $$@, \ global \ )) -$(eval $(call rule_for, \ - WINDOWS_ASSETS, \ - ion/src/simulator/windows/resource_mapping.h, \ - $(ion_simulator_assets_paths), \ - $$(PYTHON) ion/src/simulator/windows/assets.py --files $(ion_simulator_assets) --header-resource-mapping $$@, \ - global \ -)) - - -$(BUILD_DIR)/ion/src/simulator/windows/images.o: $(BUILD_DIR)/ion/src/simulator/windows/resource_mapping.h -$(BUILD_DIR)/ion/src/simulator/windows/resources.o: $(BUILD_DIR)/ion/src/simulator/windows/resources.h +$(call object_for,ion/src/simulator/windows/images.cpp): $(BUILD_DIR)/ion/src/simulator/windows/images.h +$(call object_for,ion/src/simulator/windows/resources.rc): $(BUILD_DIR)/ion/src/simulator/windows/resources_gen.rc diff --git a/ion/src/simulator/windows/assets.py b/ion/src/simulator/windows/assets.py deleted file mode 100644 index 9a7895360..000000000 --- a/ion/src/simulator/windows/assets.py +++ /dev/null @@ -1,59 +0,0 @@ -# This script generates two headers: -# - the list of the resources to be included in resources.rc -# - the mapping of the resource names and identifiers - -import sys -import re -import argparse -import io - -parser = argparse.ArgumentParser(description="Process some windows resources.") -parser.add_argument('--files', nargs='+', help='a list of file names') -parser.add_argument('--header-resource-rc', help='the .h file to generate to be included in .rc') -parser.add_argument('--header-resource-mapping', help='the .h file to generate mapping resource names and identifiers') -args = parser.parse_args() - -def process_line(f, line): - rc_re = re.compile('^(\d{1,4}) RCDATA "\.\./assets/(.*)"') - rc_match = rc_re.match(line) - if rc_match: - f.write('{"' + rc_match.groups()[1] + '", ' + rc_match.groups()[0] + '},\n') - return True - return False - -identifier = 300 - -def print_declaration(f, asset, identifier): - f.write(str(identifier) + " RCDATA " + "../assets/" + asset + "\n") - -def print_mapping(f, asset, identifier): - f.write('{"' + asset + '", ' + str(identifier) + '},\n') - -def print_mapping_header(f): - f.write("#ifndef ION_SIMULATOR_WINDOWS_RESOURCES_H\n") - f.write("#define ION_SIMULATOR_WINDOWS_RESOURCES_H\n\n") - f.write("// This file is auto-generated by assets.py\n\n") - f.write("constexpr struct {const char * identifier; int id; } resourcesIdentifiers[] = {\n") - -def print_mapping_footer(f): - f.write("};\n\n") - f.write("#endif\n") - -def print(files, path, print_header, print_footer, process_asset): - f = open(path, "w") - print_header(f) - identifier = 300 - for asset in files: - process_asset(f, asset, identifier) - identifier += 1 - print_footer(f) - f.close() - - -if (args.header_resource_rc): - print(args.files, args.header_resource_rc, lambda f: None, lambda f: None, print_declaration) - -if (args.header_resource_mapping): - print(args.files, args.header_resource_mapping, print_mapping_header, print_mapping_footer, print_mapping) - - diff --git a/ion/src/simulator/windows/images.cpp b/ion/src/simulator/windows/images.cpp index 7ef6e5ee6..1bae507fa 100644 --- a/ion/src/simulator/windows/images.cpp +++ b/ion/src/simulator/windows/images.cpp @@ -1,5 +1,5 @@ #include "../shared/platform.h" -#include +#include #include #include @@ -44,8 +44,8 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi LPSTREAM stream; int resourceID = -1; for (size_t i = 0; i < sizeof(resourcesIdentifiers)/sizeof(resourcesIdentifiers[0]); i++) { - if (strcmp(identifier, resourcesIdentifiers[i].identifier) == 0) { - resourceID = resourcesIdentifiers[i].id; + if (strcmp(identifier, resourcesIdentifiers[i].identifier()) == 0) { + resourceID = resourcesIdentifiers[i].id(); } } assert(resourceID >= 0); diff --git a/ion/src/simulator/windows/resgen.py b/ion/src/simulator/windows/resgen.py new file mode 100644 index 000000000..2f15a6bba --- /dev/null +++ b/ion/src/simulator/windows/resgen.py @@ -0,0 +1,57 @@ +# This script generates two files: +# - A .rc: the list of the resources to be included in resources.rc +# - A C++ header: the mapping of the resource names and identifiers + +import sys +import re +import argparse +import io + +parser = argparse.ArgumentParser(description="Process some windows resources.") +parser.add_argument('assets', metavar='asset', type=str, nargs='+', help='The list of assets') +parser.add_argument('-o', required=True, help='The file to generate') +args = parser.parse_args() + +def print_declaration(f, asset, identifier): + f.write(str(identifier) + ' RCDATA ' + '"../assets/' + asset + '"\n') + +def print_mapping(f, asset, identifier): + f.write('ResourceID("' + asset + '", ' + str(identifier) + '),\n') + +def print_mapping_header(f): + f.write("#ifndef ION_SIMULATOR_WINDOWS_IMAGES_H\n") + f.write("#define ION_SIMULATOR_WINDOWS_IMAGES_H\n\n") + f.write("// This file is auto-generated by resgen.py\n\n") + f.write("\nclass ResourceID {\n") + f.write("public:\n") + f.write(" constexpr ResourceID(const char * identifier, int id) : m_identifier(identifier), m_id(id) {}\n") + f.write(" const char * identifier() const { return m_identifier; }\n") + f.write(" int id() const { return m_id; }\n") + f.write("private:\n") + f.write(" const char * m_identifier;\n") + f.write(" int m_id;\n") + f.write("};\n\n") + f.write("static constexpr ResourceID resourcesIdentifiers[] = {\n") + +def print_mapping_footer(f): + f.write("};\n\n") + f.write("#endif\n") + +def print(files, path, print_header, print_footer, process_asset): + f = open(path, "w") + print_header(f) + identifier = 1000 + for asset in files: + process_asset(f, asset, identifier) + identifier += 1 + print_footer(f) + f.close() + + +if (args.o.endswith(".rc")): + print(args.assets, args.o, lambda f: None, lambda f: None, print_declaration) + +if (args.o.endswith(".h")): + print(args.assets, args.o, print_mapping_header, print_mapping_footer, print_mapping) + + diff --git a/ion/src/simulator/windows/resources.rc b/ion/src/simulator/windows/resources.rc index 0cfd4175d..9994a689a 100644 --- a/ion/src/simulator/windows/resources.rc +++ b/ion/src/simulator/windows/resources.rc @@ -1,4 +1,4 @@ -#include +#include 1 VERSIONINFO FILEVERSION 1,0,0,0 From 595532e0a5a78f156b410edd486815abfedca7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 21 Sep 2020 12:01:57 +0200 Subject: [PATCH 62/67] [ion] Remove useless variables --- ion/src/simulator/android/src/cpp/images.cpp | 1 - ion/src/simulator/ios/images.m | 1 - 2 files changed, 2 deletions(-) diff --git a/ion/src/simulator/android/src/cpp/images.cpp b/ion/src/simulator/android/src/cpp/images.cpp index 12067cba4..4b3cfd85c 100644 --- a/ion/src/simulator/android/src/cpp/images.cpp +++ b/ion/src/simulator/android/src/cpp/images.cpp @@ -26,7 +26,6 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi // TODO: Handle the case where lockPixels fails size_t bytesPerPixel = 4; - size_t bitsPerPixel = bytesPerPixel*8; SDL_Texture * texture = SDL_CreateTexture( renderer, diff --git a/ion/src/simulator/ios/images.m b/ion/src/simulator/ios/images.m index 8165b0f2b..6a68fe2c4 100644 --- a/ion/src/simulator/ios/images.m +++ b/ion/src/simulator/ios/images.m @@ -13,7 +13,6 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi size_t bytesPerPixel = 4; - size_t bitsPerPixel = bytesPerPixel*8; size_t bytesPerRow = bytesPerPixel * width; size_t bitsPerComponent = 8; From 979c227d543867ea68aac0b55570c29a1283b09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 21 Sep 2020 16:06:38 +0200 Subject: [PATCH 63/67] [ion] web: improve key highlight positions --- ion/src/simulator/web/simulator.html.inc | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ion/src/simulator/web/simulator.html.inc b/ion/src/simulator/web/simulator.html.inc index 0c3d93440..b2ed31818 100644 --- a/ion/src/simulator/web/simulator.html.inc +++ b/ion/src/simulator/web/simulator.html.inc @@ -109,17 +109,17 @@ a.action svg { KEYBOARD .nav span { position: absolute; } KEYBOARD .nav .left { - top: 36%; + top: 37%; left: 2%; width: 15%; height: 28%; } KEYBOARD .nav .right { - top: 36%; + top: 37%; left: 17%; width: 15%; height: 28%; } KEYBOARD .nav .top { - top: 7%; + top: 9%; left: 12%; width: 10%; height: 40%; } @@ -131,23 +131,25 @@ a.action svg { KEYBOARD .nav .home { top: 15%; left: 41%; - width: 16%; - height: 33%; } + width: 16.5%; + height: 31%; } KEYBOARD .nav .power { - top: 54%; - left: 42%; - width: 16%; - height: 33%; } + top: 55.4%; + left: 41%; + width: 16.5%; + height: 31%; } KEYBOARD .nav .ok { top: 31%; left: 67%; - width: 13%; - height: 38%; } + width: 13.5%; + height: 38%; + border-radius: 48%; } KEYBOARD .nav .back { top: 31%; - left: 84%; - width: 13%; - height: 38%; } + left: 83.3%; + width: 13.5%; + height: 38%; + border-radius: 48%; } KEYBOARD .functions { position: absolute; top: 26.75%; From 9786db308a46c2bbba507c94dafdbf589d87e3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 22 Sep 2020 17:26:52 +0200 Subject: [PATCH 64/67] [ion] Haptics events don't depend on SCREEN_ONLY --- ion/src/simulator/shared/haptics.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ion/src/simulator/shared/haptics.cpp b/ion/src/simulator/shared/haptics.cpp index a4f45e31a..acb456279 100644 --- a/ion/src/simulator/shared/haptics.cpp +++ b/ion/src/simulator/shared/haptics.cpp @@ -5,12 +5,9 @@ namespace Ion { namespace Simulator { namespace Haptics { -#if !EPSILON_SDL_SCREEN_ONLY static SDL_Haptic * sSDLHaptic = nullptr; -#endif void init() { -#if !EPSILON_SDL_SCREEN_ONLY if (SDL_Init(SDL_INIT_HAPTIC) == 0) { sSDLHaptic = SDL_HapticOpen(0); if (sSDLHaptic) { @@ -19,23 +16,18 @@ void init() { } } } -#endif } void shutdown() { -#if !EPSILON_SDL_SCREEN_ONLY if (sSDLHaptic) { SDL_HapticClose(sSDLHaptic); } -#endif } void perform() { -#if !EPSILON_SDL_SCREEN_ONLY if (sSDLHaptic) { SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40); } -#endif } From 0c0510599db09ad8d42408edc80bce5dfab93523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 23 Sep 2020 14:44:14 +0200 Subject: [PATCH 65/67] [ion] Simulator: haptics feedback are only implemented on Android and depends on the System settings --- ion/src/simulator/android/Makefile | 2 +- ion/src/simulator/android/src/cpp/haptics.cpp | 46 +++++++++++++++++++ .../numworks/calculator/EpsilonActivity.java | 10 ++++ ion/src/simulator/ios/Makefile | 2 +- ion/src/simulator/shared/haptics.cpp | 36 --------------- 5 files changed, 58 insertions(+), 38 deletions(-) create mode 100644 ion/src/simulator/android/src/cpp/haptics.cpp delete mode 100644 ion/src/simulator/shared/haptics.cpp diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index db1f2c006..eeba19dea 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -1,11 +1,11 @@ ion_src += $(addprefix ion/src/simulator/android/src/cpp/, \ images.cpp \ + haptics.cpp \ ) ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/language.cpp \ - haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/android/src/cpp/haptics.cpp b/ion/src/simulator/android/src/cpp/haptics.cpp new file mode 100644 index 000000000..0c1eedfce --- /dev/null +++ b/ion/src/simulator/android/src/cpp/haptics.cpp @@ -0,0 +1,46 @@ +#include "../../../shared/haptics.h" +#include +#include + +namespace Ion { +namespace Simulator { +namespace Haptics { + +static SDL_Haptic * sSDLHaptic = nullptr; + +void init() { + if (SDL_Init(SDL_INIT_HAPTIC) == 0) { + sSDLHaptic = SDL_HapticOpen(0); + if (sSDLHaptic) { + if (SDL_HapticRumbleInit(sSDLHaptic) != 0) { + sSDLHaptic = nullptr; + } + } + } +} + +void shutdown() { + if (sSDLHaptic) { + SDL_HapticClose(sSDLHaptic); + } +} + +void perform() { + // Retrieve system setting: is haptic feedback enabled? + JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); + jobject activity = static_cast(SDL_AndroidGetActivity()); + jclass j_class = env->FindClass("com/numworks/calculator/EpsilonActivity"); + jmethodID j_methodId = env->GetMethodID(j_class,"hapticFeedbackIsEnabled", "()Z"); + + bool feedbackEnabled = env->CallObjectMethod(activity, j_methodId); + + if (feedbackEnabled && sSDLHaptic) { + SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40); + } +} + + +} +} +} + diff --git a/ion/src/simulator/android/src/java/com/numworks/calculator/EpsilonActivity.java b/ion/src/simulator/android/src/java/com/numworks/calculator/EpsilonActivity.java index ef056c93e..a11c8b805 100644 --- a/ion/src/simulator/android/src/java/com/numworks/calculator/EpsilonActivity.java +++ b/ion/src/simulator/android/src/java/com/numworks/calculator/EpsilonActivity.java @@ -3,9 +3,12 @@ package com.numworks.calculator; import java.util.Locale; import android.app.Activity; +import android.content.Context; +import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; +import android.provider.Settings; import android.util.Log; import com.google.android.gms.analytics.GoogleAnalytics; @@ -13,6 +16,7 @@ import com.google.android.gms.analytics.Tracker; import com.google.android.gms.analytics.HitBuilders; import org.libsdl.app.SDLActivity; +import org.libsdl.app.SDL; public class EpsilonActivity extends SDLActivity { private static GoogleAnalytics sAnalytics; @@ -62,6 +66,12 @@ public class EpsilonActivity extends SDLActivity { ); } + public boolean hapticFeedbackIsEnabled() { + ContentResolver contentResolver = SDL.getContext().getContentResolver(); + int val = Settings.System.getInt(contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0); + return val != 0; + } + @Override protected void onCreate(Bundle savedInstanceState) { /* This is done to hide the status bar and the bottom navigation buttons. diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index fa14dc461..cdc1a03f6 100644 --- a/ion/src/simulator/ios/Makefile +++ b/ion/src/simulator/ios/Makefile @@ -5,7 +5,7 @@ ion_src += $(addprefix ion/src/simulator/ios/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ - haptics.cpp \ + dummy/haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/shared/haptics.cpp b/ion/src/simulator/shared/haptics.cpp deleted file mode 100644 index acb456279..000000000 --- a/ion/src/simulator/shared/haptics.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "haptics.h" -#include - -namespace Ion { -namespace Simulator { -namespace Haptics { - -static SDL_Haptic * sSDLHaptic = nullptr; - -void init() { - if (SDL_Init(SDL_INIT_HAPTIC) == 0) { - sSDLHaptic = SDL_HapticOpen(0); - if (sSDLHaptic) { - if (SDL_HapticRumbleInit(sSDLHaptic) != 0) { - sSDLHaptic = nullptr; - } - } - } -} - -void shutdown() { - if (sSDLHaptic) { - SDL_HapticClose(sSDLHaptic); - } -} - -void perform() { - if (sSDLHaptic) { - SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40); - } -} - - -} -} -} From 17fd1aea29791eb8d1a94faa4b059293700bfc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 23 Sep 2020 17:19:56 +0200 Subject: [PATCH 66/67] [ion] Haptics implementation are on all simulators but Ion::Haptics::isEnabled is per platform. --- ion/src/simulator/android/Makefile | 3 +- ion/src/simulator/android/src/cpp/haptics.cpp | 46 ------------------- .../android/src/cpp/haptics_enabled.cpp | 20 ++++++++ ion/src/simulator/ios/Makefile | 3 +- ion/src/simulator/linux/Makefile | 3 +- ion/src/simulator/macos/Makefile | 3 +- ion/src/simulator/shared/dummy/haptics.cpp | 19 -------- .../shared/dummy/haptics_enabled.cpp | 15 ++++++ ion/src/simulator/shared/events.cpp | 2 +- ion/src/simulator/shared/haptics.cpp | 35 ++++++++++++++ ion/src/simulator/shared/haptics.h | 3 +- ion/src/simulator/web/Makefile | 3 +- ion/src/simulator/windows/Makefile | 3 +- 13 files changed, 85 insertions(+), 73 deletions(-) delete mode 100644 ion/src/simulator/android/src/cpp/haptics.cpp create mode 100644 ion/src/simulator/android/src/cpp/haptics_enabled.cpp delete mode 100644 ion/src/simulator/shared/dummy/haptics.cpp create mode 100644 ion/src/simulator/shared/dummy/haptics_enabled.cpp create mode 100644 ion/src/simulator/shared/haptics.cpp diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index eeba19dea..2fb97edbb 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -1,11 +1,12 @@ ion_src += $(addprefix ion/src/simulator/android/src/cpp/, \ images.cpp \ - haptics.cpp \ + haptics_enabled.cpp \ ) ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/language.cpp \ + haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/android/src/cpp/haptics.cpp b/ion/src/simulator/android/src/cpp/haptics.cpp deleted file mode 100644 index 0c1eedfce..000000000 --- a/ion/src/simulator/android/src/cpp/haptics.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "../../../shared/haptics.h" -#include -#include - -namespace Ion { -namespace Simulator { -namespace Haptics { - -static SDL_Haptic * sSDLHaptic = nullptr; - -void init() { - if (SDL_Init(SDL_INIT_HAPTIC) == 0) { - sSDLHaptic = SDL_HapticOpen(0); - if (sSDLHaptic) { - if (SDL_HapticRumbleInit(sSDLHaptic) != 0) { - sSDLHaptic = nullptr; - } - } - } -} - -void shutdown() { - if (sSDLHaptic) { - SDL_HapticClose(sSDLHaptic); - } -} - -void perform() { - // Retrieve system setting: is haptic feedback enabled? - JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); - jobject activity = static_cast(SDL_AndroidGetActivity()); - jclass j_class = env->FindClass("com/numworks/calculator/EpsilonActivity"); - jmethodID j_methodId = env->GetMethodID(j_class,"hapticFeedbackIsEnabled", "()Z"); - - bool feedbackEnabled = env->CallObjectMethod(activity, j_methodId); - - if (feedbackEnabled && sSDLHaptic) { - SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40); - } -} - - -} -} -} - diff --git a/ion/src/simulator/android/src/cpp/haptics_enabled.cpp b/ion/src/simulator/android/src/cpp/haptics_enabled.cpp new file mode 100644 index 000000000..3f2e77487 --- /dev/null +++ b/ion/src/simulator/android/src/cpp/haptics_enabled.cpp @@ -0,0 +1,20 @@ +#include "../../../shared/haptics.h" +#include +#include + +namespace Ion { +namespace Simulator { +namespace Haptics { + +bool isEnabled() { + JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); + jobject activity = static_cast(SDL_AndroidGetActivity()); + jclass j_class = env->FindClass("com/numworks/calculator/EpsilonActivity"); + jmethodID j_methodId = env->GetMethodID(j_class,"hapticFeedbackIsEnabled", "()Z"); + + return env->CallObjectMethod(activity, j_methodId); +} + +} +} +} diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index cdc1a03f6..e3496ac80 100644 --- a/ion/src/simulator/ios/Makefile +++ b/ion/src/simulator/ios/Makefile @@ -5,7 +5,8 @@ ion_src += $(addprefix ion/src/simulator/ios/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ - dummy/haptics.cpp \ + dummy/haptics_enabled.cpp \ + haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 4eec77717..256685745 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -15,9 +15,10 @@ ion_src += $(addprefix ion/src/simulator/linux/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ - dummy/haptics.cpp \ + dummy/haptics_enabled.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ + haptics.cpp \ ) ifeq ($(EPSILON_TELEMETRY),1) diff --git a/ion/src/simulator/macos/Makefile b/ion/src/simulator/macos/Makefile index 4fc46018e..8171ffff2 100644 --- a/ion/src/simulator/macos/Makefile +++ b/ion/src/simulator/macos/Makefile @@ -5,9 +5,10 @@ ion_src += $(addprefix ion/src/simulator/macos/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ - dummy/haptics.cpp \ + dummy/haptics_enabled.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ + haptics.cpp \ ) ifeq ($(EPSILON_TELEMETRY),1) diff --git a/ion/src/simulator/shared/dummy/haptics.cpp b/ion/src/simulator/shared/dummy/haptics.cpp deleted file mode 100644 index 1d148beb1..000000000 --- a/ion/src/simulator/shared/dummy/haptics.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "../haptics.h" - -namespace Ion { -namespace Simulator { -namespace Haptics { - -void init() { -} - -void shutdown() { -} - -void perform() { -} - - -} -} -} diff --git a/ion/src/simulator/shared/dummy/haptics_enabled.cpp b/ion/src/simulator/shared/dummy/haptics_enabled.cpp new file mode 100644 index 000000000..ee9e48eb8 --- /dev/null +++ b/ion/src/simulator/shared/dummy/haptics_enabled.cpp @@ -0,0 +1,15 @@ +#include "../haptics.h" + +namespace Ion { +namespace Simulator { +namespace Haptics { + +bool isEnabled() { + /* Dummy default to false as failsafe. + * Avoid to get haptics feedback that are not desactivable. */ + return false; +} + +} +} +} diff --git a/ion/src/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp index 04e35f9c8..a522fe7dd 100644 --- a/ion/src/simulator/shared/events.cpp +++ b/ion/src/simulator/shared/events.cpp @@ -6,7 +6,7 @@ namespace Ion { namespace Events { void didPressNewKey() { - Simulator::Haptics::perform(); + Simulator::Haptics::rumble(); } } diff --git a/ion/src/simulator/shared/haptics.cpp b/ion/src/simulator/shared/haptics.cpp new file mode 100644 index 000000000..f0b61b2ea --- /dev/null +++ b/ion/src/simulator/shared/haptics.cpp @@ -0,0 +1,35 @@ +#include "haptics.h" +#include + +namespace Ion { +namespace Simulator { +namespace Haptics { + +static SDL_Haptic * sSDLHaptic = nullptr; + +void init() { + if (SDL_Init(SDL_INIT_HAPTIC) == 0) { + sSDLHaptic = SDL_HapticOpen(0); + if (sSDLHaptic) { + if (SDL_HapticRumbleInit(sSDLHaptic) != 0) { + sSDLHaptic = nullptr; + } + } + } +} + +void shutdown() { + if (sSDLHaptic) { + SDL_HapticClose(sSDLHaptic); + } +} + +void rumble() { + if (isEnabled() && sSDLHaptic) { + SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40); + } +} + +} +} +} diff --git a/ion/src/simulator/shared/haptics.h b/ion/src/simulator/shared/haptics.h index 1a8f88378..2adc20ede 100644 --- a/ion/src/simulator/shared/haptics.h +++ b/ion/src/simulator/shared/haptics.h @@ -6,7 +6,8 @@ namespace Simulator { namespace Haptics { void init(); -void perform(); +bool isEnabled(); +void rumble(); void shutdown(); } diff --git a/ion/src/simulator/web/Makefile b/ion/src/simulator/web/Makefile index 66f8d8e80..e8c729371 100644 --- a/ion/src/simulator/web/Makefile +++ b/ion/src/simulator/web/Makefile @@ -20,7 +20,8 @@ ion_src += $(addprefix ion/src/simulator/web/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/language.cpp \ - dummy/haptics.cpp \ + dummy/haptics_enabled.cpp \ + haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 3fd21fc20..a057924f5 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -6,7 +6,8 @@ ion_src += $(addprefix ion/src/simulator/windows/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ - dummy/haptics.cpp \ + dummy/haptics_enabled.cpp \ + haptics.cpp \ ) ion_src += ion/src/shared/collect_registers.cpp From ab906da53de73d715b652e0c129c6e791a097a4f Mon Sep 17 00:00:00 2001 From: Joachim LF Date: Sun, 11 Oct 2020 12:37:44 +0200 Subject: [PATCH 67/67] [Ion/SDL] Fix conflicts with master --- ion/src/simulator/shared/main_sdl.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ion/src/simulator/shared/main_sdl.cpp b/ion/src/simulator/shared/main_sdl.cpp index 5b9e0bfde..7b85c02ee 100644 --- a/ion/src/simulator/shared/main_sdl.cpp +++ b/ion/src/simulator/shared/main_sdl.cpp @@ -278,6 +278,7 @@ void relayout() { SDL_GetWindowSize(sWindow, &windowWidth, &windowHeight); SDL_RenderSetLogicalSize(sRenderer, windowWidth, windowHeight); + #if !EPSILON_SDL_SCREEN_ONLY if (argument_screen_only) { // Keep original aspect ration in screen_only mode. float scale = (float)(Ion::Display::Width) / (float)(Ion::Display::Height); @@ -294,6 +295,12 @@ void relayout() { } else { Layout::recompute(windowWidth, windowHeight); } + #else + sScreenRect.x = 0; + sScreenRect.y = 0; + sScreenRect.w = windowWidth; + sScreenRect.h = windowHeight; + #endif setNeedsRefresh(); } @@ -307,6 +314,9 @@ void refresh() { return; } + #if EPSILON_SDL_SCREEN_ONLY + Display::draw(sRenderer, &sScreenRect); + #else if (argument_screen_only) { Display::draw(sRenderer, &sScreenRect); } else { @@ -319,6 +329,7 @@ void refresh() { Layout::draw(sRenderer); Display::draw(sRenderer, &screenRect); } + #endif SDL_RenderPresent(sRenderer);