From 922f20ae0530d0ae041090734de67bcb9d6615d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 15 Apr 2019 09:52:09 +0200 Subject: [PATCH 01/57] [ion] Keyboard: fix getPlatformEvent (return None by default) --- ion/src/device/events_keyboard_platform.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/device/events_keyboard_platform.cpp b/ion/src/device/events_keyboard_platform.cpp index 4c2b1a605..599ad3e80 100644 --- a/ion/src/device/events_keyboard_platform.cpp +++ b/ion/src/device/events_keyboard_platform.cpp @@ -24,6 +24,7 @@ Event getPlatformEvent() { return Events::USBEnumeration; } } + return Events::None; } } From d9982a11a9606ea3407a596b01dc8e04c1af852a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 11:24:38 +0200 Subject: [PATCH 02/57] [apps] Redraw battery pictogram after a reset --- apps/apps_container.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 34636cf7a..c79eac063 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -152,11 +152,13 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { if (event == Ion::Events::USBEnumeration) { if (Ion::USB::isPlugged()) { App::Snapshot * activeSnapshot = (activeApp() == nullptr ? appSnapshotAtIndex(0) : activeApp()->snapshot()); + /* Just after a software update, the battery timer does not have time to + * fire before the calculator enters DFU mode. As the DFU mode blocks the + * event loop, we update the battery state "manually" here. + * We do it before switching to USB application to redraw the battery + * pictogram. */ + updateBatteryState(); if (switchTo(usbConnectedAppSnapshot())) { - /* Just after a software update, the battery timer does not have time to - * fire before the calculator enters DFU mode. As the DFU mode blocks the - * event loop, we update the battery state "manually" here. */ - updateBatteryState(); Ion::USB::DFU(); bool switched = switchTo(activeSnapshot); assert(switched); From 60b577e199280f34171eea17e126ddd834531e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 16 Apr 2019 13:51:59 +0200 Subject: [PATCH 03/57] [calculation] Fix tests with no symbolic computation --- apps/calculation/test/calculation_store.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 0e9f7f7da..86cf0167c 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -91,8 +91,8 @@ QUIZ_CASE(calculation_display_exact_approximate) { assertCalculationDisplay("1/2", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Equal, nullptr, nullptr, &globalContext, &store); assertCalculationDisplay("1/3", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("1/0", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("2x-x", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); + assertCalculationDisplay("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); + assertCalculationDisplay("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); assertCalculationDisplay("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); assertCalculationDisplay("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "[[1,undef,3]]", &globalContext, &store); assertCalculationDisplay("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); @@ -110,10 +110,10 @@ QUIZ_CASE(calculation_symbolic_computation) { Shared::GlobalContext globalContext; CalculationStore store; - assertCalculationDisplay("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); + assertCalculationDisplay("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); + assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); assertCalculationDisplay("1+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+1", nullptr, &globalContext, &store); - assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); + assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); assertCalculationDisplay("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", &globalContext, &store); assertCalculationDisplay("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, &globalContext, &store); assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, &globalContext, &store); From e615172cbbf494d4cc25bdae840583d23442ca63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 16 Apr 2019 13:54:05 +0200 Subject: [PATCH 04/57] [sequence] Cache context does not need to be a variable context Fix crash: u(n) = n(n-1) would crash at evaluation --- apps/sequence/cache_context.cpp | 11 ++++++++--- apps/sequence/cache_context.h | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/sequence/cache_context.cpp b/apps/sequence/cache_context.cpp index 96fb44608..0645e34e5 100644 --- a/apps/sequence/cache_context.cpp +++ b/apps/sequence/cache_context.cpp @@ -8,9 +8,9 @@ namespace Sequence { template CacheContext::CacheContext(Context * parentContext) : - VariableContext("n", parentContext), m_values{{NAN, NAN}, - {NAN, NAN}} + {NAN, NAN}}, + m_parentContext(parentContext) { } @@ -24,7 +24,12 @@ const Expression CacheContext::expressionForSymbol(const SymbolAbstract & sym Symbol s = const_cast(static_cast(symbol)); return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); } - return VariableContext::expressionForSymbol(symbol, clone); + return m_parentContext->expressionForSymbol(symbol, clone); +} + +template +void CacheContext::setExpressionForSymbol(const Expression & expression, const SymbolAbstract & symbol, Context & context) { + m_parentContext->setExpressionForSymbol(expression, symbol, context); } template diff --git a/apps/sequence/cache_context.h b/apps/sequence/cache_context.h index e9dc40560..011596ece 100644 --- a/apps/sequence/cache_context.h +++ b/apps/sequence/cache_context.h @@ -4,21 +4,22 @@ #include #include #include -#include #include "sequence_context.h" namespace Sequence { template -class CacheContext : public Poincare::VariableContext { +class CacheContext : public Poincare::Context { public: CacheContext(Poincare::Context * parentContext); const Poincare::Expression expressionForSymbol(const Poincare::SymbolAbstract & symbol, bool clone) override; + void setExpressionForSymbol(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol, Poincare::Context & context) override; void setValueForSymbol(T value, const Poincare::Symbol & symbol); private: int nameIndexForSymbol(const Poincare::Symbol & symbol); int rankIndexForSymbol(const Poincare::Symbol & symbol); T m_values[MaxNumberOfSequences][MaxRecurrenceDepth]; + Context * m_parentContext; }; } From 2bfe257f3ff31f0a4b14a4284dcaca4f8248fede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 16 Apr 2019 14:06:57 +0200 Subject: [PATCH 05/57] [kandinsky] Fonts: improve glyph % --- kandinsky/fonts/LargeSourcePixel.otf | Bin 211680 -> 211488 bytes kandinsky/fonts/SmallSourcePixel.otf | Bin 206008 -> 206120 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/kandinsky/fonts/LargeSourcePixel.otf b/kandinsky/fonts/LargeSourcePixel.otf index af4a233736893110b786c13bae44017e82f8862d..1e6c616eaab8d8ca652dbc5d0784008b119ed06f 100644 GIT binary patch delta 34292 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 YBP(y+edqIc-(`>A^Iz;qtqFbQe^s5a3;+NC delta 35296 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 diff --git a/kandinsky/fonts/SmallSourcePixel.otf b/kandinsky/fonts/SmallSourcePixel.otf index 02feffc75322b3cf8dbdf3f6f8cb9ec37ece74f2..43e3c0e566c1321d45e781f287210f766ea9ae04 100644 GIT binary patch delta 32189 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 From bdaf229cfd9a9c032f17f2b6844abbd1be0bed10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 11:31:26 +0200 Subject: [PATCH 06/57] [apps/text_field_delegate_app] Fix XNT in a sum layout It was broken by a blunder while swithching to UTF8 --- apps/shared/text_field_delegate_app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index d7b2d2582..720b6c0b1 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -57,7 +57,7 @@ bool TextFieldDelegateApp::fieldDidReceiveEvent(EditableField * field, Responder /* TODO decode here to encode again in handleEventWithText? */ constexpr int bufferSize = CodePoint::MaxCodePointCharLength+1; char buffer[bufferSize]; - size_t length = UTF8Decoder::CodePointToChars(XNT(), buffer, bufferSize); + size_t length = UTF8Decoder::CodePointToChars(field->XNTCodePoint(XNT()), buffer, bufferSize); assert(length < bufferSize - 1); buffer[length] = 0; return field->handleEventWithText(buffer); From f9e2fc6974d3083b51b47c0ca1883e0e1838293b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 11 Apr 2019 09:54:26 +0200 Subject: [PATCH 07/57] [python] Fix typo --- python/port/mpconfigport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 681b3b6b5..207d3e6e6 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -83,7 +83,7 @@ // Whether to provide "sys" module #define MICROPY_PY_SYS (0) -// Wether to provide the "urandom" module +// Whether to provide the "urandom" module #define MICROPY_PY_URANDOM (1) // Whether to include: randrange, randint, choice, random, uniform From f73951865ea484b454aef939f3e6c2c97604307f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 11:43:53 +0200 Subject: [PATCH 08/57] [python/urandom] Add init method to seed the Yasmarang generator. This commit should disappear as it will be in one of the next stable micropython versions --- python/src/extmod/modurandom.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python/src/extmod/modurandom.c b/python/src/extmod/modurandom.c index 1512a3fd4..a8d087c49 100644 --- a/python/src/extmod/modurandom.c +++ b/python/src/extmod/modurandom.c @@ -200,8 +200,20 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_urandom_uniform_obj, mod_urandom_uniform); #endif // MICROPY_PY_URANDOM_EXTRA_FUNCS + +#ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC +STATIC mp_obj_t mod_urandom___init__() { + mod_urandom_seed(MP_OBJ_NEW_SMALL_INT(MICROPY_PY_URANDOM_SEED_INIT_FUNC)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_urandom___init___obj, mod_urandom___init__); +#endif + STATIC const mp_rom_map_elem_t mp_module_urandom_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_urandom) }, + #ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&mod_urandom___init___obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_getrandbits), MP_ROM_PTR(&mod_urandom_getrandbits_obj) }, { MP_ROM_QSTR(MP_QSTR_seed), MP_ROM_PTR(&mod_urandom_seed_obj) }, #if MICROPY_PY_URANDOM_EXTRA_FUNCS From 03bb81215e582d64a8621affcd136351d56d6e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 11:44:57 +0200 Subject: [PATCH 09/57] [python/urandom] Use Ion::random to seed urandom --- python/port/helpers.cpp | 4 ++++ python/port/helpers.h | 1 + python/port/mpconfigport.h | 3 +++ 3 files changed, 8 insertions(+) diff --git a/python/port/helpers.cpp b/python/port/helpers.cpp index b30cc00b5..5f772751f 100644 --- a/python/port/helpers.cpp +++ b/python/port/helpers.cpp @@ -42,3 +42,7 @@ bool micropython_port_interrupt_if_needed() { } return false; } + +int micropython_port_random() { + return Ion::random(); +} diff --git a/python/port/helpers.h b/python/port/helpers.h index 32215ff80..761f96a7d 100644 --- a/python/port/helpers.h +++ b/python/port/helpers.h @@ -12,6 +12,7 @@ extern "C" { bool micropython_port_vm_hook_loop(); bool micropython_port_interruptible_msleep(uint32_t delay); bool micropython_port_interrupt_if_needed(); +int micropython_port_random(); #ifdef __cplusplus } diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 207d3e6e6..dae99b6f2 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -89,6 +89,9 @@ // Whether to include: randrange, randint, choice, random, uniform #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +// Function to seed URANDOM with on init +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC micropython_port_random() + // Make a pointer to RAM callable (eg set lower bit for Thumb code) // (This scheme won't work if we want to mix Thumb and normal ARM code.) #define MICROPY_MAKE_POINTER_CALLABLE(p) (p) From 73e094796246c6ff789e80e3b61d1a361953e290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 12:10:47 +0200 Subject: [PATCH 10/57] [apps/calculation/tests] Tests on displayExactApprox for random/round --- apps/calculation/test/calculation_store.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 86cf0167c..cf251d910 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -104,6 +104,8 @@ QUIZ_CASE(calculation_display_exact_approximate) { Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); assertCalculationDisplay("3+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+3", nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); + assertCalculationDisplay("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); + assertCalculationDisplay("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", &globalContext, &store); } QUIZ_CASE(calculation_symbolic_computation) { From a48478882c1ac3b3558284b9c056f5cde6fd9a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 12:11:37 +0200 Subject: [PATCH 11/57] [apps/calc] If result contains random/round, display approximation only --- apps/calculation/calculation.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 2a498d278..efa8823d8 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -159,7 +159,15 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { return DisplayOutput::ExactOnly; } bool approximateOnly = false; - if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { + if (exactOutput().recursivelyMatches([](const Expression e, Context & c, bool replaceSymbols) { + /* If the exact result contains one of the following types, do not + * display it. */ + ExpressionNode::Type t = e.type(); + return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round);}, + *context, true)) + { + approximateOnly = true; + } else if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { /* If the exact and approximate results' texts are equal and their layouts * too, do not display the exact result. If the two layouts are not equal * because of the number of significant digits, we display both. */ From 326b8b87612fba08af4010934e7c30ecb570d2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 14:15:31 +0200 Subject: [PATCH 12/57] [poincare/tests] randint's simplification gives its approximation --- apps/calculation/test/calculation_store.cpp | 2 ++ poincare/test/complex.cpp | 2 +- poincare/test/random.cpp | 9 +-------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index cf251d910..d132110ea 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -106,6 +106,8 @@ QUIZ_CASE(calculation_display_exact_approximate) { Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); assertCalculationDisplay("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); assertCalculationDisplay("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", &globalContext, &store); + assertCalculationDisplay("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", &globalContext, &store); + } QUIZ_CASE(calculation_symbolic_computation) { diff --git a/poincare/test/complex.cpp b/poincare/test/complex.cpp index e74e0e3ac..9ae0a374c 100644 --- a/poincare/test/complex.cpp +++ b/poincare/test/complex.cpp @@ -104,7 +104,7 @@ QUIZ_CASE(poincare_complex_simplify) { assert_parsed_expression_simplify_to("permute(10, 4)", "5040", User, Radian, Cartesian); // TODO: prediction is not simplified yet //assert_parsed_expression_simplify_to("prediction(-2,-3)", "prediction(-2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("randint(2,4)", "randint(2,4)", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("randint(2,2)", "2", User, Radian, Cartesian); assert_parsed_expression_simplify_to("random()", "random()", User, Radian, Cartesian); assert_parsed_expression_simplify_to("re(x)", "re(x)", User, Radian, Cartesian); assert_parsed_expression_simplify_to("round(x,y)", "round(x,y)", User, Radian, Cartesian); diff --git a/poincare/test/random.cpp b/poincare/test/random.cpp index 6a40f8bd8..52f892140 100644 --- a/poincare/test/random.cpp +++ b/poincare/test/random.cpp @@ -20,12 +20,5 @@ QUIZ_CASE(poincare_random_simplify) { } QUIZ_CASE(poincare_randint_simplify) { - assert_parsed_expression_simplify_to("1/randint(3,10)+1/3+1/4", "1/randint(3,10)+7/12"); - assert_parsed_expression_simplify_to("randint(3,10)+randint(3,10)", "randint(3,10)+randint(3,10)"); - assert_parsed_expression_simplify_to("randint(3,10)-randint(3,10)", "-randint(3,10)+randint(3,10)"); - assert_parsed_expression_simplify_to("1/randint(3,10)+1/3+1/4+1/randint(3,10)", "1/randint(3,10)+1/randint(3,10)+7/12"); - assert_parsed_expression_simplify_to("randint(3,10)×randint(3,10)", "randint(3,10)×randint(3,10)"); - assert_parsed_expression_simplify_to("randint(3,10)/randint(3,10)", "randint(3,10)/randint(3,10)"); - assert_parsed_expression_simplify_to("3^randint(3,10)×3^randint(3,10)", "3^randint(3,10)×3^randint(3,10)"); - assert_parsed_expression_simplify_to("randint(3,10)×ln(2)×3+randint(3,10)×ln(2)×5", "5×ln(2)×randint(3,10)+3×ln(2)×randint(3,10)"); + assert_parsed_expression_simplify_to("1/randint(2,2)+1/2", "1"); } From 1e20b283dc002e48c6eacf07a6efc3495d0e40db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 14:28:00 +0200 Subject: [PATCH 13/57] [poincre/randint] Randit simplifies itself to its approximation --- poincare/include/poincare/expression.h | 1 + poincare/include/poincare/randint.h | 4 ++++ poincare/src/randint.cpp | 29 ++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 5f0198e8f..f889f4cc7 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -71,6 +71,7 @@ class Expression : public TreeHandle { friend class PowerNode; friend class PredictionInterval; friend class Product; + friend class Randint; friend class RealPart; friend class Round; friend class SignFunction; diff --git a/poincare/include/poincare/randint.h b/poincare/include/poincare/randint.h index ef2004b60..ace041666 100644 --- a/poincare/include/poincare/randint.h +++ b/poincare/include/poincare/randint.h @@ -36,6 +36,8 @@ private: return templateApproximate(context, complexFormat, angleUnit); } template Evaluation templateApproximate(Context& context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + // Simplification + Expression shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, bool symbolicComputation) override; }; class Randint final : public Expression { @@ -44,6 +46,8 @@ public: Randint(const RandintNode * n) : Expression(n) {} static Randint Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder(ArrayBuilder(child0, child1).array(), 2); } static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("randint", 2, &UntypedBuilderTwoChildren); +private: + Expression shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target); }; } diff --git a/poincare/src/randint.cpp b/poincare/src/randint.cpp index 47d20058b..e30b2b3df 100644 --- a/poincare/src/randint.cpp +++ b/poincare/src/randint.cpp @@ -1,9 +1,13 @@ #include #include -#include -#include +#include +#include #include +#include +#include +#include #include +#include extern "C" { #include @@ -37,5 +41,26 @@ template Evaluation RandintNode::templateApproximate(Context & c return Complex::Builder(result); } +Expression RandintNode::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, bool symbolicComputation) { + return Randint(this).shallowReduce(context, complexFormat, angleUnit, target); +} + +Expression Randint::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target) { + Expression e = Expression::defaultShallowReduce(); + if (e.isUndefined()) { + return e; + } + float eval = approximateToScalar(context, complexFormat, angleUnit); + Expression result; + if (std::isnan(eval)) { + result = Undefined::Builder(); + } else if (std::isinf(eval)) { + result = Infinity::Builder(eval < 0); + } else { + result = Rational::Builder(Integer((int)eval)); + } + replaceWithInPlace(result); + return result; +} } From 7362e965a8f2a951000f1af2a5f336c94610f96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 14:33:52 +0200 Subject: [PATCH 14/57] [poincare/randint] Do not accept infinite bounds Before, randint(1,inf) would give inf --- poincare/src/randint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/randint.cpp b/poincare/src/randint.cpp index e30b2b3df..eee3afb1f 100644 --- a/poincare/src/randint.cpp +++ b/poincare/src/randint.cpp @@ -33,7 +33,7 @@ template Evaluation RandintNode::templateApproximate(Context & c Evaluation bInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); T a = aInput.toScalar(); T b = bInput.toScalar(); - if (std::isnan(a) || std::isnan(b) || a != std::round(a) || b != std::round(b) || a > b) { + if (std::isnan(a) || std::isnan(b) || a != std::round(a) || b != std::round(b) || a > b || std::isinf(a) || std::isinf(b)) { return Complex::Undefined(); } From 4afcb95e18ef926d1e25b22de80e610ce509664b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 17 Apr 2019 14:35:46 +0200 Subject: [PATCH 15/57] [poincare/tests] More tests on randint --- poincare/test/random.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/poincare/test/random.cpp b/poincare/test/random.cpp index 52f892140..b8dd06c3a 100644 --- a/poincare/test/random.cpp +++ b/poincare/test/random.cpp @@ -21,4 +21,7 @@ QUIZ_CASE(poincare_random_simplify) { QUIZ_CASE(poincare_randint_simplify) { assert_parsed_expression_simplify_to("1/randint(2,2)+1/2", "1"); + assert_parsed_expression_simplify_to("randint(1, inf)", "undef"); + assert_parsed_expression_simplify_to("randint(-inf, 3)", "undef"); + assert_parsed_expression_simplify_to("randint(4, 3)", "undef"); } From ef12e6de79e2918940cd9e90fcf6180ab04af85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 18 Apr 2019 13:57:43 +0200 Subject: [PATCH 16/57] [apps] BatteryView: when the battery is EMPTY and the device hasn't shut down yet, avoid drawing full battery (and breaking assertion) --- apps/battery_view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/battery_view.cpp b/apps/battery_view.cpp index dbbdd9e34..81b90e126 100644 --- a/apps/battery_view.cpp +++ b/apps/battery_view.cpp @@ -66,7 +66,7 @@ void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(batteryInsideX, 0, batteryInsideWidth, k_batteryHeight), Palette::YellowLight); KDRect frame((k_batteryWidth-k_flashWidth)/2, 0, k_flashWidth, k_flashHeight); ctx->blendRectWithMask(frame, KDColorWhite, (const uint8_t *)flashMask, s_flashWorkingBuffer); - } else if (m_chargeState == Ion::Battery::Charge::LOW) { + } else if (m_chargeState == Ion::Battery::Charge::EMPTY || m_chargeState == Ion::Battery::Charge::LOW) { assert(!m_isPlugged); // Low: Quite empty battery ctx->fillRect(KDRect(batteryInsideX, 0, 2*k_elementWidth, k_batteryHeight), Palette::LowBattery); From e39172a7cdc3a930505218e595e3209d67bfa545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 18 Apr 2019 13:58:42 +0200 Subject: [PATCH 17/57] [apps] AppsContainer: 'shutdownDueToLowBattery' can be call when the battery is actually LOW (due to some oscillations between LOW and EMPTY). Handle that case --- apps/apps_container.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index c79eac063..6e7cef01d 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -312,6 +312,14 @@ void AppsContainer::displayExamModePopUp(bool activate) { } void AppsContainer::shutdownDueToLowBattery() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + /* We early escape here. When the battery switches from LOW to EMPTY, it + * oscillates a few times before stabilizing to EMPTY. So we might call + * 'shutdownDueToLowBattery' but the battery level still answers LOW instead + * of EMPTY. We want to avoid uselessly redrawing the whole window in that + * case. */ + return; + } while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { Ion::Backlight::setBrightness(0); m_emptyBatteryWindow.redraw(true); From ab108bbd2c2a32166260055a7e8fb26eb94e74e4 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 28 Mar 2019 13:50:18 +0100 Subject: [PATCH 18/57] [apps/graph] Add preimage to i18n --- apps/graph/base.de.i18n | 2 ++ apps/graph/base.en.i18n | 2 ++ apps/graph/base.es.i18n | 2 ++ apps/graph/base.fr.i18n | 2 ++ apps/graph/base.pt.i18n | 2 ++ 5 files changed, 10 insertions(+) diff --git a/apps/graph/base.de.i18n b/apps/graph/base.de.i18n index ed06c9cbc..e5ec488fa 100644 --- a/apps/graph/base.de.i18n +++ b/apps/graph/base.de.i18n @@ -11,12 +11,14 @@ Compute = "Berechnen" Zeros = "Nullstellen" Tangent = "Tangente" Intersection = "Schnittmenge" +Preimage = "Urbild" SelectLowerBound = "Untere Integrationsgrenze" SelectUpperBound = "Obere Integrationsgrenze" NoMaximumFound = "Kein Maximalwert gefunden" NoMinimumFound = "Kein Mindestwert gefunden" NoZeroFound = "Keine Nullstelle gefunden" NoIntersectionFound = "Kein Schnittpunkt gefunden" +NoPreimageFound = "Kein Urbild gefunden" DerivativeFunctionColumn = "Spalte der Ableitungsfunktion" HideDerivativeColumn = "Ableitungsfunktion ausblenden" AllowedCharactersAZaz09 = "Erlaubte Zeichen: A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.en.i18n b/apps/graph/base.en.i18n index 34847cd07..18142b32a 100644 --- a/apps/graph/base.en.i18n +++ b/apps/graph/base.en.i18n @@ -11,12 +11,14 @@ Compute = "Calculate" Zeros = "Zeros" Tangent = "Tangent" Intersection = "Intersection" +Preimage = "Inverse image" SelectLowerBound = "Select lower bound" SelectUpperBound = "Select upper bound" NoMaximumFound = "No maximum found" NoMinimumFound = "No minimum found" NoZeroFound = "No zero found" NoIntersectionFound = "No intersection found" +NoPreimageFound = "No inverse image found" DerivativeFunctionColumn = "Derivative function column" HideDerivativeColumn = "Hide the derivative function" AllowedCharactersAZaz09 = "Allowed characters: A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.es.i18n b/apps/graph/base.es.i18n index 3d095ec4d..fbb763c64 100644 --- a/apps/graph/base.es.i18n +++ b/apps/graph/base.es.i18n @@ -11,12 +11,14 @@ Compute = "Calcular" Zeros = "Raíces" Tangent = "Tangente" Intersection = "Intersección" +Preimage = "Imagen inversa" SelectLowerBound = "Seleccionar el límite inferior" SelectUpperBound = "Seleccionar el límite superior" NoMaximumFound = "Níngun máximo encontrado" NoMinimumFound = "Níngun mínimo encontrado" NoZeroFound = "Ninguna raíz encontrada" NoIntersectionFound = "Ninguna intersección encontrada" +NoPreimageFound = "Ninguna imagen inversa encontrada" DerivativeFunctionColumn = "Columna de la derivada" HideDerivativeColumn = "Ocultar la derivada" AllowedCharactersAZaz09 = "Caracteres permitidos : A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.fr.i18n b/apps/graph/base.fr.i18n index 6dd75e4ea..5014969aa 100644 --- a/apps/graph/base.fr.i18n +++ b/apps/graph/base.fr.i18n @@ -11,12 +11,14 @@ Compute = "Calculer" Zeros = "Zéros" Tangent = "Tangente" Intersection = "Intersection" +Preimage = "Antécédent" SelectLowerBound = "Selectionner la borne inférieure" SelectUpperBound = "Selectionner la borne supérieure" NoMaximumFound = "Aucun maximum trouvé" NoMinimumFound = "Aucun minimum trouvé" NoZeroFound = "Aucun zéro trouvé" NoIntersectionFound = "Aucune intersection trouvée" +NoPreimageFound = "Aucun antécédent trouvé" DerivativeFunctionColumn = "Colonne de la fonction derivée" HideDerivativeColumn = "Masquer la fonction derivée" AllowedCharactersAZaz09 = "Caractères autorisés : A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.pt.i18n b/apps/graph/base.pt.i18n index eed09f293..1e33f5c91 100644 --- a/apps/graph/base.pt.i18n +++ b/apps/graph/base.pt.i18n @@ -11,12 +11,14 @@ Compute = "Calcular" Zeros = "Raízes" Tangent = "Tangente" Intersection = "Intersecção" +Preimage = "Imagem inversa" SelectLowerBound = "Selecionar limite superior" SelectUpperBound = "Selecionar limite inferior" NoMaximumFound = "Nenhum máximo encontrado" NoMinimumFound = "Nenhum mínimo encontrado" NoZeroFound = "Nenhuma raiz encontrada" NoIntersectionFound = "Nenhuma intersecção encontrada" +NoPreimageFound = "Nenhuma imagem inversa encontrada" DerivativeFunctionColumn = "Coluna da função derivada" HideDerivativeColumn = "Esconder função derivada" AllowedCharactersAZaz09 = "Caracteres permitidos : A-Z, a-z, 0-9, _" From e0774cba4b5ddd78bdca6d58c569cc9054848b8a Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 2 Apr 2019 11:31:35 +0200 Subject: [PATCH 19/57] [apps/graph/graph] Define PreimageGraphController class --- apps/graph/Makefile | 1 + .../graph/graph/preimage_graph_controller.cpp | 29 +++++++++++++++++++ apps/graph/graph/preimage_graph_controller.h | 22 ++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 apps/graph/graph/preimage_graph_controller.cpp create mode 100644 apps/graph/graph/preimage_graph_controller.h diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 5f8ea6f7e..9d5696b8e 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -12,6 +12,7 @@ app_src += $(addprefix apps/graph/,\ graph/graph_controller.cpp \ graph/graph_controller_helper.cpp \ graph/graph_view.cpp \ + graph/preimage_graph_controller.cpp\ graph/integral_graph_controller.cpp \ graph/intersection_graph_controller.cpp \ graph/root_graph_controller.cpp \ diff --git a/apps/graph/graph/preimage_graph_controller.cpp b/apps/graph/graph/preimage_graph_controller.cpp new file mode 100644 index 000000000..818de7210 --- /dev/null +++ b/apps/graph/graph/preimage_graph_controller.cpp @@ -0,0 +1,29 @@ +#include "preimage_graph_controller.h" + +namespace Graph { + +PreimageGraphController::PreimageGraphController( + Responder * parentResponder, + GraphView * graphView, + BannerView * bannerView, + Shared::InteractiveCurveViewRange * curveViewRange, + Shared::CurveViewCursor * cursor +) : + CalculationGraphController( + parentResponder, + graphView, + bannerView, + curveViewRange, + cursor, + I18n::Message::NoPreimageFound + ), + m_image(NAN) +{ +} + +Poincare::Expression::Coordinate2D PreimageGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { + Poincare::Expression expression = Poincare::Float::Builder(m_image); + return functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, expression); +} + +} diff --git a/apps/graph/graph/preimage_graph_controller.h b/apps/graph/graph/preimage_graph_controller.h new file mode 100644 index 000000000..995b06b0a --- /dev/null +++ b/apps/graph/graph/preimage_graph_controller.h @@ -0,0 +1,22 @@ +#include "calculation_graph_controller.h" + +namespace Graph { + +class PreimageGraphController : public CalculationGraphController { +public: + PreimageGraphController( + Responder * parentResponder, + GraphView * graphView, + BannerView * bannerView, + Shared::InteractiveCurveViewRange * curveViewRange, + Shared::CurveViewCursor * cursor + ); + const char * title() override { return I18n::translate(I18n::Message::Preimage); } + double image() { return m_image; } + void setImage(double value) { m_image = value; } +private: + Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + double m_image; +}; + +} From 23fd28b3b42a08496a9f819401feb3bfbb91571b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 2 Apr 2019 11:41:52 +0200 Subject: [PATCH 20/57] [apps/graph/graph] Define PreimageParameterController class --- apps/graph/Makefile | 1 + .../graph/preimage_parameter_controller.cpp | 53 +++++++++++++++++++ .../graph/preimage_parameter_controller.h | 31 +++++++++++ 3 files changed, 85 insertions(+) create mode 100644 apps/graph/graph/preimage_parameter_controller.cpp create mode 100644 apps/graph/graph/preimage_parameter_controller.h diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 9d5696b8e..b3b7ef5b0 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -13,6 +13,7 @@ app_src += $(addprefix apps/graph/,\ graph/graph_controller_helper.cpp \ graph/graph_view.cpp \ graph/preimage_graph_controller.cpp\ + graph/preimage_parameter_controller.cpp\ graph/integral_graph_controller.cpp \ graph/intersection_graph_controller.cpp \ graph/root_graph_controller.cpp \ diff --git a/apps/graph/graph/preimage_parameter_controller.cpp b/apps/graph/graph/preimage_parameter_controller.cpp new file mode 100644 index 000000000..89797dd0b --- /dev/null +++ b/apps/graph/graph/preimage_parameter_controller.cpp @@ -0,0 +1,53 @@ +#include "preimage_parameter_controller.h" +#include "../app.h" +#include + +namespace Graph { + +PreimageParameterController::PreimageParameterController( + Responder * parentResponder, + InputEventHandlerDelegate * inputEventHandlerDelegate, + Shared::InteractiveCurveViewRange * graphRange, + Shared::CurveViewCursor * cursor, + PreimageGraphController * preimageGraphController +) : + Shared::GoToParameterController( + parentResponder, + inputEventHandlerDelegate, + graphRange, + cursor, + I18n::Message::Y + ), + m_preimageGraphController(preimageGraphController) +{ +} + +const char * PreimageParameterController::title() { + return I18n::translate(I18n::Message::Preimage); +} + +void PreimageParameterController::viewWillAppear() { + m_preimageGraphController->setImage(m_cursor->y()); + Shared::GoToParameterController::viewWillAppear(); +} + +void PreimageParameterController::buttonAction() { + m_preimageGraphController->setRecord(m_record); + StackViewController * stack = static_cast(parentResponder()); + stack->pop(); + stack->pop(); + stack->pop(); + stack->push(m_preimageGraphController); +} + +double PreimageParameterController::parameterAtIndex(int index) { + assert(index == 0); + return m_preimageGraphController->image(); +} +bool PreimageParameterController::setParameterAtIndex(int parameterIndex, double f) { + assert(parameterIndex == 0); + m_preimageGraphController->setImage(f); + return true; +} + +} diff --git a/apps/graph/graph/preimage_parameter_controller.h b/apps/graph/graph/preimage_parameter_controller.h new file mode 100644 index 000000000..dbe683004 --- /dev/null +++ b/apps/graph/graph/preimage_parameter_controller.h @@ -0,0 +1,31 @@ +#ifndef GRAPH_PREIMAGE_PARAMETER_CONTROLLER +#define GRAPH_PREIMAGE_PARAMETER_CONTROLLER + +#include "../../shared/go_to_parameter_controller.h" +#include "preimage_graph_controller.h" + +namespace Graph { + +class PreimageParameterController : public Shared::GoToParameterController { +public: + PreimageParameterController( + Responder * parentResponder, + InputEventHandlerDelegate * inputEventHandlerDelegate, + Shared::InteractiveCurveViewRange * graphRange, + Shared::CurveViewCursor * cursor, + PreimageGraphController * preimageGraphController + ); + const char * title() override; + void setRecord(Ion::Storage::Record record) { m_record = record; } + void viewWillAppear() override; +private: + void buttonAction() override; + double parameterAtIndex(int index) override; + bool setParameterAtIndex(int parameterIndex, double f) override; + Ion::Storage::Record m_record; + PreimageGraphController * m_preimageGraphController; +}; + +} + +#endif From 48d3a58ffc101858387e798a12ddb2c54bb60db1 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 2 Apr 2019 12:06:21 +0200 Subject: [PATCH 21/57] [apps/graph/graph] CalculationParameterController inherits from ListViewDataSource instead of SimpleListViewDataSource --- .../graph/graph/calculation_parameter_controller.cpp | 12 ++++++++---- apps/graph/graph/calculation_parameter_controller.h | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index cd5bb5877..259a916f5 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -82,19 +82,23 @@ int CalculationParameterController::numberOfRows() { return k_totalNumberOfCells; }; +KDCoordinate CalculationParameterController::rowHeight(int j) { + return Metric::ParameterCellHeight; +} -HighlightCell * CalculationParameterController::reusableCell(int index) { +HighlightCell * CalculationParameterController::reusableCell(int index, int type) { assert(index >= 0); assert(index < k_totalNumberOfCells); return &m_cells[index]; } -int CalculationParameterController::reusableCellCount() { +int CalculationParameterController::reusableCellCount(int type) { return k_totalNumberOfCells; } -KDCoordinate CalculationParameterController::cellHeight() { - return Metric::ParameterCellHeight; +int CalculationParameterController::typeAtLocation(int i, int j) { + assert(i == 0); + return 0; } void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index 77b1cac88..9e55126d7 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -14,7 +14,7 @@ namespace Graph { -class CalculationParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +class CalculationParameterController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { public: CalculationParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * range, Shared::CurveViewCursor * cursor); View * view() override; @@ -22,9 +22,10 @@ public: bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; int numberOfRows() override; - KDCoordinate cellHeight() override; - HighlightCell * reusableCell(int index) override; - int reusableCellCount() override; + KDCoordinate rowHeight(int j) override; + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + int typeAtLocation(int i, int j) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; void setRecord(Ion::Storage::Record record); private: From 6d66b2e75e006a233f637d2d074cfc6713a98a1d Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 2 Apr 2019 12:14:19 +0200 Subject: [PATCH 22/57] [apps/graph/graph] Add Preimage controllers to CalculationParameterController --- .../calculation_parameter_controller.cpp | 51 +++++++++++++------ .../graph/calculation_parameter_controller.h | 8 ++- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index 259a916f5..f7df007b4 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -9,8 +9,11 @@ namespace Graph { CalculationParameterController::CalculationParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, BannerView * bannerView, InteractiveCurveViewRange * range, CurveViewCursor * cursor) : ViewController(parentResponder), + m_preimageCell(I18n::Message::Preimage), m_selectableTableView(this), m_record(), + m_preimageParameterController(nullptr, inputEventHandlerDelegate, range, cursor, &m_preimageGraphController), + m_preimageGraphController(nullptr, graphView, bannerView, range, cursor), m_tangentGraphController(nullptr, graphView, bannerView, range, cursor), m_integralGraphController(nullptr, inputEventHandlerDelegate, graphView, range, cursor), m_minimumGraphController(nullptr, graphView, bannerView, range, cursor), @@ -34,30 +37,35 @@ void CalculationParameterController::didBecomeFirstResponder() { } bool CalculationParameterController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + int row = selectedRow(); + if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && row == 0)) { ViewController * controller = nullptr; - switch(selectedRow()) { + switch(row) { case 0: + m_preimageParameterController.setRecord(m_record); + controller = &m_preimageParameterController; + break; + case 1: m_intersectionGraphController.setRecord(m_record); controller = &m_intersectionGraphController; break; - case 1: + case 2: m_maximumGraphController.setRecord(m_record); controller = &m_maximumGraphController; break; - case 2: + case 3: m_minimumGraphController.setRecord(m_record); controller = &m_minimumGraphController; break; - case 3: + case 4: m_rootGraphController.setRecord(m_record); controller = &m_rootGraphController; break; - case 4: + case 5: m_tangentGraphController.setRecord(m_record); controller = &m_tangentGraphController; break; - case 5: + case 6: m_integralGraphController.setRecord(m_record); controller = &m_integralGraphController; break; @@ -65,8 +73,10 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) { return false; } StackViewController * stack = static_cast(parentResponder()); - stack->pop(); - stack->pop(); + if (row > 0) { + stack->pop(); + stack->pop(); + } stack->push(controller); return true; } @@ -79,6 +89,7 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) { } int CalculationParameterController::numberOfRows() { + constexpr int k_totalNumberOfCells = k_totalNumberOfReusableCells + 1; return k_totalNumberOfCells; }; @@ -88,23 +99,31 @@ KDCoordinate CalculationParameterController::rowHeight(int j) { HighlightCell * CalculationParameterController::reusableCell(int index, int type) { assert(index >= 0); - assert(index < k_totalNumberOfCells); - return &m_cells[index]; + assert(index < reusableCellCount(type)); + if (type == 0) { + return &m_cells[index]; + } + assert(type == 1); + return &m_preimageCell; } int CalculationParameterController::reusableCellCount(int type) { - return k_totalNumberOfCells; + if (type == 0) { + return k_totalNumberOfReusableCells; + } + return 1; } int CalculationParameterController::typeAtLocation(int i, int j) { assert(i == 0); - return 0; + return j == 0; } void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; - I18n::Message titles[k_totalNumberOfCells] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; - myCell->setMessage(titles[index]); + if (cell != &m_preimageCell) { + I18n::Message titles[] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; + static_cast(cell)->setMessage(titles[index - 1]); + } } void CalculationParameterController::setRecord(Ion::Storage::Record record) { diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index 9e55126d7..043f53a7b 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -3,6 +3,7 @@ #include #include "../cartesian_function_store.h" +#include "preimage_parameter_controller.h" #include "tangent_graph_controller.h" #include "extremum_graph_controller.h" #include "integral_graph_controller.h" @@ -29,10 +30,13 @@ public: void willDisplayCellForIndex(HighlightCell * cell, int index) override; void setRecord(Ion::Storage::Record record); private: - constexpr static int k_totalNumberOfCells = 6; - MessageTableCell m_cells[k_totalNumberOfCells]; + MessageTableCellWithChevron m_preimageCell; + constexpr static int k_totalNumberOfReusableCells = 6; + MessageTableCell m_cells[k_totalNumberOfReusableCells]; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; + PreimageParameterController m_preimageParameterController; + PreimageGraphController m_preimageGraphController; TangentGraphController m_tangentGraphController; IntegralGraphController m_integralGraphController; MinimumGraphController m_minimumGraphController; From 2f5d1b029c278f52ccef84311e0c12a3b96aabcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 18 Apr 2019 15:10:11 +0200 Subject: [PATCH 23/57] [apps] BatteryView: avoid redrawing when battery picto switch from 'low' to 'empty' (the pictograms for low and empty are identical) --- apps/battery_view.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/battery_view.cpp b/apps/battery_view.cpp index 81b90e126..9e8b97e9c 100644 --- a/apps/battery_view.cpp +++ b/apps/battery_view.cpp @@ -22,6 +22,12 @@ const uint8_t tickMask[BatteryView::k_tickHeight][BatteryView::k_tickWidth] = { }; bool BatteryView::setChargeState(Ion::Battery::Charge chargeState) { + /* There is no specific battery picto for 'empty' battery as the whole device + * shut down. Still, there might be a redrawing of the window before shutting + * down so we handle this case as the 'low' battery one. Plus, we avoid + * trigerring a redrawing by not marking anything as dirty when switching + * from 'low' to 'empty' battery. */ + chargeState = chargeState == Ion::Battery::Charge::EMPTY ? Ion::Battery::Charge::LOW : chargeState; if (chargeState != m_chargeState) { m_chargeState = chargeState; markRectAsDirty(bounds()); @@ -52,6 +58,7 @@ KDColor s_flashWorkingBuffer[BatteryView::k_flashHeight*BatteryView::k_flashWidt KDColor s_tickWorkingBuffer[BatteryView::k_tickHeight*BatteryView::k_tickWidth]; void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { + assert(m_chargeState != Ion::Battery::Charge::EMPTY); /* We draw from left to right. The middle part representing the battery *'content' depends on the charge */ @@ -66,7 +73,7 @@ void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(batteryInsideX, 0, batteryInsideWidth, k_batteryHeight), Palette::YellowLight); KDRect frame((k_batteryWidth-k_flashWidth)/2, 0, k_flashWidth, k_flashHeight); ctx->blendRectWithMask(frame, KDColorWhite, (const uint8_t *)flashMask, s_flashWorkingBuffer); - } else if (m_chargeState == Ion::Battery::Charge::EMPTY || m_chargeState == Ion::Battery::Charge::LOW) { + } else if (m_chargeState == Ion::Battery::Charge::LOW) { assert(!m_isPlugged); // Low: Quite empty battery ctx->fillRect(KDRect(batteryInsideX, 0, 2*k_elementWidth, k_batteryHeight), Palette::LowBattery); From 13ddb80b576a54a2775f66821ae066997241475c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 18 Apr 2019 15:19:43 +0200 Subject: [PATCH 24/57] [graph] Fix build --- apps/graph/graph/preimage_graph_controller.h | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/graph/graph/preimage_graph_controller.h b/apps/graph/graph/preimage_graph_controller.h index 995b06b0a..b9a553e6a 100644 --- a/apps/graph/graph/preimage_graph_controller.h +++ b/apps/graph/graph/preimage_graph_controller.h @@ -1,4 +1,5 @@ #include "calculation_graph_controller.h" +#include namespace Graph { From 47ae5118e70da22d3333e00c23e5a2728a4259db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 18 Apr 2019 15:43:54 +0200 Subject: [PATCH 25/57] [poincare] Fix Layout::replaceWithJuxtapositionOf (keep an empty layout if both children are empty) --- poincare/src/layout.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 98641e9bd..1aef45379 100644 --- a/poincare/src/layout.cpp +++ b/poincare/src/layout.cpp @@ -99,7 +99,7 @@ void Layout::replaceWithJuxtapositionOf(Layout leftChild, Layout rightChild, Lay * replaceWith. */ HorizontalLayout horizontalLayoutR = HorizontalLayout::Builder(); p.replaceChild(*this, horizontalLayoutR, cursor); - horizontalLayoutR.addOrMergeChildAtIndex(leftChild, 0, true); + horizontalLayoutR.addOrMergeChildAtIndex(leftChild, 0, false); if (putCursorInTheMiddle) { if (!horizontalLayoutR.isEmpty()) { cursor->setLayout(horizontalLayoutR.childAtIndex(horizontalLayoutR.numberOfChildren()-1)); From c4502ceab3c938c6b3609bde1ef43a53a7bb4a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 18 Apr 2019 16:11:15 +0200 Subject: [PATCH 26/57] [sequence] Fix isDefined --- apps/sequence/sequence.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index 0f2a359d7..818d6c087 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -92,7 +92,7 @@ bool Sequence::isDefined() { case Type::SingleRecurrence: return data->initialConditionSize(0) > 0 && value().size > metaDataSize() + data->initialConditionSize(0); default: - return data->initialConditionSize(0) > 0 && data->initialConditionSize(0) > 0 && value().size > metaDataSize() + data->initialConditionSize(0) + data->initialConditionSize(1); + return data->initialConditionSize(0) > 0 && data->initialConditionSize(1) > 0 && value().size > metaDataSize() + data->initialConditionSize(0) + data->initialConditionSize(1); } } From 4f4880de2ae078bae4f02f889a91a65e9c844945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 19 Apr 2019 17:56:26 +0200 Subject: [PATCH 27/57] [poincare] Power: fix infinite loop Factorizing 1+(8+sqrt(6))^(1/2)^(-1) on the same denominator would lead to an infinite loop in Real mode (because (8+sqrt(6))^(1/2)^(-1) is not simplified to (8+sqrt(6))^(-1/2)) --- poincare/src/power.cpp | 34 ++++++++++++++++++++++++++-------- poincare/test/power.cpp | 3 ++- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index c1b5563d3..07c2c9753 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -572,10 +572,22 @@ Expression Power::shallowReduce(Context & context, Preferences::ComplexFormat co * This rule is not generally true: ((-2)^2)^(1/2) != (-2)^(2*1/2) = -2 * This rule is true if: * - a > 0 - * - in Real: when b and c are integers - * - in other modes: when c is integer - * (Warning: in real mode only c integer is not enough: - * ex: ((-2)^(1/2))^2 = unreal != -2) + * - c is an integer + * + * Warning 1: in real mode only c integer is not enough: + * ex: ((-2)^(1/2))^2 = unreal != -2 + * We escape that case by returning 'unreal' if the a^b is complex. + * + * Warning 2: If we did not apply this rule on expressions of the form + * (a^b)^(-1), we would end up in infinite loop when factorizing an addition + * on the same denominator. + * For ex: + * 1+[tan(2)^1/2]^(-1) --> (tan(2)^1/2+tan(2)^1/2*[tan(2)^1/2]^(-1))/tan(2)^1/2 + * --> tan(2)+tan(2)*[tan(2)^1/2]^(-1)/tan(2) + * --> tan(2)^(3/2)+tan(2)^(3/2)*[tan(2)^1/2]^(-1)/tan(2)^3/2 + * --> ... + * Indeed, we have to apply the rule (a^b)^c -> a^(b*c) as soon as c is an + * integer. */ if (childAtIndex(0).type() == ExpressionNode::Type::Power) { Power p = childAtIndex(0).convert(); @@ -584,11 +596,17 @@ Expression Power::shallowReduce(Context & context, Preferences::ComplexFormat co bool cInteger = (childAtIndex(1).type() == ExpressionNode::Type::Rational && childAtIndex(1).convert().integerDenominator().isOne()); if (aPositive || cInteger) { - // Check that the complex format is not Real or that b is an integer - bool bInteger = (p.childAtIndex(1).type() == ExpressionNode::Type::Rational && p.childAtIndex(1).convert().integerDenominator().isOne()); - if (aPositive || complexFormat != Preferences::ComplexFormat::Real || bInteger) { - return simplifyPowerPower(context, complexFormat, angleUnit, target); + /* If the complexFormat is real, we check that the inner power is defined + * before applying the rule (a^b)^c -> a^(b*c). Otherwise, we return + * 'unreal'. */ + if (complexFormat == Preferences::ComplexFormat::Real) { + Expression approximation = p.approximate(context, complexFormat, angleUnit); + if (approximation.type() == ExpressionNode::Type::Unreal) { + replaceWithInPlace(approximation); + return approximation; + } } + return simplifyPowerPower(context, complexFormat, angleUnit, target); } } // Step 11: (a*b*c*...)^r ? diff --git a/poincare/test/power.cpp b/poincare/test/power.cpp index d9d8692b0..bc54ba21e 100644 --- a/poincare/test/power.cpp +++ b/poincare/test/power.cpp @@ -106,5 +106,6 @@ QUIZ_CASE(poincare_power_simplify) { assert_parsed_expression_simplify_to("(-1)^(1/3)", "1/2+√(3)/2×𝐢"); assert_parsed_expression_simplify_to("R(-x)", "R(-x)"); assert_parsed_expression_simplify_to("√(x)^2", "x", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("√(x)^2", "√(x)^2", User, Radian, Real); + assert_parsed_expression_simplify_to("√(-3)^2", "unreal", User, Radian, Real); + assert_parsed_expression_simplify_to("1+((8+√(6))^(1/2))^-1+(8+√(6))^(1/2)", "(√(√(6)+8)+√(6)+9)/√(√(6)+8)", User, Radian, Real); } From fcbfc575b10119c478509a15abb35d7e80c7130e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 19 Apr 2019 17:58:33 +0200 Subject: [PATCH 28/57] [poincare] Keep unused log function when building with POINCARE_TREE_LOG --- poincare/include/poincare/tree_pool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/include/poincare/tree_pool.h b/poincare/include/poincare/tree_pool.h index 78c8a7342..a124964e3 100644 --- a/poincare/include/poincare/tree_pool.h +++ b/poincare/include/poincare/tree_pool.h @@ -44,7 +44,7 @@ public: #if POINCARE_TREE_LOG void flatLog(std::ostream & stream); void treeLog(std::ostream & stream); - void log() { treeLog(std::cout); } + __attribute__((__used__)) void log() { treeLog(std::cout); } #endif int numberOfNodes() const; From 8bfb3f7a6c655b9ec7c4718bbb69cc48adc5bc95 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 5 Apr 2019 11:52:05 +0200 Subject: [PATCH 29/57] [apps/calculation] HistoryViewCell does not need to hold layouts --- apps/calculation/history_view_cell.cpp | 24 +++++++------------ apps/calculation/history_view_cell.h | 3 --- .../scrollable_expression_view.cpp | 4 ++++ apps/calculation/scrollable_expression_view.h | 1 + 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index fe0bba7a9..a95bbb476 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -27,9 +27,6 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, HistoryViewCell::HistoryViewCell(Responder * parentResponder) : Responder(parentResponder), m_calculation(), - m_inputLayout(), - m_leftOutputLayout(), - m_rightOutputLayout(), m_inputView(this), m_scrollableOutputView(this) { @@ -64,7 +61,7 @@ void HistoryViewCell::setHighlighted(bool highlight) { Poincare::Layout HistoryViewCell::layout() const { assert(m_dataSource); if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { - return m_inputLayout; + return m_inputView.layout(); } else { return m_scrollableOutputView.layout(); } @@ -119,28 +116,23 @@ void HistoryViewCell::setCalculation(Calculation * calculation) { return; } m_calculation = *calculation; - m_inputLayout = calculation->createInputLayout(); - m_inputView.setLayout(m_inputLayout); + m_inputView.setLayout(calculation->createInputLayout()); App * calculationApp = (App *)app(); /* Both output expressions have to be updated at the same time. Otherwise, * when updating one layout, if the second one still points to a deleted * layout, calling to layoutSubviews() would fail. */ - if (!m_leftOutputLayout.isUninitialized()) { - m_leftOutputLayout = Poincare::Layout(); - } - if (!m_rightOutputLayout.isUninitialized()) { - m_rightOutputLayout = Poincare::Layout(); - } + Poincare::Layout leftOutputLayout = Poincare::Layout(); + Poincare::Layout rightOutputLayout; Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); if (display == Calculation::DisplayOutput::ExactOnly) { - m_rightOutputLayout = calculation->createExactOutputLayout(); + rightOutputLayout = calculation->createExactOutputLayout(); } else { - m_rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); + rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); if (display == Calculation::DisplayOutput::ExactAndApproximate) { - m_leftOutputLayout = calculation->createExactOutputLayout(); + leftOutputLayout = calculation->createExactOutputLayout(); } } - m_scrollableOutputView.setLayouts(m_rightOutputLayout, m_leftOutputLayout); + m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 55053d6dc..90cdf0944 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -46,9 +46,6 @@ public: private: constexpr static KDCoordinate k_resultWidth = 80; Calculation m_calculation; - Poincare::Layout m_inputLayout; - Poincare::Layout m_leftOutputLayout; - Poincare::Layout m_rightOutputLayout; ScrollableExpressionView m_inputView; Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; HistoryViewCellDataSource * m_dataSource; diff --git a/apps/calculation/scrollable_expression_view.cpp b/apps/calculation/scrollable_expression_view.cpp index 4860699ad..fae79f5d6 100644 --- a/apps/calculation/scrollable_expression_view.cpp +++ b/apps/calculation/scrollable_expression_view.cpp @@ -17,6 +17,10 @@ ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder) ); } +Poincare::Layout ScrollableExpressionView::layout() const { + return m_expressionView.layout(); +} + void ScrollableExpressionView::setLayout(Layout layout) { m_expressionView.setLayout(layout); } diff --git a/apps/calculation/scrollable_expression_view.h b/apps/calculation/scrollable_expression_view.h index 15ce343bf..95fc67350 100644 --- a/apps/calculation/scrollable_expression_view.h +++ b/apps/calculation/scrollable_expression_view.h @@ -8,6 +8,7 @@ namespace Calculation { class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource { public: ScrollableExpressionView(Responder * parentResponder); + Poincare::Layout layout() const; void setLayout(Poincare::Layout layout); void setBackgroundColor(KDColor backgroundColor) override; void setExpressionBackgroundColor(KDColor backgroundColor); From 0e89c08a26fb825d6309c4acb14c8a38b3de4ee9 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 9 Apr 2019 14:36:18 +0200 Subject: [PATCH 30/57] [escher] Simplify TableView::scrollToCell --- escher/include/escher/table_view.h | 5 +---- escher/src/table_view.cpp | 6 +----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/escher/include/escher/table_view.h b/escher/include/escher/table_view.h index ca2db0d32..5c16a8c30 100644 --- a/escher/include/escher/table_view.h +++ b/escher/include/escher/table_view.h @@ -34,7 +34,6 @@ protected: void setHorizontalCellOverlap(KDCoordinate o) { m_horizontalCellOverlap = o; } void setVerticalCellOverlap(KDCoordinate o) { m_verticalCellOverlap = o; } - void scrollToCell(int i, int j) const; void reloadCellAtLocation(int i, int j); HighlightCell * cellAtLocation(int i, int j); TableViewDataSource * dataSource(); @@ -42,6 +41,7 @@ protected: int columnsScrollingOffset() const; int numberOfDisplayableRows() const; int numberOfDisplayableColumns() const; + KDRect cellFrame(int i, int j) const; void layoutSubviews() override; protected: #if ESCHER_VIEW_LOGGING @@ -54,9 +54,6 @@ protected: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - /* realCellWidth enables to handle list view for which - * TableViewDataSource->cellWidht = 0 */ - KDRect cellFrame(int i, int j) const; /* These two methods transform an index (of subview for instance) into * coordinates that refer to the data source entire table */ int absoluteColumnNumberFromSubviewIndex(int index) const; diff --git a/escher/src/table_view.cpp b/escher/src/table_view.cpp index ed0ae9c7c..79345f8f5 100644 --- a/escher/src/table_view.cpp +++ b/escher/src/table_view.cpp @@ -20,7 +20,7 @@ TableViewDataSource * TableView::dataSource() { // This method computes the minimal scrolling needed to properly display the // requested cell. void TableView::scrollToCell(int i, int j) { - m_contentView.scrollToCell(i, j); + scrollToContentRect(m_contentView.cellFrame(i, j), true); } HighlightCell * TableView::cellAtLocation(int i, int j) { @@ -96,10 +96,6 @@ KDCoordinate TableView::ContentView::width() const { return result ? result : m_tableView->maxContentWidthDisplayableWithoutScrolling(); } -void TableView::ContentView::scrollToCell(int x, int y) const { - m_tableView->scrollToContentRect(cellFrame(x, y), true); -} - void TableView::ContentView::reloadCellAtLocation(int i, int j) { HighlightCell * cell = cellAtLocation(i, j); if (cell) { From ed04e5e9e40af9d38b751b366450ccea0bf51a56 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 11 Apr 2019 15:39:19 +0200 Subject: [PATCH 31/57] [apps/calculation] Simplify HistoryViewCell::handleEvent --- apps/calculation/history_view_cell.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index a95bbb476..de27689ed 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -153,9 +153,8 @@ bool HistoryViewCell::handleEvent(Ion::Events::Event event) { HistoryViewCellDataSource::SubviewType otherSubviewType = m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input ? HistoryViewCellDataSource::SubviewType::Output : HistoryViewCellDataSource::SubviewType::Input; CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder(); tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow()); - HistoryViewCell * selectedCell = (HistoryViewCell *)(tableView->selectedCell()); - m_dataSource->setSelectedSubviewType(otherSubviewType, selectedCell); - app()->setFirstResponder(selectedCell); + m_dataSource->setSelectedSubviewType(otherSubviewType, this); + app()->setFirstResponder(this); return true; } return false; From f2b1129ce22c3f78cef4faedc0e2548247a1b55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 22 Apr 2019 11:07:56 +0200 Subject: [PATCH 32/57] [escher] SelectableTableViewDataSource: avoid calling m_delegate->tableViewDidChangeSelection when only reloading the data. --- escher/include/escher/selectable_table_view.h | 4 ++-- escher/src/selectable_table_view.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/escher/include/escher/selectable_table_view.h b/escher/include/escher/selectable_table_view.h index bc58044ec..6e1e7c000 100644 --- a/escher/include/escher/selectable_table_view.h +++ b/escher/include/escher/selectable_table_view.h @@ -29,8 +29,8 @@ public: virtual bool handleEvent(Ion::Events::Event event) override; virtual void didEnterResponderChain(Responder * previousFirstResponder) override; virtual void willExitResponderChain(Responder * nextFirstResponder) override; - void deselectTable(); - bool selectCellAtLocation(int i, int j, bool setFirstResponder = true); + void deselectTable(bool notifySelectableDelegate = true); + bool selectCellAtLocation(int i, int j, bool setFirstResponder = true, bool notifySelectableDelegate = true); HighlightCell * selectedCell(); protected: SelectableTableViewDataSource * m_selectionDataSource; diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 7282164f9..995e7c3e7 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -37,7 +37,7 @@ void SelectableTableView::selectColumn(int i) { void SelectableTableView::reloadData(bool setFirstResponder) { int col = selectedColumn(); int row = selectedRow(); - deselectTable(); + deselectTable(false); /* FIXME: The problem with calling deselectTable is that at this point in time * the datasource's model is very likely to have changed. Therefore it's * rather complicated to get a pointer to the currently selected cell (in @@ -45,7 +45,7 @@ void SelectableTableView::reloadData(bool setFirstResponder) { /* As a workaround, datasources can reset the highlighted state in their * willDisplayCell callback. */ TableView::layoutSubviews(); - selectCellAtLocation(col, row, setFirstResponder); + selectCellAtLocation(col, row, setFirstResponder, false); } void SelectableTableView::didEnterResponderChain(Responder * previousFirstResponder) { @@ -60,18 +60,18 @@ void SelectableTableView::willExitResponderChain(Responder * nextFirstResponder) unhighlightSelectedCell(); } -void SelectableTableView::deselectTable() { +void SelectableTableView::deselectTable(bool notifySelectableDelegate) { unhighlightSelectedCell(); int previousSelectedCellX = selectedColumn(); int previousSelectedCellY = selectedRow(); selectColumn(0); selectRow(-1); - if (m_delegate) { + if (m_delegate && notifySelectableDelegate) { m_delegate->tableViewDidChangeSelection(this, previousSelectedCellX, previousSelectedCellY); } } -bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstResponder) { +bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstResponder, bool notifySelectableDelegate) { if (i < 0 || i >= dataSource()->numberOfColumns()) { return false; } @@ -84,7 +84,7 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon selectColumn(i); selectRow(j); - if (m_delegate) { + if (m_delegate && notifySelectableDelegate) { m_delegate->tableViewDidChangeSelection(this, previousX, previousY); } From 1b2ce4a18e48713bb1a5cd6b5f620938fb4957a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 22 Apr 2019 14:12:00 +0200 Subject: [PATCH 33/57] [calculation] Improve Calculation model: new display mode ExactAndApproximateToggle --- apps/calculation/calculation.cpp | 37 ++++++++++++++++++-------------- apps/calculation/calculation.h | 8 +++++-- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index efa8823d8..298cb0002 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -19,7 +19,8 @@ Calculation::Calculation() : m_exactOutputText(), m_approximateOutputText(), m_height(-1), - m_equalSign(EqualSign::Unknown) + m_equalSign(EqualSign::Unknown), + m_toggleDisplayExact(false) { } @@ -58,26 +59,31 @@ void Calculation::setContent(const char * c, Context * context, Expression ansEx PoincareHelpers::Serialize(approximateOutput, m_approximateOutputText, sizeof(m_approximateOutputText)); } -KDCoordinate Calculation::height(Context * context) { - if (m_height < 0) { +KDCoordinate Calculation::height(Context * context, bool isSelected) { + if (m_height < 0 || isSelected) { + KDCoordinate height; Layout inputLayout = createInputLayout(); KDCoordinate inputHeight = inputLayout.layoutSize().height(); Layout approximateLayout = createApproximateOutputLayout(context); Layout exactLayout = createExactOutputLayout(); DisplayOutput display = displayOutput(context); - if (display == DisplayOutput::ExactOnly) { + if (display == DisplayOutput::ExactOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle && m_toggleDisplayExact)) { KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - m_height = inputHeight+exactOutputHeight; - } else if (display == DisplayOutput::ApproximateOnly) { + height = inputHeight+exactOutputHeight; + } else if (display == DisplayOutput::ApproximateOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle && !m_toggleDisplayExact)) { KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); - m_height = inputHeight+approximateOutputHeight; + height = inputHeight+approximateOutputHeight; } else { - assert(display == DisplayOutput::ExactAndApproximate); + assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && isSelected)); KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); KDCoordinate outputHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactOutputHeight-exactLayout.baseline(), approximateOutputHeight-approximateLayout.baseline()); - m_height = inputHeight + outputHeight; + height = inputHeight + outputHeight; } + if (isSelected) { + return height; + } + m_height = height; } return m_height; } @@ -158,7 +164,6 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { if (shouldOnlyDisplayExactOutput()) { return DisplayOutput::ExactOnly; } - bool approximateOnly = false; if (exactOutput().recursivelyMatches([](const Expression e, Context & c, bool replaceSymbols) { /* If the exact result contains one of the following types, do not * display it. */ @@ -166,19 +171,19 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round);}, *context, true)) { - approximateOnly = true; + return DisplayOutput::ApproximateOnly; } else if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { /* If the exact and approximate results' texts are equal and their layouts * too, do not display the exact result. If the two layouts are not equal * because of the number of significant digits, we display both. */ - approximateOnly = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal; + return exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; } else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0) { // If the approximate result is 'unreal' or the exact result is 'undef' - approximateOnly = true; - } else { - approximateOnly = input().isApproximate(*context) || exactOutput().isApproximate(*context); + return DisplayOutput::ApproximateOnly; + } else if (input().isApproximate(*context) || exactOutput().isApproximate(*context)) { + return DisplayOutput::ExactAndApproximateToggle; } - return approximateOnly ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; + return DisplayOutput::ExactAndApproximate; } bool Calculation::shouldOnlyDisplayExactOutput() { diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 0a4822209..26ea185bf 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -21,7 +21,8 @@ public: enum class DisplayOutput : uint8_t { ExactOnly, ApproximateOnly, - ExactAndApproximate + ExactAndApproximate, + ExactAndApproximateToggle }; Calculation(); @@ -29,7 +30,7 @@ public: /* c.reset() is the equivalent of c = Calculation() without copy assingment. */ void reset(); void setContent(const char * c, Poincare::Context * context, Poincare::Expression ansExpression); - KDCoordinate height(Poincare::Context * context); + KDCoordinate height(Poincare::Context * context, bool isSelected = false); const char * inputText(); const char * exactOutputText(); const char * approximateOutputText(); @@ -42,6 +43,8 @@ public: bool isEmpty(); void tidy(); DisplayOutput displayOutput(Poincare::Context * context); + bool toggleDisplayExact() const { return m_toggleDisplayExact; } + void setToggleDisplayExact(bool displayExact) { m_toggleDisplayExact = displayExact; } bool shouldOnlyDisplayExactOutput(); EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context); private: @@ -54,6 +57,7 @@ private: char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; KDCoordinate m_height; EqualSign m_equalSign; + bool m_toggleDisplayExact; }; } From bba51b971680d763e9169eb11e3b81921e2449d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 22 Apr 2019 14:14:15 +0200 Subject: [PATCH 34/57] [calculation] Display exact and approximate outputs in the calculation history when the output is selected for calculations involving decimal numbers (1.2) --- apps/calculation/history_controller.cpp | 7 +++++-- apps/calculation/history_controller.h | 1 + apps/calculation/history_view_cell.cpp | 10 ++++------ apps/calculation/history_view_cell.h | 7 ++++++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index ba3ec25cd..c3a5e1caf 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -106,6 +106,9 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { } void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { + if (previousSelectedCellY == selectedRow()) { + return; + } if (previousSelectedCellY == -1) { setSelectedSubviewType(SubviewType::Output); } else if (selectedRow() < previousSelectedCellY) { @@ -139,7 +142,7 @@ int HistoryController::reusableCellCount(int type) { void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) { HistoryViewCell * myCell = (HistoryViewCell *)cell; - myCell->setCalculation(m_calculationStore->calculationAtIndex(index)); + myCell->setCalculation(m_calculationStore->calculationAtIndex(index), index == selectedRow() && selectedSubviewType() == SubviewType::Output); myCell->setEven(index%2 == 0); myCell->reloadCell(); } @@ -150,7 +153,7 @@ KDCoordinate HistoryController::rowHeight(int j) { } Calculation * calculation = m_calculationStore->calculationAtIndex(j); App * calculationApp = (App *)app(); - return calculation->height(calculationApp->localContext()) + 4 * Metric::CommonSmallMargin; + return calculation->height(calculationApp->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin; } int HistoryController::typeAtLocation(int i, int j) { diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index c80346b25..a41c9d501 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -28,6 +28,7 @@ public: void scrollToCell(int i, int j); private: CalculationSelectableTableView * selectableTableView(); + void historyViewCellDidChangeSelection() override { m_selectableTableView.reloadData(); } constexpr static int k_maxNumberOfDisplayedRows = 5; CalculationSelectableTableView m_selectableTableView; HistoryViewCell m_calculationHistory[k_maxNumberOfDisplayedRows]; diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index de27689ed..11a9bcb25 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -20,6 +20,7 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, if (cell) { cell->setHighlighted(cell->isHighlighted()); } + historyViewCellDidChangeSelection(); } /* HistoryViewCell */ @@ -111,10 +112,7 @@ void HistoryViewCell::layoutSubviews() { )); } -void HistoryViewCell::setCalculation(Calculation * calculation) { - if (*calculation == m_calculation) { - return; - } +void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) { m_calculation = *calculation; m_inputView.setLayout(calculation->createInputLayout()); App * calculationApp = (App *)app(); @@ -124,11 +122,11 @@ void HistoryViewCell::setCalculation(Calculation * calculation) { Poincare::Layout leftOutputLayout = Poincare::Layout(); Poincare::Layout rightOutputLayout; Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactOnly) { + if (display == Calculation::DisplayOutput::ExactOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !isSelected && calculation->toggleDisplayExact())) { rightOutputLayout = calculation->createExactOutputLayout(); } else { rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactAndApproximate) { + if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && isSelected)) { leftOutputLayout = calculation->createExactOutputLayout(); } } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 90cdf0944..d18f7c845 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -20,6 +20,10 @@ public: void setSelectedSubviewType(SubviewType subviewType, HistoryViewCell * cell = nullptr); SubviewType selectedSubviewType() { return m_selectedSubviewType; } private: + /* This method should belong to a delegate instead of a data source but as + * both the data source and the delegate will be the same controller, we + * avoid keeping 2 pointers in HistoryViewCell. */ + virtual void historyViewCellDidChangeSelection() = 0; SubviewType m_selectedSubviewType; }; @@ -36,7 +40,8 @@ public: } Poincare::Layout layout() const override; KDColor backgroundColor() const override; - void setCalculation(Calculation * calculation); + Calculation * calculation() { return &m_calculation; } + void setCalculation(Calculation * calculation, bool isSelected = false); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews() override; From 494a339c9bfb83c0fdfe100378f4e40395a45e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 23 Apr 2019 11:14:39 +0200 Subject: [PATCH 35/57] [escher] TextView: avoid useless markRectAsDirty when updating backgroundColor and textColor --- escher/src/text_view.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/escher/src/text_view.cpp b/escher/src/text_view.cpp index b8bc1b8e9..384b81ea3 100644 --- a/escher/src/text_view.cpp +++ b/escher/src/text_view.cpp @@ -1,13 +1,17 @@ #include void TextView::setBackgroundColor(KDColor backgroundColor) { - m_backgroundColor = backgroundColor; - markRectAsDirty(bounds()); + if (m_backgroundColor != backgroundColor) { + m_backgroundColor = backgroundColor; + markRectAsDirty(bounds()); + } } void TextView::setTextColor(KDColor textColor) { - m_textColor = textColor; - markRectAsDirty(bounds()); + if (m_textColor != textColor) { + m_textColor = textColor; + markRectAsDirty(bounds()); + } } void TextView::setAlignment(float horizontalAlignment, float verticalAlignment) { From 55e325cc3ab18b1be7d3b9eeee2bb5639be50d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 23 Apr 2019 11:15:17 +0200 Subject: [PATCH 36/57] [shared] ScrollableExactApproximateExpressionsView: change the selected output only when one of the two layout is uninitialized. Otherwise, the parent cell decides of which of right or left output should be selected. --- apps/calculation/history_view_cell.cpp | 8 ++++++++ .../scrollable_exact_approximate_expressions_cell.cpp | 1 + .../scrollable_exact_approximate_expressions_view.cpp | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 11a9bcb25..97108cd5c 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -133,6 +133,14 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); + + // Select the right output according to the calculation display output + if (display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && isSelected)) { + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); + } else { + assert(display == Calculation::DisplayOutput::ExactOnly || display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !isSelected)); + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); + } } void HistoryViewCell::didBecomeFirstResponder() { diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp index e9c456b75..3894849dd 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp @@ -30,6 +30,7 @@ void ScrollableExactApproximateExpressionsCell::reloadScroll() { } void ScrollableExactApproximateExpressionsCell::didBecomeFirstResponder() { + m_view.setSelectedSubviewPosition(ScrollableExactApproximateExpressionsView::SubviewPosition::Left); app()->setFirstResponder(&m_view); } diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index 0e003e857..d7a8fd0e3 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -126,7 +126,8 @@ void ScrollableExactApproximateExpressionsView::setEqualMessage(I18n::Message eq void ScrollableExactApproximateExpressionsView::didBecomeFirstResponder() { if (m_contentCell.leftExpressionView()->layout().isUninitialized()) { setSelectedSubviewPosition(SubviewPosition::Right); - } else { + } + if (m_contentCell.rightExpressionView()->layout().isUninitialized()) { setSelectedSubviewPosition(SubviewPosition::Left); } } From 0c453386d33250f684a0506bd44a49b10cf02188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 23 Apr 2019 15:12:14 +0200 Subject: [PATCH 37/57] [calculation] Calculation: improve memoization of a calculation height --- apps/calculation/calculation.cpp | 33 ++++++++++++++++++-------------- apps/calculation/calculation.h | 1 + 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 298cb0002..fc8f55f1a 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -19,6 +19,7 @@ Calculation::Calculation() : m_exactOutputText(), m_approximateOutputText(), m_height(-1), + m_selectedHeight(-1), m_equalSign(EqualSign::Unknown), m_toggleDisplayExact(false) { @@ -60,32 +61,35 @@ void Calculation::setContent(const char * c, Context * context, Expression ansEx } KDCoordinate Calculation::height(Context * context, bool isSelected) { - if (m_height < 0 || isSelected) { - KDCoordinate height; + KDCoordinate * memoizedHeight = isSelected ? &m_selectedHeight : &m_height; + if (*memoizedHeight < 0) { + DisplayOutput display = displayOutput(context); Layout inputLayout = createInputLayout(); KDCoordinate inputHeight = inputLayout.layoutSize().height(); - Layout approximateLayout = createApproximateOutputLayout(context); - Layout exactLayout = createExactOutputLayout(); - DisplayOutput display = displayOutput(context); if (display == DisplayOutput::ExactOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle && m_toggleDisplayExact)) { - KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - height = inputHeight+exactOutputHeight; + KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); + *memoizedHeight = inputHeight+exactOutputHeight; } else if (display == DisplayOutput::ApproximateOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle && !m_toggleDisplayExact)) { - KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); - height = inputHeight+approximateOutputHeight; + KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); + *memoizedHeight = inputHeight+approximateOutputHeight; } else { assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && isSelected)); + Layout approximateLayout = createApproximateOutputLayout(context); + Layout exactLayout = createExactOutputLayout(); KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); KDCoordinate outputHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactOutputHeight-exactLayout.baseline(), approximateOutputHeight-approximateLayout.baseline()); - height = inputHeight + outputHeight; + *memoizedHeight = inputHeight + outputHeight; } - if (isSelected) { - return height; + /* For all display output except ExactAndApproximateToggle, the selected + * height and the usual height are identical. We update both heights in + * theses cases. */ + if (display != DisplayOutput::ExactAndApproximateToggle) { + m_height = *memoizedHeight; + m_selectedHeight = *memoizedHeight; } - m_height = height; } - return m_height; + return *memoizedHeight; } const char * Calculation::inputText() { @@ -125,6 +129,7 @@ bool Calculation::isEmpty() { void Calculation::tidy() { /* Uninitialized all Expression stored to free the Pool */ m_height = -1; + m_selectedHeight = -1; m_equalSign = EqualSign::Unknown; } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 26ea185bf..ac16e8fd2 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -56,6 +56,7 @@ private: char m_exactOutputText[Constant::MaxSerializedExpressionSize]; char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; KDCoordinate m_height; + KDCoordinate m_selectedHeight; EqualSign m_equalSign; bool m_toggleDisplayExact; }; From 8629b4c9e96a69f883e2c003ce1813be9d2c66a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 09:43:22 +0200 Subject: [PATCH 38/57] [shared] ScrollableExactApproximateExpressionsView: selecting the right or left output reload the scroll to display the selected output --- ...crollable_exact_approximate_expressions_view.cpp | 13 ++++++++++++- .../scrollable_exact_approximate_expressions_view.h | 4 +--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index d7a8fd0e3..58f68ef10 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -123,6 +123,17 @@ void ScrollableExactApproximateExpressionsView::setEqualMessage(I18n::Message eq m_contentCell.approximateSign()->setMessage(equalSignMessage); } +void ScrollableExactApproximateExpressionsView::setSelectedSubviewPosition(SubviewPosition subviewPosition, bool scrollToSelection) { + m_contentCell.setSelectedSubviewPosition(subviewPosition); + if (scrollToSelection) { + if (subviewPosition == SubviewPosition::Left) { + reloadScroll(); + } else { + scrollToContentRect(m_contentCell.bounds(), true); + } + } +} + void ScrollableExactApproximateExpressionsView::didBecomeFirstResponder() { if (m_contentCell.leftExpressionView()->layout().isUninitialized()) { setSelectedSubviewPosition(SubviewPosition::Right); @@ -142,7 +153,7 @@ bool ScrollableExactApproximateExpressionsView::handleEvent(Ion::Events::Event e if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible) || (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftExpressionIsVisible)) { SubviewPosition otherSubviewPosition = selectedSubviewPosition() == SubviewPosition::Left ? SubviewPosition::Right : SubviewPosition::Left; - setSelectedSubviewPosition(otherSubviewPosition); + setSelectedSubviewPosition(otherSubviewPosition, false); return true; } return ScrollableView::handleEvent(event); diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.h b/apps/shared/scrollable_exact_approximate_expressions_view.h index 38a35357e..10b581861 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.h +++ b/apps/shared/scrollable_exact_approximate_expressions_view.h @@ -20,9 +20,7 @@ public: SubviewPosition selectedSubviewPosition() { return m_contentCell.selectedSubviewPosition(); } - void setSelectedSubviewPosition(SubviewPosition subviewPosition) { - m_contentCell.setSelectedSubviewPosition(subviewPosition); - } + void setSelectedSubviewPosition(SubviewPosition subviewPosition, bool reloadScroll = true); void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; Poincare::Layout layout() const { From e29c2b8b39220d8c8d2aed6850591e8601b4a75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 09:45:29 +0200 Subject: [PATCH 39/57] [calculation] HistoryViewCell: reloadCell selects the right or left output according to the calculation hold (this enables to discard unnecessary reloadScroll) --- apps/calculation/history_view_cell.cpp | 25 ++++++++++++------------- apps/calculation/history_view_cell.h | 1 - 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 97108cd5c..3310771c5 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -56,7 +56,6 @@ void HistoryViewCell::setHighlighted(bool highlight) { m_scrollableOutputView.evenOddCell()->setHighlighted(true); } } - reloadScroll(); } Poincare::Layout HistoryViewCell::layout() const { @@ -71,12 +70,20 @@ Poincare::Layout HistoryViewCell::layout() const { void HistoryViewCell::reloadCell() { m_scrollableOutputView.evenOddCell()->reloadCell(); layoutSubviews(); - reloadScroll(); -} -void HistoryViewCell::reloadScroll() { + // Reload input scroll m_inputView.reloadScroll(); - m_scrollableOutputView.reloadScroll(); + + /* Select the right output according to the calculation display output. This + * will reload the scroll to display the selected output. */ + App * calculationApp = (App *)app(); + Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); + if (display == Calculation::DisplayOutput::ExactAndApproximate) { + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); + } else { + assert(display == Calculation::DisplayOutput::ApproximateOnly || display == Calculation::DisplayOutput::ExactAndApproximateToggle || display == Calculation::DisplayOutput::ExactOnly); + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); + } } KDColor HistoryViewCell::backgroundColor() const { @@ -133,14 +140,6 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); - - // Select the right output according to the calculation display output - if (display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && isSelected)) { - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); - } else { - assert(display == Calculation::DisplayOutput::ExactOnly || display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !isSelected)); - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); - } } void HistoryViewCell::didBecomeFirstResponder() { diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index d18f7c845..d106ce922 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -31,7 +31,6 @@ class HistoryViewCell : public ::EvenOddCell, public Responder { public: HistoryViewCell(Responder * parentResponder = nullptr); void reloadCell() override; - void reloadScroll(); void setEven(bool even) override; void setHighlighted(bool highlight) override; void setDataSource(HistoryViewCellDataSource * dataSource) { m_dataSource = dataSource; } From ff87c8c53c41774bb384c3d49774bebc6200970a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 14:34:18 +0200 Subject: [PATCH 40/57] [escher] ScrollableView: discard duplicate data source of scrolling offset --- ...lable_exact_approximate_expressions_view.cpp | 4 ++-- escher/include/escher/scrollable_view.h | 1 - escher/src/scrollable_view.cpp | 17 +++++++---------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index 58f68ef10..dbb70ce11 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -147,9 +147,9 @@ bool ScrollableExactApproximateExpressionsView::handleEvent(Ion::Events::Event e if (m_contentCell.leftExpressionView()->layout().isUninitialized()) { return ScrollableView::handleEvent(event); } - bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - m_contentCell.rightExpressionView()->minimalSizeForOptimalDisplay().width() - m_manualScrollingOffset.x() < bounds().width() + bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - m_contentCell.rightExpressionView()->minimalSizeForOptimalDisplay().width() - contentOffset().x() < bounds().width() ; - bool leftExpressionIsVisible = m_contentCell.leftExpressionView()->minimalSizeForOptimalDisplay().width() - m_manualScrollingOffset.x() > 0; + bool leftExpressionIsVisible = m_contentCell.leftExpressionView()->minimalSizeForOptimalDisplay().width() - contentOffset().x() > 0; if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible) || (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftExpressionIsVisible)) { SubviewPosition otherSubviewPosition = selectedSubviewPosition() == SubviewPosition::Left ? SubviewPosition::Right : SubviewPosition::Left; diff --git a/escher/include/escher/scrollable_view.h b/escher/include/escher/scrollable_view.h index ba4aaea4e..6d82205e3 100644 --- a/escher/include/escher/scrollable_view.h +++ b/escher/include/escher/scrollable_view.h @@ -12,7 +12,6 @@ public: void reloadScroll(bool forceRelayout = false); protected: KDSize contentSize() const override; - KDPoint m_manualScrollingOffset; }; #endif diff --git a/escher/src/scrollable_view.cpp b/escher/src/scrollable_view.cpp index 9efc0fd1e..5d5e90f57 100644 --- a/escher/src/scrollable_view.cpp +++ b/escher/src/scrollable_view.cpp @@ -7,8 +7,7 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur ScrollableView::ScrollableView(Responder * parentResponder, View * view, ScrollViewDataSource * dataSource) : Responder(parentResponder), - ScrollView(view, dataSource), - m_manualScrollingOffset(KDPointZero) + ScrollView(view, dataSource) { setDecoratorType(ScrollView::Decorator::Type::None); } @@ -16,40 +15,38 @@ ScrollableView::ScrollableView(Responder * parentResponder, View * view, ScrollV bool ScrollableView::handleEvent(Ion::Events::Event event) { KDPoint translation = KDPointZero; if (event == Ion::Events::Left) { - KDCoordinate movementToEdge = m_manualScrollingOffset.x(); + KDCoordinate movementToEdge = contentOffset().x(); if (movementToEdge > 0) { translation = KDPoint(-minCoordinate(Metric::ScrollStep, movementToEdge), 0); } } if (event == Ion::Events::Right) { - KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().width() - bounds().width() - m_manualScrollingOffset.x(); + KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().width() - bounds().width() - contentOffset().x(); if (movementToEdge > 0) { translation = KDPoint(minCoordinate(Metric::ScrollStep, movementToEdge), 0); } } if (event == Ion::Events::Up) { - KDCoordinate movementToEdge = m_manualScrollingOffset.y(); + KDCoordinate movementToEdge = contentOffset().y(); if (movementToEdge > 0) { translation = KDPoint(0, -minCoordinate(Metric::ScrollStep, movementToEdge)); } } if (event == Ion::Events::Down) { - KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().height() - bounds().height() - m_manualScrollingOffset.y(); + KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().height() - bounds().height() - contentOffset().y(); if (movementToEdge > 0) { translation = KDPoint(0, minCoordinate(Metric::ScrollStep, movementToEdge)); } } if (translation != KDPointZero) { - m_manualScrollingOffset = m_manualScrollingOffset.translatedBy(translation); - setContentOffset(m_manualScrollingOffset); + setContentOffset(contentOffset().translatedBy(translation)); return true; } return false; } void ScrollableView::reloadScroll(bool forceReLayout) { - m_manualScrollingOffset = KDPointZero; - setContentOffset(m_manualScrollingOffset, forceReLayout); + setContentOffset(KDPointZero, forceReLayout); } KDSize ScrollableView::contentSize() const { From 75f2b55dd8db4f68bb968e326d6aa503b43c8364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 14:56:07 +0200 Subject: [PATCH 41/57] [shared] ScrollableExactApproximateExpressionsView: fix scrolling when selecting the left or right output result --- apps/shared/scrollable_exact_approximate_expressions_view.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index dbb70ce11..f0e0831f3 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -127,9 +127,11 @@ void ScrollableExactApproximateExpressionsView::setSelectedSubviewPosition(Subvi m_contentCell.setSelectedSubviewPosition(subviewPosition); if (scrollToSelection) { if (subviewPosition == SubviewPosition::Left) { + // Scroll to the left extremity reloadScroll(); } else { - scrollToContentRect(m_contentCell.bounds(), true); + // Scroll to the right extremity + scrollToContentPoint(KDPoint(m_contentCell.bounds().width(), 0), true); } } } From c6846bb85c3f5859d0d869525e508988e89934af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 15:32:21 +0200 Subject: [PATCH 42/57] [poincare] VerticalOffsetLayout: change name of 'Type' to 'Position' --- apps/regression/calculation_controller.cpp | 2 +- apps/regression/model/cubic_model.cpp | 4 +-- apps/regression/model/exponential_model.cpp | 2 +- apps/regression/model/logistic_model.cpp | 2 +- apps/regression/model/power_model.cpp | 2 +- apps/regression/model/quadratic_model.cpp | 2 +- apps/regression/model/quartic_model.cpp | 6 ++--- apps/sequence/list/sequence_toolbox.cpp | 6 ++--- .../list/type_parameter_controller.cpp | 2 +- apps/sequence/sequence.cpp | 10 +++---- .../sub_menu/preferences_controller.cpp | 2 +- apps/solver/solutions_controller.cpp | 2 +- .../include/poincare/vertical_offset_layout.h | 14 +++++----- poincare/src/layout_cursor.cpp | 8 +++--- poincare/src/layout_helper.cpp | 2 +- poincare/src/power.cpp | 2 +- poincare/src/symbol.cpp | 8 +++--- poincare/src/vertical_offset_layout.cpp | 26 +++++++++---------- poincare/test/layouts.cpp | 14 +++++----- poincare/test/vertical_offset_layout.cpp | 2 +- 20 files changed, 59 insertions(+), 59 deletions(-) diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 7a7bfce1b..0a0ddb1db 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -25,7 +25,7 @@ CalculationController::CalculationController(Responder * parentResponder, Button m_hideableCell(), m_store(store) { - m_r2Layout = HorizontalLayout::Builder(CodePointLayout::Builder('r', KDFont::SmallFont), VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript)); + m_r2Layout = HorizontalLayout::Builder(CodePointLayout::Builder('r', KDFont::SmallFont), VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Superscript)); m_selectableTableView.setVerticalCellOverlap(0); m_selectableTableView.setBackgroundColor(Palette::WallScreenDark); m_selectableTableView.setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin); diff --git a/apps/regression/model/cubic_model.cpp b/apps/regression/model/cubic_model.cpp index 7a1febc48..32bd4d4af 100644 --- a/apps/regression/model/cubic_model.cpp +++ b/apps/regression/model/cubic_model.cpp @@ -26,7 +26,7 @@ Layout CubicModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('b', k_layoutFont), @@ -34,7 +34,7 @@ Layout CubicModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('c', k_layoutFont), diff --git a/apps/regression/model/exponential_model.cpp b/apps/regression/model/exponential_model.cpp index be49691c0..e29bdb047 100644 --- a/apps/regression/model/exponential_model.cpp +++ b/apps/regression/model/exponential_model.cpp @@ -22,7 +22,7 @@ Layout ExponentialModel::layout() { CodePointLayout::Builder(UCodePointMiddleDot, k_layoutFont), CodePointLayout::Builder('X', k_layoutFont) ), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ) }; m_layout = HorizontalLayout::Builder(layoutChildren, size); diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index 9ff69660f..5ffe02a48 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -28,7 +28,7 @@ Layout LogisticModel::layout() { CodePointLayout::Builder('e', k_layoutFont), VerticalOffsetLayout::Builder( HorizontalLayout::Builder(exponentLayoutChildren, exponentSize), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ) }; m_layout = FractionLayout::Builder( diff --git a/apps/regression/model/power_model.cpp b/apps/regression/model/power_model.cpp index 779d290a7..928e25514 100644 --- a/apps/regression/model/power_model.cpp +++ b/apps/regression/model/power_model.cpp @@ -19,7 +19,7 @@ Layout PowerModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('b', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), }; m_layout = HorizontalLayout::Builder(layoutChildren, size); diff --git a/apps/regression/model/quadratic_model.cpp b/apps/regression/model/quadratic_model.cpp index 51b64eceb..a2cb20400 100644 --- a/apps/regression/model/quadratic_model.cpp +++ b/apps/regression/model/quadratic_model.cpp @@ -26,7 +26,7 @@ Layout QuadraticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('b', k_layoutFont), diff --git a/apps/regression/model/quartic_model.cpp b/apps/regression/model/quartic_model.cpp index 84d021e6d..09388c0a3 100644 --- a/apps/regression/model/quartic_model.cpp +++ b/apps/regression/model/quartic_model.cpp @@ -26,7 +26,7 @@ Layout QuarticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('4', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('b', k_layoutFont), @@ -34,7 +34,7 @@ Layout QuarticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('c', k_layoutFont), @@ -42,7 +42,7 @@ Layout QuarticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('d', k_layoutFont), diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index b71c41246..e6281baae 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -78,18 +78,18 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu const char * indice = j == 0 ? "n" : "n+1"; m_addedCellLayout[j] = HorizontalLayout::Builder( CodePointLayout::Builder(sequenceName[0], KDFont::LargeFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); m_addedCellLayout[j+recurrenceDepth] = HorizontalLayout::Builder( CodePointLayout::Builder(otherSequenceName[0], KDFont::LargeFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); } if (recurrenceDepth < 2) { const char * indice = recurrenceDepth == 0 ? "n" : (recurrenceDepth == 1 ? "n+1" : "n+2"); m_addedCellLayout[2*recurrenceDepth] = HorizontalLayout::Builder( CodePointLayout::Builder(otherSequenceName[0], KDFont::LargeFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); } } diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index ab01f1c43..aa2e9b4e7 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -124,7 +124,7 @@ void TypeParameterController::willDisplayCellAtLocation(HighlightCell * cell, in const char * subscripts[3] = {"n", "n+1", "n+2"}; m_layouts[j] = HorizontalLayout::Builder( CodePointLayout::Builder(nextName[0], font), - VerticalOffsetLayout::Builder(LayoutHelper::String(subscripts[j], strlen(subscripts[j]), font), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(subscripts[j], strlen(subscripts[j]), font), VerticalOffsetLayoutNode::Position::Subscript) ); ExpressionTableCellWithPointer * myCell = (ExpressionTableCellWithPointer *)cell; myCell->setLayout(m_layouts[j]); diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index 818d6c087..cf03d9e9e 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -78,7 +78,7 @@ Poincare::Layout Sequence::nameLayout() { if (m_nameLayout.isUninitialized()) { m_nameLayout = HorizontalLayout::Builder( CodePointLayout::Builder(fullName()[0], KDFont::SmallFont), - VerticalOffsetLayout::Builder(CodePointLayout::Builder(Symbol(), KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(CodePointLayout::Builder(Symbol(), KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Subscript) ); } return m_nameLayout; @@ -236,16 +236,16 @@ void Sequence::DefinitionModel::buildName(Sequence * sequence) { if (sequence->type() == Type::Explicit) { m_name = HorizontalLayout::Builder( CodePointLayout::Builder(name, k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String("n", 1, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(LayoutHelper::String("n", 1, k_layoutFont), VerticalOffsetLayoutNode::Position::Subscript)); } else if (sequence->type() == Type::SingleRecurrence) { m_name = HorizontalLayout::Builder( CodePointLayout::Builder(name, k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String("n+1", 3, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(LayoutHelper::String("n+1", 3, k_layoutFont), VerticalOffsetLayoutNode::Position::Subscript)); } else { assert(sequence->type() == Type::DoubleRecurrence); m_name = HorizontalLayout::Builder( CodePointLayout::Builder(name, k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String("n+2", 3, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(LayoutHelper::String("n+2", 3, k_layoutFont), VerticalOffsetLayoutNode::Position::Subscript)); } } @@ -273,7 +273,7 @@ void Sequence::InitialConditionModel::buildName(Sequence * sequence) { Layout indexLayout = LayoutHelper::String(buffer, strlen(buffer), k_layoutFont); m_name = HorizontalLayout::Builder( CodePointLayout::Builder(sequence->fullName()[0], k_layoutFont), - VerticalOffsetLayout::Builder(indexLayout, VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(indexLayout, VerticalOffsetLayoutNode::Position::Subscript)); } template double Sequence::templatedApproximateAtAbscissa(double, SequenceContext*) const; diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index fb53112d4..527a4a530 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -94,7 +94,7 @@ Layout PreferencesController::layoutForPreferences(I18n::Message message) { const char * superscript = "𝐢θ"; return HorizontalLayout::Builder( LayoutHelper::String(base, strlen(base), k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(superscript, strlen(superscript), k_layoutFont), VerticalOffsetLayoutNode::Type::Superscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(superscript, strlen(superscript), k_layoutFont), VerticalOffsetLayoutNode::Position::Superscript) ); } default: diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 68041e423..346e053e6 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -77,7 +77,7 @@ SolutionsController::SolutionsController(Responder * parentResponder, EquationSt m_delta2Layout(), m_contentView(this) { - m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); + m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); const char * deltaB = "Δ=b"; static_cast(m_delta2Layout).addOrMergeChildAtIndex(LayoutHelper::String(deltaB, 3, KDFont::SmallFont), 0, false); for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) { diff --git a/poincare/include/poincare/vertical_offset_layout.h b/poincare/include/poincare/vertical_offset_layout.h index 94162aa44..7a5e35341 100644 --- a/poincare/include/poincare/vertical_offset_layout.h +++ b/poincare/include/poincare/vertical_offset_layout.h @@ -8,18 +8,18 @@ namespace Poincare { class VerticalOffsetLayoutNode final : public LayoutNode { public: - enum class Type { + enum class Position { Subscript, Superscript }; - VerticalOffsetLayoutNode(Type type = Type::Superscript) : + VerticalOffsetLayoutNode(Position position = Position::Superscript) : LayoutNode(), - m_type(type) + m_position(position) {} // VerticalOffsetLayoutNode - Type type() const { return m_type; } + Position position() const { return m_position; } // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; @@ -37,7 +37,7 @@ public: int numberOfChildren() const override { return 1; } #if POINCARE_TREE_LOG virtual void logNodeName(std::ostream & stream) const override { - stream << (m_type == Type::Subscript ? "Subscript" : "Superscript"); + stream << (m_position == Position::Subscript ? "Subscript" : "Superscript"); } #endif @@ -53,12 +53,12 @@ private: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override {} LayoutNode * indiceLayout() { return childAtIndex(0); } LayoutNode * baseLayout(); - Type m_type; + Position m_position; }; class VerticalOffsetLayout final : public Layout { public: - static VerticalOffsetLayout Builder(Layout l, VerticalOffsetLayoutNode::Type type); + static VerticalOffsetLayout Builder(Layout l, VerticalOffsetLayoutNode::Position position); VerticalOffsetLayout() = delete; }; diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index da42ec1a4..234d35b14 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -78,7 +78,7 @@ void LayoutCursor::addEmptyExponentialLayout() { EmptyLayout emptyLayout = EmptyLayout::Builder(); HorizontalLayout sibling = HorizontalLayout::Builder( CodePointLayout::Builder(UCodePointScriptSmallE), - VerticalOffsetLayout::Builder(emptyLayout, VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayout::Builder(emptyLayout, VerticalOffsetLayoutNode::Position::Superscript)); m_layout.addSibling(this, sibling, false); m_layout = emptyLayout; } @@ -103,13 +103,13 @@ void LayoutCursor::addEmptySquareRootLayout() { } void LayoutCursor::addEmptyPowerLayout() { - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(EmptyLayout::Builder(), VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(EmptyLayout::Builder(), VerticalOffsetLayoutNode::Position::Superscript); privateAddEmptyPowerLayout(offsetLayout); m_layout = offsetLayout.childAtIndex(0); } void LayoutCursor::addEmptySquarePowerLayout() { - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Position::Superscript); privateAddEmptyPowerLayout(offsetLayout); } @@ -121,7 +121,7 @@ void LayoutCursor::addEmptyTenPowerLayout() { CodePointLayout::Builder('0'), VerticalOffsetLayout::Builder( emptyLayout, - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); m_layout.addSibling(this, sibling, false); m_layout = emptyLayout; } diff --git a/poincare/src/layout_helper.cpp b/poincare/src/layout_helper.cpp index 91a48b984..c5f989c62 100644 --- a/poincare/src/layout_helper.cpp +++ b/poincare/src/layout_helper.cpp @@ -92,7 +92,7 @@ HorizontalLayout LayoutHelper::CodePointString(const CodePoint * buffer, int buf Layout LayoutHelper::Logarithm(Layout argument, Layout index) { HorizontalLayout resultLayout = String("log", 3); - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(index, VerticalOffsetLayoutNode::Type::Subscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(index, VerticalOffsetLayoutNode::Position::Subscript); resultLayout.addChildAtIndex(offsetLayout, resultLayout.numberOfChildren(), resultLayout.numberOfChildren(), nullptr); resultLayout.addOrMergeChildAtIndex(Parentheses(argument, false), resultLayout.numberOfChildren(), true); return resultLayout; diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 07c2c9753..f17f5d676 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -138,7 +138,7 @@ Layout PowerNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); result.addChildAtIndex(VerticalOffsetLayout::Builder( indiceOperand->createLayout(floatDisplayMode, numberOfSignificantDigits), - VerticalOffsetLayoutNode::Type::Superscript), + VerticalOffsetLayoutNode::Position::Superscript), result.numberOfChildren(), result.numberOfChildren(), nullptr); diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 4d64effca..65037ff94 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -90,28 +90,28 @@ Layout SymbolNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, in CodePointLayout::Builder('u'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('n'), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } if (strcmp(m_name, "u(n+1)") == 0) { return HorizontalLayout::Builder( CodePointLayout::Builder('u'), VerticalOffsetLayout::Builder( LayoutHelper::String("n+1", 3), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } if (strcmp(m_name, "v(n)") == 0) { return HorizontalLayout::Builder( CodePointLayout::Builder('v'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('n'), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } if (strcmp(m_name, "v(n+1)") == 0) { return HorizontalLayout::Builder( CodePointLayout::Builder('v'), VerticalOffsetLayout::Builder( LayoutHelper::String("n+1", 3), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } return LayoutHelper::String(m_name, strlen(m_name)); } diff --git a/poincare/src/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 627e15af7..03077a5b3 100644 --- a/poincare/src/vertical_offset_layout.cpp +++ b/poincare/src/vertical_offset_layout.cpp @@ -54,7 +54,7 @@ void VerticalOffsetLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * sho } void VerticalOffsetLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { - if (m_type == Type::Superscript) { + if (m_position == Position::Superscript) { // Case: Superscript. if (cursor->isEquivalentTo(LayoutCursor(this, LayoutCursor::Position::Right))) { // Case: Right. Move to the indice. @@ -71,7 +71,7 @@ void VerticalOffsetLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * should } /* Case: Subscript, Left or Right of the indice. Put the cursor at the same * position, pointing this. */ - if (m_type == Type::Subscript + if (m_position == Position::Subscript && (cursor->isEquivalentTo(LayoutCursor(indiceLayout(), LayoutCursor::Position::Left)) || cursor->isEquivalentTo(LayoutCursor(indiceLayout(), LayoutCursor::Position::Right)))) { @@ -82,7 +82,7 @@ void VerticalOffsetLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * should } void VerticalOffsetLayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { - if (m_type == Type::Subscript) { + if (m_position == Position::Subscript) { // Case: Subscript. if (cursor->isEquivalentTo(LayoutCursor(this, LayoutCursor::Position::Right))) { // Case: Right. Move to the indice. @@ -99,7 +99,7 @@ void VerticalOffsetLayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shou } /* Case: Superscript, Left or Right of the indice. Put the cursor at the same * position, pointing this. */ - if (m_type == Type::Superscript + if (m_position == Position::Superscript && cursor->layoutNode() == indiceLayout()) { cursor->setLayoutNode(this); @@ -149,7 +149,7 @@ void VerticalOffsetLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { } int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - if (m_type == Type::Subscript) { + if (m_position == Position::Subscript) { if (bufferSize == 0) { return -1; } @@ -172,7 +172,7 @@ int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferenc return numberOfChar; } - assert(m_type == Type::Superscript); + assert(m_position == Position::Superscript); /* If the layout is a superscript, write: * "UCodePointLeftSuperscript indice UCodePointRightSuperscript" */ int numberOfChar = SerializationHelper::CodePoint(buffer, bufferSize, UCodePointLeftSuperscript); @@ -189,7 +189,7 @@ int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferenc KDSize VerticalOffsetLayoutNode::computeSize() { KDSize indiceSize = indiceLayout()->layoutSize(); KDCoordinate width = indiceSize.width(); - if (m_type == Type::Superscript) { + if (m_position == Position::Superscript) { LayoutNode * parentNode = parent(); assert(parentNode != nullptr); assert(parentNode->isHorizontal()); @@ -203,7 +203,7 @@ KDSize VerticalOffsetLayoutNode::computeSize() { } KDCoordinate VerticalOffsetLayoutNode::computeBaseline() { - if (m_type == Type::Subscript) { + if (m_position == Position::Subscript) { return baseLayout()->baseline(); } else { return indiceLayout()->layoutSize().height() - k_indiceHeight + baseLayout()->baseline(); @@ -212,17 +212,17 @@ KDCoordinate VerticalOffsetLayoutNode::computeBaseline() { KDPoint VerticalOffsetLayoutNode::positionOfChild(LayoutNode * child) { assert(child == indiceLayout()); - if (m_type == Type::Superscript) { + if (m_position == Position::Superscript) { return KDPointZero; } - assert(m_type == Type::Subscript); + assert(m_position == Position::Subscript); return KDPoint(0, baseLayout()->layoutSize().height() - k_indiceHeight); } bool VerticalOffsetLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) { if (sibling->isVerticalOffset()) { VerticalOffsetLayoutNode * verticalOffsetSibling = static_cast(sibling); - if (verticalOffsetSibling->type() == Type::Superscript) { + if (verticalOffsetSibling->position() == Position::Superscript) { Layout rootLayout = root(); Layout thisRef = Layout(this); Layout parentRef = Layout(parent()); @@ -265,9 +265,9 @@ LayoutNode * VerticalOffsetLayoutNode::baseLayout() { return parentNode->childAtIndex(idxInParent - 1); } -VerticalOffsetLayout VerticalOffsetLayout::Builder(Layout l, VerticalOffsetLayoutNode::Type type) { +VerticalOffsetLayout VerticalOffsetLayout::Builder(Layout l, VerticalOffsetLayoutNode::Position position) { void * bufferNode = TreePool::sharedPool()->alloc(sizeof(VerticalOffsetLayoutNode)); - VerticalOffsetLayoutNode * node = new (bufferNode) VerticalOffsetLayoutNode(type); + VerticalOffsetLayoutNode * node = new (bufferNode) VerticalOffsetLayoutNode(position); TreeHandle h = TreeHandle::BuildWithGhostChildren(node); h.replaceChildAtIndexInPlace(0, l); return static_cast(h); diff --git a/poincare/test/layouts.cpp b/poincare/test/layouts.cpp index 5a3171ffc..36c613be3 100644 --- a/poincare/test/layouts.cpp +++ b/poincare/test/layouts.cpp @@ -67,7 +67,7 @@ QUIZ_CASE(poincare_create_all_layouts) { EmptyLayout e23 = EmptyLayout::Builder(); EmptyLayout e24 = EmptyLayout::Builder(); SumLayout e25 = SumLayout::Builder(e21, e22, e23, e24); - VerticalOffsetLayout e26 = VerticalOffsetLayout::Builder(e25, VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout e26 = VerticalOffsetLayout::Builder(e25, VerticalOffsetLayoutNode::Position::Superscript); } Matrix BuildOneChildMatrix(Expression entry) { @@ -141,7 +141,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('3'), CodePointLayout::Builder('+'), CodePointLayout::Builder('4')), - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); e = Power::Builder( Rational::Builder(2), Addition::Builder( @@ -154,7 +154,7 @@ QUIZ_CASE(poincare_parse_layouts) { l1.addChildAtIndex(CodePointLayout::Builder('l'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(CodePointLayout::Builder('o'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(CodePointLayout::Builder('g'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); - l1.addChildAtIndex(VerticalOffsetLayout::Builder(CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Type::Subscript), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(VerticalOffsetLayout::Builder(CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Position::Subscript), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(LeftParenthesisLayout::Builder(), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(CodePointLayout::Builder('2'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(RightParenthesisLayout::Builder(), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); @@ -189,7 +189,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2'), - VerticalOffsetLayoutNode::Type::Superscript), + VerticalOffsetLayoutNode::Position::Superscript), CodePointLayout::Builder('!')); e = Factorial::Builder( Power::Builder( @@ -223,7 +223,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('3'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2'), - VerticalOffsetLayoutNode::Type::Superscript), + VerticalOffsetLayoutNode::Position::Superscript), CodePointLayout::Builder('!')), CodePointLayout::Builder('7'), CodePointLayout::Builder('4'), @@ -251,7 +251,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('7'), CodePointLayout::Builder('4'), CodePointLayout::Builder('5')), - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); m = BuildOneChildMatrix( Factorial::Builder( Rational::Builder(3))); @@ -268,7 +268,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder(UCodePointScriptSmallE), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3'), - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); e = Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(UCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3)))); assert_parsed_expression_is("2ℯ^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(UCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3))))); assert_parsed_layout_is(l, e); diff --git a/poincare/test/vertical_offset_layout.cpp b/poincare/test/vertical_offset_layout.cpp index d30b58ee2..7da0bbae7 100644 --- a/poincare/test/vertical_offset_layout.cpp +++ b/poincare/test/vertical_offset_layout.cpp @@ -11,7 +11,7 @@ QUIZ_CASE(poincare_vertical_offset_layout_serialize) { CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( LayoutHelper::String("x+5", 3), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ) ); assert(UCodePointLeftSuperscript == '\x12'); From 064025edd58e1d7e5932c3ddc4017646f1602216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 15:45:55 +0200 Subject: [PATCH 43/57] [poincare] Layout: add a Type to all layouts --- .../include/poincare/absolute_value_layout.h | 3 +++ .../poincare/binomial_coefficient_layout.h | 3 +++ .../include/poincare/bracket_pair_layout.h | 3 +++ poincare/include/poincare/ceiling_layout.h | 3 +++ poincare/include/poincare/code_point_layout.h | 3 +++ .../include/poincare/condensed_sum_layout.h | 3 +++ poincare/include/poincare/conjugate_layout.h | 3 +++ poincare/include/poincare/empty_layout.h | 3 +++ poincare/include/poincare/floor_layout.h | 4 +++ poincare/include/poincare/fraction_layout.h | 3 +++ poincare/include/poincare/grid_layout.h | 3 +++ poincare/include/poincare/horizontal_layout.h | 3 +++ poincare/include/poincare/integral_layout.h | 3 +++ poincare/include/poincare/layout.h | 3 +++ poincare/include/poincare/layout_node.h | 27 +++++++++++++++++++ .../poincare/left_parenthesis_layout.h | 3 +++ .../poincare/left_square_bracket_layout.h | 4 +++ poincare/include/poincare/matrix_layout.h | 3 +++ poincare/include/poincare/nth_root_layout.h | 3 +++ poincare/include/poincare/product_layout.h | 4 +++ .../poincare/right_parenthesis_layout.h | 3 +++ .../poincare/right_square_bracket_layout.h | 4 +++ poincare/include/poincare/sum_layout.h | 4 +++ .../include/poincare/vertical_offset_layout.h | 3 +++ 24 files changed, 101 insertions(+) diff --git a/poincare/include/poincare/absolute_value_layout.h b/poincare/include/poincare/absolute_value_layout.h index 5a909f36d..f23b56737 100644 --- a/poincare/include/poincare/absolute_value_layout.h +++ b/poincare/include/poincare/absolute_value_layout.h @@ -11,6 +11,9 @@ class AbsoluteValueLayoutNode final : public BracketPairLayoutNode { public: using BracketPairLayoutNode::BracketPairLayoutNode; + // Layout + Type type() const override { return Type::AbsoluteValueLayout; } + // SerializationHelperInterface int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, AbsoluteValue::s_functionHelper.name()); diff --git a/poincare/include/poincare/binomial_coefficient_layout.h b/poincare/include/poincare/binomial_coefficient_layout.h index 2187496ce..9a6136616 100644 --- a/poincare/include/poincare/binomial_coefficient_layout.h +++ b/poincare/include/poincare/binomial_coefficient_layout.h @@ -11,6 +11,9 @@ class BinomialCoefficientLayoutNode final : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::BinomialCoefficientLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/bracket_pair_layout.h b/poincare/include/poincare/bracket_pair_layout.h index c3e33309b..7505596c8 100644 --- a/poincare/include/poincare/bracket_pair_layout.h +++ b/poincare/include/poincare/bracket_pair_layout.h @@ -10,6 +10,9 @@ class BracketPairLayoutNode : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::BracketPairLayout; } + static void RenderWithChildSize(KDSize childSize, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); // LayoutNode diff --git a/poincare/include/poincare/ceiling_layout.h b/poincare/include/poincare/ceiling_layout.h index 6a3755175..fe3377f5d 100644 --- a/poincare/include/poincare/ceiling_layout.h +++ b/poincare/include/poincare/ceiling_layout.h @@ -12,6 +12,9 @@ class CeilingLayoutNode final : public BracketPairLayoutNode { public: using BracketPairLayoutNode::BracketPairLayoutNode; + // Layout + Type type() const override { return Type::CeilingLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, Ceiling::s_functionHelper.name()); } diff --git a/poincare/include/poincare/code_point_layout.h b/poincare/include/poincare/code_point_layout.h index 458641c39..e596c200c 100644 --- a/poincare/include/poincare/code_point_layout.h +++ b/poincare/include/poincare/code_point_layout.h @@ -20,6 +20,9 @@ public: m_font(font) {} + // Layout + Type type() const override { return Type::CodePointLayout; } + // CodePointLayout CodePoint codePoint() const { return m_codePoint; } const KDFont * font() const { return m_font; } diff --git a/poincare/include/poincare/condensed_sum_layout.h b/poincare/include/poincare/condensed_sum_layout.h index 93c8ca9f9..0495cbdb1 100644 --- a/poincare/include/poincare/condensed_sum_layout.h +++ b/poincare/include/poincare/condensed_sum_layout.h @@ -13,6 +13,9 @@ class CondensedSumLayoutNode /*final*/ : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::CondensedSumLayout; } + /* CondensedSumLayout is only used in apps/shared/sum_graph_controller.cpp, in * a view with no cursor. */ void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override { assert(false); } diff --git a/poincare/include/poincare/conjugate_layout.h b/poincare/include/poincare/conjugate_layout.h index 8dabdf3cf..4f7747d47 100644 --- a/poincare/include/poincare/conjugate_layout.h +++ b/poincare/include/poincare/conjugate_layout.h @@ -10,6 +10,9 @@ class ConjugateLayoutNode final : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::ConjugateLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/empty_layout.h b/poincare/include/poincare/empty_layout.h index cc07406bb..2b8a7ccea 100644 --- a/poincare/include/poincare/empty_layout.h +++ b/poincare/include/poincare/empty_layout.h @@ -13,6 +13,9 @@ public: Grey }; + // Layout + Type type() const override { return Type::EmptyLayout; } + EmptyLayoutNode(Color color = Color::Yellow, bool visible = true, const KDFont * font = KDFont::LargeFont, bool margins = true) : LayoutNode(), m_isVisible(visible), diff --git a/poincare/include/poincare/floor_layout.h b/poincare/include/poincare/floor_layout.h index 0e560a917..61e07f26f 100644 --- a/poincare/include/poincare/floor_layout.h +++ b/poincare/include/poincare/floor_layout.h @@ -11,6 +11,10 @@ namespace Poincare { class FloorLayoutNode final : public BracketPairLayoutNode { public: using BracketPairLayoutNode::BracketPairLayoutNode; + + // Layout + Type type() const override { return Type::FloorLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, Floor::s_functionHelper.name()); } diff --git a/poincare/include/poincare/fraction_layout.h b/poincare/include/poincare/fraction_layout.h index 03b3979b0..423198210 100644 --- a/poincare/include/poincare/fraction_layout.h +++ b/poincare/include/poincare/fraction_layout.h @@ -10,6 +10,9 @@ class FractionLayoutNode /*final*/ : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::FractionLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/grid_layout.h b/poincare/include/poincare/grid_layout.h index d6f8f8f70..f58fc3bc7 100644 --- a/poincare/include/poincare/grid_layout.h +++ b/poincare/include/poincare/grid_layout.h @@ -22,6 +22,9 @@ public: m_numberOfColumns(0) {} + // Layout + Type type() const override { return Type::GridLayout; } + int numberOfRows() const { return m_numberOfRows; } int numberOfColumns() const { return m_numberOfColumns; } virtual void setNumberOfRows(int numberOfRows) { m_numberOfRows = numberOfRows; } diff --git a/poincare/include/poincare/horizontal_layout.h b/poincare/include/poincare/horizontal_layout.h index 6f11a7b78..86d118117 100644 --- a/poincare/include/poincare/horizontal_layout.h +++ b/poincare/include/poincare/horizontal_layout.h @@ -18,6 +18,9 @@ public: m_numberOfChildren(0) {} + // Layout + Type type() const override { return Type::HorizontalLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/integral_layout.h b/poincare/include/poincare/integral_layout.h index 9b33b2c40..1a4836c61 100644 --- a/poincare/include/poincare/integral_layout.h +++ b/poincare/include/poincare/integral_layout.h @@ -14,6 +14,9 @@ public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::IntegralLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 7cffc3ded..10413cbdb 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -24,6 +24,9 @@ public: return static_cast(TreeHandle::node()); } + // Properties + LayoutNode::Type type() const { return node()->type(); } + // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite) { return node()->draw(ctx, p, expressionColor, backgroundColor); diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index bfa3aaa9c..b63191ee8 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -16,6 +16,30 @@ public: Up, Down }; + enum class Type : uint8_t { + AbsoluteValueLayout, + BinomialCoefficientLayout, + BracketPairLayout, + CeilingLayout, + CodePointLayout, + CondensedSumLayout, + ConjugateLayout, + EmptyLayout, + FloorLayout, + FractionLayout, + GridLayout, + HorizontalLayout, + IntegralLayout, + LeftParenthesisLayout, + LeftSquareBracketLayout, + MatrixLayout, + NthRootLayout, + ProductLayout, + RightParenthesisLayout, + RightSquareBracketLayout, + SumLayout, + VerticalOffsetLayout + }; // Constructor LayoutNode() : @@ -28,6 +52,9 @@ public: { } + /* Poor man's RTTI */ + virtual Type type() const = 0; + // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); KDPoint origin(); diff --git a/poincare/include/poincare/left_parenthesis_layout.h b/poincare/include/poincare/left_parenthesis_layout.h index 8aaa41bde..a528b628c 100644 --- a/poincare/include/poincare/left_parenthesis_layout.h +++ b/poincare/include/poincare/left_parenthesis_layout.h @@ -11,6 +11,9 @@ class LeftParenthesisLayoutNode final : public ParenthesisLayoutNode { public: using ParenthesisLayoutNode::ParenthesisLayoutNode; + // Layout + Type type() const override { return Type::LeftParenthesisLayout; } + static void RenderWithChildHeight(KDCoordinate childHeight, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); // Layout Node diff --git a/poincare/include/poincare/left_square_bracket_layout.h b/poincare/include/poincare/left_square_bracket_layout.h index c4f069c44..3b01b8277 100644 --- a/poincare/include/poincare/left_square_bracket_layout.h +++ b/poincare/include/poincare/left_square_bracket_layout.h @@ -10,6 +10,10 @@ namespace Poincare { class LeftSquareBracketLayoutNode final : public SquareBracketLayoutNode { public: using SquareBracketLayoutNode::SquareBracketLayoutNode; + + // Layout + Type type() const override { return Type::LeftSquareBracketLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, '['); } diff --git a/poincare/include/poincare/matrix_layout.h b/poincare/include/poincare/matrix_layout.h index 21cf04985..9f69ff35a 100644 --- a/poincare/include/poincare/matrix_layout.h +++ b/poincare/include/poincare/matrix_layout.h @@ -15,6 +15,9 @@ class MatrixLayoutNode final : public GridLayoutNode { public: using GridLayoutNode::GridLayoutNode; + // Layout + Type type() const override { return Type::MatrixLayout; } + // MatrixLayoutNode void addGreySquares(); void removeGreySquares(); diff --git a/poincare/include/poincare/nth_root_layout.h b/poincare/include/poincare/nth_root_layout.h index 1b2e27fc9..6f95eb970 100644 --- a/poincare/include/poincare/nth_root_layout.h +++ b/poincare/include/poincare/nth_root_layout.h @@ -19,6 +19,9 @@ public: m_hasIndex(hasIndex) {} + // Layout + Type type() const override { return Type::NthRootLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/product_layout.h b/poincare/include/poincare/product_layout.h index 3ea063232..fa78f0b1a 100644 --- a/poincare/include/poincare/product_layout.h +++ b/poincare/include/poincare/product_layout.h @@ -9,6 +9,10 @@ namespace Poincare { class ProductLayoutNode final : public SequenceLayoutNode { public: using SequenceLayoutNode::SequenceLayoutNode; + + // Layout + Type type() const override { return Type::ProductLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; size_t size() const override { return sizeof(ProductLayoutNode); } #if POINCARE_TREE_LOG diff --git a/poincare/include/poincare/right_parenthesis_layout.h b/poincare/include/poincare/right_parenthesis_layout.h index 4782c5fe3..c7cb54057 100644 --- a/poincare/include/poincare/right_parenthesis_layout.h +++ b/poincare/include/poincare/right_parenthesis_layout.h @@ -11,6 +11,9 @@ class RightParenthesisLayoutNode final : public ParenthesisLayoutNode { public: using ParenthesisLayoutNode::ParenthesisLayoutNode; + // Layout + Type type() const override { return Type::RightParenthesisLayout; } + static void RenderWithChildHeight(KDCoordinate childHeight, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); // LayoutNode diff --git a/poincare/include/poincare/right_square_bracket_layout.h b/poincare/include/poincare/right_square_bracket_layout.h index e37132b85..fd1f6b83b 100644 --- a/poincare/include/poincare/right_square_bracket_layout.h +++ b/poincare/include/poincare/right_square_bracket_layout.h @@ -10,6 +10,10 @@ namespace Poincare { class RightSquareBracketLayoutNode final : public SquareBracketLayoutNode { public: using SquareBracketLayoutNode::SquareBracketLayoutNode; + + // Layout + Type type() const override { return Type::RightSquareBracketLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, ']'); } diff --git a/poincare/include/poincare/sum_layout.h b/poincare/include/poincare/sum_layout.h index 57c6206db..ee040c522 100644 --- a/poincare/include/poincare/sum_layout.h +++ b/poincare/include/poincare/sum_layout.h @@ -9,6 +9,10 @@ namespace Poincare { class SumLayoutNode final : public SequenceLayoutNode { public: using SequenceLayoutNode::SequenceLayoutNode; + + // Layout + Type type() const override { return Type::SumLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; size_t size() const override { return sizeof(SumLayoutNode); } #if POINCARE_TREE_LOG diff --git a/poincare/include/poincare/vertical_offset_layout.h b/poincare/include/poincare/vertical_offset_layout.h index 7a5e35341..92890b143 100644 --- a/poincare/include/poincare/vertical_offset_layout.h +++ b/poincare/include/poincare/vertical_offset_layout.h @@ -18,6 +18,9 @@ public: m_position(position) {} + // Layout + Type type() const override { return Type::VerticalOffsetLayout; } + // VerticalOffsetLayoutNode Position position() const { return m_position; } From 3234622df6c322a1cb2ba8c01ec6a522a1b69b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 24 Apr 2019 15:54:39 +0200 Subject: [PATCH 44/57] [poincare] Layout: discard methods isVerticalOffset(), isHorizontal(), isRightParenthesisLayout(), isLeftBracket(), isRightBracket(), isCodePoint(), isLeftParenthesisLayout() and use type() instead --- apps/solver/list_controller.cpp | 2 +- escher/src/layout_field.cpp | 6 +-- poincare/include/poincare/code_point_layout.h | 1 - poincare/include/poincare/horizontal_layout.h | 1 - poincare/include/poincare/layout.h | 5 -- poincare/include/poincare/layout_node.h | 8 ---- .../poincare/left_parenthesis_layout.h | 1 - .../poincare/left_square_bracket_layout.h | 1 - poincare/include/poincare/matrix_layout.h | 1 - .../poincare/right_parenthesis_layout.h | 1 - .../poincare/right_square_bracket_layout.h | 1 - .../include/poincare/vertical_offset_layout.h | 1 - poincare/src/bracket_layout.cpp | 46 +++++++++---------- poincare/src/code_point_layout.cpp | 2 +- poincare/src/fraction_layout.cpp | 6 +-- poincare/src/horizontal_layout.cpp | 4 +- poincare/src/layout.cpp | 16 +++---- poincare/src/layout_cursor.cpp | 12 ++--- poincare/src/layout_node.cpp | 2 +- poincare/src/matrix_layout.cpp | 6 +-- poincare/src/vertical_offset_layout.cpp | 8 ++-- 21 files changed, 55 insertions(+), 76 deletions(-) diff --git a/apps/solver/list_controller.cpp b/apps/solver/list_controller.cpp index bd7f0eac4..bd8cb69a0 100644 --- a/apps/solver/list_controller.cpp +++ b/apps/solver/list_controller.cpp @@ -122,7 +122,7 @@ bool textRepresentsAnEquality(const char * text) { bool layoutRepresentsAnEquality(Poincare::Layout l) { Poincare::Layout match = l.recursivelyMatches( [](Poincare::Layout layout) { - return layout.isCodePoint() && static_cast(layout).codePoint() == '='; }); + return layout.type() == Poincare::LayoutNode::Type::CodePointLayout && static_cast(layout).codePoint() == '='; }); return !match.isUninitialized(); } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 5fba22bea..1b6e030e6 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -136,10 +136,10 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool /* Special case: if the text is "random()", the cursor should not be set * inside the parentheses. */ pointedLayout = resultLayout; - } else if (resultLayout.isHorizontal()) { + } else if (resultLayout.type() == LayoutNode::Type::HorizontalLayout) { pointedLayout = resultLayout.recursivelyMatches( [](Poincare::Layout layout) { - return layout.isLeftParenthesis() || layout.isEmpty();}); + return layout.type() == LayoutNode::Type::LeftParenthesisLayout || layout.isEmpty();}); } } /* Insert the layout. If pointedLayout is uninitialized, the cursor will @@ -294,7 +294,7 @@ void LayoutField::insertLayoutAtCursor(Layout layoutR, Layout pointedLayoutR, bo // Handle empty layouts m_contentView.cursor()->showEmptyLayoutIfNeeded(); - bool layoutWillBeMerged = layoutR.isHorizontal(); + bool layoutWillBeMerged = layoutR.type() == LayoutNode::Type::HorizontalLayout; Layout lastMergedLayoutChild = layoutWillBeMerged ? layoutR.childAtIndex(layoutR.numberOfChildren()-1) : Layout(); // Add the layout diff --git a/poincare/include/poincare/code_point_layout.h b/poincare/include/poincare/code_point_layout.h index e596c200c..e2415325b 100644 --- a/poincare/include/poincare/code_point_layout.h +++ b/poincare/include/poincare/code_point_layout.h @@ -31,7 +31,6 @@ public: void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - bool isCodePoint() const override { return true; } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; bool canBeOmittedMultiplicationLeftFactor() const override; bool canBeOmittedMultiplicationRightFactor() const override; diff --git a/poincare/include/poincare/horizontal_layout.h b/poincare/include/poincare/horizontal_layout.h index 86d118117..1333108a8 100644 --- a/poincare/include/poincare/horizontal_layout.h +++ b/poincare/include/poincare/horizontal_layout.h @@ -28,7 +28,6 @@ public: void deleteBeforeCursor(LayoutCursor * cursor) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - bool isHorizontal() const override { return true; } bool isEmpty() const override { return m_numberOfChildren == 1 && const_cast(this)->childAtIndex(0)->isEmpty(); } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override { return m_numberOfChildren != 0; } bool hasText() const override; diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 10413cbdb..c035096ec 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -48,11 +48,6 @@ public: Layout recursivelyMatches(LayoutTest test) const; bool mustHaveLeftSibling() const { return const_cast(this)->node()->mustHaveLeftSibling(); } bool isEmpty() const { return const_cast(this)->node()->isEmpty(); } - bool isHorizontal() const { return const_cast(this)->node()->isHorizontal(); } - bool isMatrix() const { return const_cast(this)->node()->isMatrix(); } - bool isVerticalOffset() const { return const_cast(this)->node()->isVerticalOffset(); } - bool isLeftParenthesis() const { return const_cast(this)->node()->isLeftParenthesis(); } - bool isCodePoint() const { return const_cast(this)->node()->isCodePoint(); } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return const_cast(this)->node()->isCollapsable(numberOfOpenParenthesis, goingLeft); } int leftCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->leftCollapsingAbsorbingChildIndex(); } int rightCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->rightCollapsingAbsorbingChildIndex(); } diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index b63191ee8..b667d5d30 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -120,17 +120,9 @@ public: * returns true, because |3|2 means |3|*2. A '+' CodePointLayout returns false, * because +'something' nevers means +*'something'. */ virtual bool mustHaveLeftSibling() const { return false; } - virtual bool isVerticalOffset() const { return false; } /* For now, mustHaveLeftSibling and isVerticalOffset behave the same, but code * is clearer with different names. */ - virtual bool isHorizontal() const { return false; } - virtual bool isLeftParenthesis() const { return false; } - virtual bool isRightParenthesis() const { return false; } - virtual bool isLeftBracket() const { return false; } - virtual bool isRightBracket() const { return false; } virtual bool isEmpty() const { return false; } - virtual bool isMatrix() const { return false; } - virtual bool isCodePoint() const { return false; } virtual bool hasUpperLeftIndex() const { return false; } virtual CodePoint XNTCodePoint() const { LayoutNode * p = parent(); diff --git a/poincare/include/poincare/left_parenthesis_layout.h b/poincare/include/poincare/left_parenthesis_layout.h index a528b628c..f552a16f7 100644 --- a/poincare/include/poincare/left_parenthesis_layout.h +++ b/poincare/include/poincare/left_parenthesis_layout.h @@ -18,7 +18,6 @@ public: // Layout Node bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - bool isLeftParenthesis() const override { return true; } // Serializable Node int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { diff --git a/poincare/include/poincare/left_square_bracket_layout.h b/poincare/include/poincare/left_square_bracket_layout.h index 3b01b8277..6789a741d 100644 --- a/poincare/include/poincare/left_square_bracket_layout.h +++ b/poincare/include/poincare/left_square_bracket_layout.h @@ -17,7 +17,6 @@ public: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, '['); } - bool isLeftBracket() const override { return true; } // TreeNode size_t size() const override { return sizeof(LeftSquareBracketLayoutNode); } diff --git a/poincare/include/poincare/matrix_layout.h b/poincare/include/poincare/matrix_layout.h index 9f69ff35a..3d68602f7 100644 --- a/poincare/include/poincare/matrix_layout.h +++ b/poincare/include/poincare/matrix_layout.h @@ -26,7 +26,6 @@ public: void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void willAddSiblingToEmptyChildAtIndex(int childIndex) override; - bool isMatrix() const override { return true; } // SerializableNode int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/right_parenthesis_layout.h b/poincare/include/poincare/right_parenthesis_layout.h index c7cb54057..42c81295c 100644 --- a/poincare/include/poincare/right_parenthesis_layout.h +++ b/poincare/include/poincare/right_parenthesis_layout.h @@ -18,7 +18,6 @@ public: // LayoutNode bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - bool isRightParenthesis() const override { return true; } // SerializableNode int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { diff --git a/poincare/include/poincare/right_square_bracket_layout.h b/poincare/include/poincare/right_square_bracket_layout.h index fd1f6b83b..1689e7782 100644 --- a/poincare/include/poincare/right_square_bracket_layout.h +++ b/poincare/include/poincare/right_square_bracket_layout.h @@ -17,7 +17,6 @@ public: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, ']'); } - bool isRightBracket() const override { return true; } // TreeNode size_t size() const override { return sizeof(RightSquareBracketLayoutNode); } diff --git a/poincare/include/poincare/vertical_offset_layout.h b/poincare/include/poincare/vertical_offset_layout.h index 92890b143..77977a860 100644 --- a/poincare/include/poincare/vertical_offset_layout.h +++ b/poincare/include/poincare/vertical_offset_layout.h @@ -32,7 +32,6 @@ public: void deleteBeforeCursor(LayoutCursor * cursor) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; bool mustHaveLeftSibling() const override { return true; } - bool isVerticalOffset() const override { return true; } bool canBeOmittedMultiplicationRightFactor() const override { return false; } // TreeNode diff --git a/poincare/src/bracket_layout.cpp b/poincare/src/bracket_layout.cpp index a13f6d040..2b22c8aa5 100644 --- a/poincare/src/bracket_layout.cpp +++ b/poincare/src/bracket_layout.cpp @@ -47,9 +47,9 @@ KDCoordinate BracketLayoutNode::computeBaseline() { assert(parentLayout != nullptr); int idxInParent = parentLayout->indexOfChild(this); int numberOfSiblings = parentLayout->numberOfChildren(); - if (((isLeftParenthesis() || isLeftBracket()) && idxInParent == numberOfSiblings - 1) - || ((isRightParenthesis() || isRightBracket()) && idxInParent == 0) - || ((isLeftParenthesis() || isLeftBracket()) && idxInParent < numberOfSiblings - 1 && parentLayout->childAtIndex(idxInParent + 1)->isVerticalOffset())) + if (((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent == numberOfSiblings - 1) + || ((type() == Type::RightParenthesisLayout || type() == Type::RightSquareBracketLayout) && idxInParent == 0) + || ((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent < numberOfSiblings - 1 && parentLayout->childAtIndex(idxInParent + 1)->type() == Type::VerticalOffsetLayout)) { /* The bracket does not have siblings on its open direction, or it is a left * bracket that is base of a superscript layout. In the latter case, it @@ -60,13 +60,13 @@ KDCoordinate BracketLayoutNode::computeBaseline() { int currentNumberOfOpenBrackets = 1; KDCoordinate result = 0; - int increment = (isLeftParenthesis() || isLeftBracket()) ? 1 : -1; + int increment = (type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) ? 1 : -1; for (int i = idxInParent + increment; i >= 0 && i < numberOfSiblings; i+=increment) { LayoutNode * sibling = parentLayout->childAtIndex(i); - if ((isLeftParenthesis() && sibling->isRightParenthesis()) - || (isLeftBracket() && sibling->isRightBracket()) - || (isRightParenthesis() && sibling->isLeftParenthesis()) - || (isRightBracket() && sibling->isLeftBracket())) + if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout)) { if (i == idxInParent + increment) { /* If the bracket is immediately closed, we set the baseline to half the @@ -77,10 +77,10 @@ KDCoordinate BracketLayoutNode::computeBaseline() { if (currentNumberOfOpenBrackets == 0) { break; } - } else if ((isLeftParenthesis() && sibling->isLeftParenthesis()) - || (isLeftBracket() && sibling->isLeftBracket()) - || (isRightParenthesis() && sibling->isRightParenthesis()) - || (isRightBracket() && sibling->isRightBracket())) + } else if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout)) { currentNumberOfOpenBrackets++; } @@ -103,9 +103,9 @@ KDCoordinate BracketLayoutNode::computeChildHeight() { KDCoordinate result = Metric::MinimalBracketAndParenthesisHeight; int idxInParent = parentLayout->indexOfChild(this); int numberOfSiblings = parentLayout->numberOfChildren(); - if ((isLeftParenthesis() || isLeftBracket()) + if ((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent < numberOfSiblings - 1 - && parentLayout->childAtIndex(idxInParent + 1)->isVerticalOffset()) + && parentLayout->childAtIndex(idxInParent + 1)->type() == Type::VerticalOffsetLayout) { /* If a left bracket is the base of a superscript layout, it should have a * a default height, else it creates an infinite loop because the bracket @@ -117,22 +117,22 @@ KDCoordinate BracketLayoutNode::computeChildHeight() { KDCoordinate maxAboveBaseline = 0; int currentNumberOfOpenBrackets = 1; - int increment = (isLeftParenthesis() || isLeftBracket()) ? 1 : -1; + int increment = (type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) ? 1 : -1; for (int i = idxInParent + increment; i >= 0 && i < numberOfSiblings; i+= increment) { LayoutNode * sibling = parentLayout->childAtIndex(i); - if ((isLeftParenthesis() && sibling->isRightParenthesis()) - || (isLeftBracket() && sibling->isRightBracket()) - || (isRightParenthesis() && sibling->isLeftParenthesis()) - || (isRightBracket() && sibling->isLeftBracket())) + if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout)) { currentNumberOfOpenBrackets--; if (currentNumberOfOpenBrackets == 0) { break; } - } else if ((isLeftParenthesis() && sibling->isLeftParenthesis()) - || (isLeftBracket() && sibling->isLeftBracket()) - || (isRightParenthesis() && sibling->isRightParenthesis()) - || (isRightBracket() && sibling->isRightBracket())) + } else if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout)) { currentNumberOfOpenBrackets++; } diff --git a/poincare/src/code_point_layout.cpp b/poincare/src/code_point_layout.cpp index 4914aecb4..254000cb1 100644 --- a/poincare/src/code_point_layout.cpp +++ b/poincare/src/code_point_layout.cpp @@ -49,7 +49,7 @@ bool CodePointLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goin int indexOfThis = parent.indexOfChild(thisRef); if (indexOfThis > 0) { Layout leftBrother = parent.childAtIndex(indexOfThis-1); - if (leftBrother.isCodePoint() + if (leftBrother.type() == Type::CodePointLayout && static_cast(leftBrother).codePoint() == UCodePointLatinLetterSmallCapitalE) { return true; diff --git a/poincare/src/fraction_layout.cpp b/poincare/src/fraction_layout.cpp index 8b6c85f9c..8af85a37d 100644 --- a/poincare/src/fraction_layout.cpp +++ b/poincare/src/fraction_layout.cpp @@ -133,13 +133,13 @@ int FractionLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pr } // Add a multiplication if omitted. - if (idxInParent > 0 && p->isHorizontal() && p->childAtIndex(idxInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { + if (idxInParent > 0 && p->type() == Type::HorizontalLayout && p->childAtIndex(idxInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointMiddleDot); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} } bool addParenthesis = false; - if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->isVerticalOffset()) { + if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->type() == Type::HorizontalLayout && p->childAtIndex(idxInParent + 1)->type() == Type::VerticalOffsetLayout) { addParenthesis = true; // Add parenthesis numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, '('); @@ -157,7 +157,7 @@ int FractionLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pr } // Add a multiplication if omitted. - if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->canBeOmittedMultiplicationRightFactor()) { + if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->type() == Type::HorizontalLayout && p->childAtIndex(idxInParent + 1)->canBeOmittedMultiplicationRightFactor()) { numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointMiddleDot); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} } diff --git a/poincare/src/horizontal_layout.cpp b/poincare/src/horizontal_layout.cpp index 10dc63766..475ab8737 100644 --- a/poincare/src/horizontal_layout.cpp +++ b/poincare/src/horizontal_layout.cpp @@ -309,7 +309,7 @@ bool HorizontalLayoutNode::willReplaceChild(LayoutNode * oldChild, LayoutNode * /* If the new child is also an horizontal layout, steal the children of the * new layout then destroy it. */ bool oldWasAncestorOfNewLayout = newChild->hasAncestor(oldChild, false); - if (newChild->isHorizontal()) { + if (newChild->type() == LayoutNode::Type::HorizontalLayout) { int indexForInsertion = indexOfChild(oldChild); if (cursor != nullptr) { /* If the old layout is not an ancestor of the new layout, or if the @@ -354,7 +354,7 @@ bool HorizontalLayoutNode::willReplaceChild(LayoutNode * oldChild, LayoutNode * // HorizontalLayout void HorizontalLayout::addOrMergeChildAtIndex(Layout l, int index, bool removeEmptyChildren, LayoutCursor * cursor) { - if (l.isHorizontal()) { + if (l.type() == LayoutNode::Type::HorizontalLayout) { mergeChildrenAtIndex(HorizontalLayout(static_cast(l.node())), index, removeEmptyChildren, cursor); } else { addChildAtIndex(l, index, numberOfChildren(), cursor, removeEmptyChildren); diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 1aef45379..333e0441a 100644 --- a/poincare/src/layout.cpp +++ b/poincare/src/layout.cpp @@ -94,7 +94,7 @@ void Layout::replaceWith(Layout newChild, LayoutCursor * cursor) { void Layout::replaceWithJuxtapositionOf(Layout leftChild, Layout rightChild, LayoutCursor * cursor, bool putCursorInTheMiddle) { Layout p = parent(); assert(!p.isUninitialized()); - if (!p.isHorizontal()) { + if (p.type() != LayoutNode::Type::HorizontalLayout) { /* One of the children to juxtapose might be "this", so we cannot just call * replaceWith. */ HorizontalLayout horizontalLayoutR = HorizontalLayout::Builder(); @@ -166,21 +166,21 @@ void Layout::addSibling(LayoutCursor * cursor, Layout sibling, bool moveCursor) Layout rootLayout = root(); Layout p = parent(); assert(!p.isUninitialized()); - if (p.isHorizontal()) { + if (p.type() == LayoutNode::Type::HorizontalLayout) { int indexInParent = p.indexOfChild(*this); int siblingIndex = cursor->position() == LayoutCursor::Position::Left ? indexInParent : indexInParent + 1; /* Special case: If the neighbour sibling is a VerticalOffsetLayout, let it * handle the insertion of the new sibling. Do not enter the special case if * "this" is a VerticalOffsetLayout, to avoid an infinite loop. */ - if (!isVerticalOffset()) { + if (type() != LayoutNode::Type::VerticalOffsetLayout) { Layout neighbour; if (cursor->position() == LayoutCursor::Position::Left && indexInParent > 0) { neighbour = p.childAtIndex(indexInParent - 1); } else if (cursor->position() == LayoutCursor::Position::Right && indexInParent < p.numberOfChildren() - 1) { neighbour = p.childAtIndex(indexInParent + 1); } - if (!neighbour.isUninitialized() && neighbour.isVerticalOffset()) { + if (!neighbour.isUninitialized() && neighbour.type() == LayoutNode::Type::VerticalOffsetLayout) { if (moveCursor) { cursor->setLayout(neighbour); cursor->setPosition(cursor->position() == LayoutCursor::Position::Left ? LayoutCursor::Position::Right : LayoutCursor::Position::Left); @@ -234,7 +234,7 @@ void Layout::removeChildAtIndex(int index, LayoutCursor * cursor, bool force) { void Layout::collapseOnDirection(HorizontalDirection direction, int absorbingChildIndex) { Layout p = parent(); - if (p.isUninitialized() || !p.isHorizontal()) { + if (p.isUninitialized() || p.type() != LayoutNode::Type::HorizontalLayout) { return; } int idxInParent = p.indexOfChild(*this); @@ -242,7 +242,7 @@ void Layout::collapseOnDirection(HorizontalDirection direction, int absorbingChi int numberOfOpenParenthesis = 0; bool canCollapse = true; Layout absorbingChild = childAtIndex(absorbingChildIndex); - if (absorbingChild.isUninitialized() || !absorbingChild.isHorizontal()) { + if (absorbingChild.isUninitialized() || absorbingChild.type() != LayoutNode::Type::HorizontalLayout) { return; } HorizontalLayout horizontalAbsorbingChild = HorizontalLayout(static_cast(absorbingChild.node())); @@ -285,7 +285,7 @@ void Layout::collapseSiblings(LayoutCursor * cursor) { Layout rootLayout = root(); if (node()->shouldCollapseSiblingsOnRight()) { Layout absorbingChild = childAtIndex(rightCollapsingAbsorbingChildIndex()); - if (!absorbingChild.isHorizontal()) { + if (absorbingChild.type() != LayoutNode::Type::HorizontalLayout) { Layout horRef = HorizontalLayout::Builder(); replaceChild(absorbingChild, horRef, cursor, true); horRef.addChildAtIndexInPlace(absorbingChild, 0, 0); @@ -294,7 +294,7 @@ void Layout::collapseSiblings(LayoutCursor * cursor) { } if (node()->shouldCollapseSiblingsOnLeft()) { Layout absorbingChild = childAtIndex(leftCollapsingAbsorbingChildIndex()); - if (!absorbingChild.isHorizontal()) { + if (absorbingChild.type() != LayoutNode::Type::HorizontalLayout) { Layout horRef = HorizontalLayout::Builder(); replaceChild(absorbingChild, horRef, cursor, true); horRef.addChildAtIndexInPlace(absorbingChild, 0, 0); diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 234d35b14..686405c13 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -190,7 +190,7 @@ void LayoutCursor::insertText(const char * text) { } void LayoutCursor::addLayoutAndMoveCursor(Layout l) { - bool layoutWillBeMerged = l.isHorizontal(); + bool layoutWillBeMerged = l.type() == LayoutNode::Type::HorizontalLayout; m_layout.addSibling(this, l, true); if (!layoutWillBeMerged) { l.collapseSiblings(this); @@ -199,7 +199,7 @@ void LayoutCursor::addLayoutAndMoveCursor(Layout l) { void LayoutCursor::clearLayout() { Layout rootLayoutR = m_layout.root(); - assert(rootLayoutR.isHorizontal()); + assert(rootLayoutR.type() == LayoutNode::Type::HorizontalLayout); rootLayoutR.removeChildrenInPlace(rootLayoutR.numberOfChildren()); m_layout = rootLayoutR; } @@ -239,10 +239,10 @@ bool LayoutCursor::baseForNewPowerLayout() { * be the base of a new power layout: the base layout should be anything but * an horizontal layout with no child. */ if (m_position == Position::Right) { - return !(m_layout.isHorizontal() && m_layout.numberOfChildren() == 0); + return !(m_layout.type() == LayoutNode::Type::HorizontalLayout && m_layout.numberOfChildren() == 0); } else { assert(m_position == Position::Left); - if (m_layout.isHorizontal()) { + if (m_layout.type() == LayoutNode::Type::HorizontalLayout) { return false; } if (m_layout.isEmpty()) { @@ -254,7 +254,7 @@ bool LayoutCursor::baseForNewPowerLayout() { } LayoutCursor equivalentLayoutCursor = m_layout.equivalentCursor(this); if (equivalentLayoutCursor.layoutReference().isUninitialized() - || (equivalentLayoutCursor.layoutReference().isHorizontal() + || (equivalentLayoutCursor.layoutReference().type() == LayoutNode::Type::HorizontalLayout && equivalentLayoutCursor.position() == Position::Left)) { return false; @@ -285,7 +285,7 @@ bool LayoutCursor::privateShowHideEmptyLayoutIfNeeded(bool show) { /* Change the visibility of the neighbouring empty layout: it might be either * an EmptyLayout or an HorizontalLayout with one child only, and this child * is an EmptyLayout. */ - if (adjacentEmptyLayout.isHorizontal()) { + if (adjacentEmptyLayout.type() == LayoutNode::Type::HorizontalLayout) { static_cast(adjacentEmptyLayout.childAtIndex(0).node())->setVisible(show); } else { static_cast(adjacentEmptyLayout.node())->setVisible(show); diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index 53f80eba5..60818e3ac 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -215,7 +215,7 @@ bool LayoutNode::changeGreySquaresOfAllMatrixAncestors(bool add) { bool changedSquares = false; Layout currentAncestor = Layout(parent()); while (!currentAncestor.isUninitialized()) { - if (currentAncestor.isMatrix()) { + if (currentAncestor.type() == Type::MatrixLayout) { if (add) { MatrixLayout(static_cast(currentAncestor.node())).addGreySquares(); } else { diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index 659b3682f..d390eea1e 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -180,7 +180,7 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { } if (childIndex % m_numberOfColumns == correspondingColumn) { if (lastLayoutOfRow->isEmpty()) { - if (!lastLayoutOfRow->isHorizontal()) { + if (lastLayoutOfRow->type() != Type::HorizontalLayout) { static_cast(lastLayoutOfRow)->setColor(EmptyLayoutNode::Color::Yellow); } else { assert(lastLayoutOfRow->numberOfChildren() == 1); @@ -203,7 +203,7 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { break; } if (lastLayoutOfColumn->isEmpty()) { - if (!lastLayoutOfColumn->isHorizontal()) { + if (lastLayoutOfColumn->type() != Type::HorizontalLayout) { static_cast(lastLayoutOfColumn)->setColor(EmptyLayoutNode::Color::Yellow); } else { assert(lastLayoutOfColumn->numberOfChildren() == 1); @@ -253,7 +253,7 @@ bool MatrixLayoutNode::hasGreySquares() const { } LayoutNode * lastChild = const_cast(this)->childAtIndex(m_numberOfRows * m_numberOfColumns - 1); if (lastChild->isEmpty() - && !lastChild->isHorizontal() + && lastChild->type() != Type::HorizontalLayout && (static_cast(lastChild))->color() == EmptyLayoutNode::Color::Grey) { assert(isRowEmpty(m_numberOfRows - 1)); diff --git a/poincare/src/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 03077a5b3..8db4a472e 100644 --- a/poincare/src/vertical_offset_layout.cpp +++ b/poincare/src/vertical_offset_layout.cpp @@ -192,7 +192,7 @@ KDSize VerticalOffsetLayoutNode::computeSize() { if (m_position == Position::Superscript) { LayoutNode * parentNode = parent(); assert(parentNode != nullptr); - assert(parentNode->isHorizontal()); + assert(parentNode->type() == Type::HorizontalLayout); int idxInParent = parentNode->indexOfChild(this); if (idxInParent < parentNode->numberOfChildren() - 1 && parentNode->childAtIndex(idxInParent + 1)->hasUpperLeftIndex()) { width += k_separationMargin; @@ -220,13 +220,13 @@ KDPoint VerticalOffsetLayoutNode::positionOfChild(LayoutNode * child) { } bool VerticalOffsetLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) { - if (sibling->isVerticalOffset()) { + if (sibling->type() == Type::VerticalOffsetLayout) { VerticalOffsetLayoutNode * verticalOffsetSibling = static_cast(sibling); if (verticalOffsetSibling->position() == Position::Superscript) { Layout rootLayout = root(); Layout thisRef = Layout(this); Layout parentRef = Layout(parent()); - assert(parentRef.isHorizontal()); + assert(parentRef.type() == Type::HorizontalLayout); // Add the Left parenthesis int idxInParent = parentRef.indexOfChild(thisRef); int leftParenthesisIndex = idxInParent; @@ -259,7 +259,7 @@ bool VerticalOffsetLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode LayoutNode * VerticalOffsetLayoutNode::baseLayout() { LayoutNode * parentNode = parent(); assert(parentNode != nullptr); - assert(parentNode->isHorizontal()); + assert(parentNode->type() == Type::HorizontalLayout); int idxInParent = parentNode->indexOfChild(this); assert(idxInParent > 0); return parentNode->childAtIndex(idxInParent - 1); From 4330e4de4270a40a5e8f7475a010897528e05d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 25 Apr 2019 11:51:42 +0200 Subject: [PATCH 45/57] [poincare] Layout: implement isIdenticalTo --- poincare/include/poincare/code_point_layout.h | 1 + poincare/include/poincare/empty_layout.h | 1 + poincare/include/poincare/layout.h | 1 + poincare/include/poincare/layout_node.h | 3 ++ poincare/include/poincare/nth_root_layout.h | 1 + .../include/poincare/vertical_offset_layout.h | 1 + poincare/src/code_point_layout.cpp | 8 +++++ poincare/src/empty_layout.cpp | 8 +++++ poincare/src/layout_node.cpp | 13 ++++++++ poincare/src/nth_root_layout.cpp | 8 +++++ poincare/src/vertical_offset_layout.cpp | 8 +++++ poincare/test/layouts.cpp | 33 +++++++++++++++++++ 12 files changed, 86 insertions(+) diff --git a/poincare/include/poincare/code_point_layout.h b/poincare/include/poincare/code_point_layout.h index e2415325b..fd765bc93 100644 --- a/poincare/include/poincare/code_point_layout.h +++ b/poincare/include/poincare/code_point_layout.h @@ -22,6 +22,7 @@ public: // Layout Type type() const override { return Type::CodePointLayout; } + bool isIdenticalTo(Layout l) override; // CodePointLayout CodePoint codePoint() const { return m_codePoint; } diff --git a/poincare/include/poincare/empty_layout.h b/poincare/include/poincare/empty_layout.h index 2b8a7ccea..b3fe6a6d8 100644 --- a/poincare/include/poincare/empty_layout.h +++ b/poincare/include/poincare/empty_layout.h @@ -15,6 +15,7 @@ public: // Layout Type type() const override { return Type::EmptyLayout; } + bool isIdenticalTo(Layout l) override; EmptyLayoutNode(Color color = Color::Yellow, bool visible = true, const KDFont * font = KDFont::LargeFont, bool margins = true) : LayoutNode(), diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index c035096ec..8aa19ad4e 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -26,6 +26,7 @@ public: // Properties LayoutNode::Type type() const { return node()->type(); } + bool isIdenticalTo(Layout l) { return isUninitialized() ? l.isUninitialized() : node()->isIdenticalTo(l); } // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite) { diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index b667d5d30..297fa85d8 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -55,6 +55,9 @@ public: /* Poor man's RTTI */ virtual Type type() const = 0; + // Comparison + virtual bool isIdenticalTo(Layout l); + // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); KDPoint origin(); diff --git a/poincare/include/poincare/nth_root_layout.h b/poincare/include/poincare/nth_root_layout.h index 6f95eb970..3b19fd2d0 100644 --- a/poincare/include/poincare/nth_root_layout.h +++ b/poincare/include/poincare/nth_root_layout.h @@ -21,6 +21,7 @@ public: // Layout Type type() const override { return Type::NthRootLayout; } + bool isIdenticalTo(Layout l) override; // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/vertical_offset_layout.h b/poincare/include/poincare/vertical_offset_layout.h index 77977a860..b3ef24c5a 100644 --- a/poincare/include/poincare/vertical_offset_layout.h +++ b/poincare/include/poincare/vertical_offset_layout.h @@ -20,6 +20,7 @@ public: // Layout Type type() const override { return Type::VerticalOffsetLayout; } + bool isIdenticalTo(Layout l) override; // VerticalOffsetLayoutNode Position position() const { return m_position; } diff --git a/poincare/src/code_point_layout.cpp b/poincare/src/code_point_layout.cpp index 254000cb1..38dd16b0e 100644 --- a/poincare/src/code_point_layout.cpp +++ b/poincare/src/code_point_layout.cpp @@ -4,6 +4,14 @@ namespace Poincare { +bool CodePointLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::CodePointLayout) { + return false; + } + CodePointLayout & cpl = static_cast(l); + return codePoint() == cpl.codePoint() && font() == cpl.font(); +} + // LayoutNode void CodePointLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { if (cursor->position() == LayoutCursor::Position::Right) { diff --git a/poincare/src/empty_layout.cpp b/poincare/src/empty_layout.cpp index 4bed5b777..b9a02747f 100644 --- a/poincare/src/empty_layout.cpp +++ b/poincare/src/empty_layout.cpp @@ -5,6 +5,14 @@ namespace Poincare { +bool EmptyLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::EmptyLayout) { + return false; + } + EmptyLayoutNode * n = static_cast(l.node()); + return color() == n->color(); +} + void EmptyLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { cursor->setPosition(LayoutCursor::Position::Left); LayoutNode * p = parent(); diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index 60818e3ac..1132300fe 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -7,6 +7,19 @@ namespace Poincare { +bool LayoutNode::isIdenticalTo(Layout l) { + if (l.isUninitialized() || type() != l.type() || numberOfChildren() != l.numberOfChildren()) { + return false; + } + for (int i = 0; i < numberOfChildren(); i++) { + Layout child = childAtIndex(i); + if (!childAtIndex(i)->isIdenticalTo(l.childAtIndex(i))) { + return false; + } + } + return true; +} + // Rendering void LayoutNode::draw(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { diff --git a/poincare/src/nth_root_layout.cpp b/poincare/src/nth_root_layout.cpp index c689064d0..62beeb8af 100644 --- a/poincare/src/nth_root_layout.cpp +++ b/poincare/src/nth_root_layout.cpp @@ -20,6 +20,14 @@ const uint8_t radixPixel[NthRootLayoutNode::k_leftRadixHeight][NthRootLayoutNode {0xFF, 0xFF, 0xFF, 0xFF, 0x00}, }; +bool NthRootLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::NthRootLayout) { + return false; + } + NthRootLayout & nrl = static_cast(l); + return hasUpperLeftIndex() == nrl.node()->hasUpperLeftIndex() && LayoutNode::isIdenticalTo(l); +} + void NthRootLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { if (cursor->layoutNode() == radicandLayout() && cursor->position() == LayoutCursor::Position::Left) diff --git a/poincare/src/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 8db4a472e..4aaad5bc1 100644 --- a/poincare/src/vertical_offset_layout.cpp +++ b/poincare/src/vertical_offset_layout.cpp @@ -8,6 +8,14 @@ namespace Poincare { +bool VerticalOffsetLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::VerticalOffsetLayout) { + return false; + } + VerticalOffsetLayoutNode * n = static_cast(l.node()); + return position() == n->position() && LayoutNode::isIdenticalTo(l); +} + void VerticalOffsetLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { if (cursor->layoutNode() == indiceLayout() && cursor->position() == LayoutCursor::Position::Left) diff --git a/poincare/test/layouts.cpp b/poincare/test/layouts.cpp index 36c613be3..37e55e170 100644 --- a/poincare/test/layouts.cpp +++ b/poincare/test/layouts.cpp @@ -273,3 +273,36 @@ QUIZ_CASE(poincare_parse_layouts) { assert_parsed_expression_is("2ℯ^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(UCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3))))); assert_parsed_layout_is(l, e); } + +QUIZ_CASE(poincare_layouts_comparison) { + Layout e0 = CodePointLayout::Builder('a'); + Layout e1 = CodePointLayout::Builder('a'); + Layout e2 = CodePointLayout::Builder('b'); + quiz_assert(e0.isIdenticalTo(e1)); + quiz_assert(!e0.isIdenticalTo(e2)); + + Layout e3 = EmptyLayout::Builder(); + Layout e4 = EmptyLayout::Builder(); + quiz_assert(e3.isIdenticalTo(e4)); + quiz_assert(!e3.isIdenticalTo(e0)); + + Layout e5 = NthRootLayout::Builder(e0); + Layout e6 = NthRootLayout::Builder(e1); + Layout e7 = NthRootLayout::Builder(e2); + quiz_assert(e5.isIdenticalTo(e6)); + quiz_assert(!e5.isIdenticalTo(e7)); + quiz_assert(!e5.isIdenticalTo(e0)); + + Layout e8 = VerticalOffsetLayout::Builder(e5, VerticalOffsetLayoutNode::Position::Superscript); + Layout e9 = VerticalOffsetLayout::Builder(e6, VerticalOffsetLayoutNode::Position::Superscript); + Layout e10 = VerticalOffsetLayout::Builder(NthRootLayout::Builder(CodePointLayout::Builder('a')), VerticalOffsetLayoutNode::Position::Subscript); + quiz_assert(e8.isIdenticalTo(e9)); + quiz_assert(!e8.isIdenticalTo(e10)); + quiz_assert(!e8.isIdenticalTo(e0)); + + Layout e11 = SumLayout::Builder(e0, e3, e6, e2); + Layout e12 = SumLayout::Builder(CodePointLayout::Builder('a'), EmptyLayout::Builder(), NthRootLayout::Builder(CodePointLayout::Builder('a')), CodePointLayout::Builder('b')); + Layout e13 = ProductLayout::Builder(CodePointLayout::Builder('a'), EmptyLayout::Builder(), NthRootLayout::Builder(CodePointLayout::Builder('a')), CodePointLayout::Builder('b')); + quiz_assert(e11.isIdenticalTo(e12)); + quiz_assert(!e11.isIdenticalTo(e13)); +} From 76b4d36826f0de8bd3cda8d8181e269c887d9699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 25 Apr 2019 13:44:39 +0200 Subject: [PATCH 46/57] [escher] Clean empty lines --- escher/include/escher/scrollable_view.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/escher/include/escher/scrollable_view.h b/escher/include/escher/scrollable_view.h index 6d82205e3..4f1996461 100644 --- a/escher/include/escher/scrollable_view.h +++ b/escher/include/escher/scrollable_view.h @@ -15,5 +15,3 @@ protected: }; #endif - - From 2b921c99653cf88d748d6a68440a8732a9cb4e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 25 Apr 2019 13:46:57 +0200 Subject: [PATCH 47/57] [escher] ExpressionView: optimize setLayouts to avoid marking the whole view as dirty when the layout did not really changed --- .../scrollable_exact_approximate_expressions_view.cpp | 8 +++++--- escher/include/escher/expression_view.h | 2 +- escher/src/expression_view.cpp | 6 +++++- escher/src/layout_field.cpp | 5 +++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index f0e0831f3..98bbc4d91 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -114,9 +114,11 @@ ScrollableExactApproximateExpressionsView::ScrollableExactApproximateExpressions } void ScrollableExactApproximateExpressionsView::setLayouts(Poincare::Layout rightLayout, Poincare::Layout leftLayout) { - m_contentCell.rightExpressionView()->setLayout(rightLayout); - m_contentCell.leftExpressionView()->setLayout(leftLayout); - m_contentCell.layoutSubviews(); + bool updateRightLayout = m_contentCell.rightExpressionView()->setLayout(rightLayout); + bool updateLeftLayout = m_contentCell.leftExpressionView()->setLayout(leftLayout); + if (updateRightLayout || updateLeftLayout) { + m_contentCell.layoutSubviews(); + } } void ScrollableExactApproximateExpressionsView::setEqualMessage(I18n::Message equalSignMessage) { diff --git a/escher/include/escher/expression_view.h b/escher/include/escher/expression_view.h index 0a1bdb7fe..b11ea2db6 100644 --- a/escher/include/escher/expression_view.h +++ b/escher/include/escher/expression_view.h @@ -16,7 +16,7 @@ public: ExpressionView(float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); Poincare::Layout layout() const { return m_layout; } - void setLayout(Poincare::Layout layout); + bool setLayout(Poincare::Layout layout); void drawRect(KDContext * ctx, KDRect rect) const override; void setBackgroundColor(KDColor backgroundColor); void setTextColor(KDColor textColor); diff --git a/escher/src/expression_view.cpp b/escher/src/expression_view.cpp index 6360470f8..64cf5c47f 100644 --- a/escher/src/expression_view.cpp +++ b/escher/src/expression_view.cpp @@ -14,9 +14,13 @@ ExpressionView::ExpressionView(float horizontalAlignment, float verticalAlignmen { } -void ExpressionView::setLayout(Layout layoutR) { +bool ExpressionView::setLayout(Layout layoutR) { + if (m_layout.isIdenticalTo(layoutR)) { + return false; + } m_layout = layoutR; markRectAsDirty(bounds()); + return true; } void ExpressionView::setBackgroundColor(KDColor backgroundColor) { diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 1b6e030e6..a25088ef0 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -28,8 +28,9 @@ void LayoutField::ContentView::setEditing(bool isEditing) { void LayoutField::ContentView::clearLayout() { HorizontalLayout h = HorizontalLayout::Builder(); - m_expressionView.setLayout(h); - m_cursor.setLayout(h); + if (m_expressionView.setLayout(h)) { + m_cursor.setLayout(h); + } } KDSize LayoutField::ContentView::minimalSizeForOptimalDisplay() const { From 25722d578d970b83e0e7dd0fb3e21608b5ab0832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 25 Apr 2019 13:52:50 +0200 Subject: [PATCH 48/57] [calculation] HistoryViewCell: memoize calculation and cell selection to speed up navigation in HistoryController --- apps/calculation/history_view_cell.cpp | 10 ++++++++-- apps/calculation/history_view_cell.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 3310771c5..1f8d915a9 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -28,6 +28,7 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, HistoryViewCell::HistoryViewCell(Responder * parentResponder) : Responder(parentResponder), m_calculation(), + m_calculationSelected(false), m_inputView(this), m_scrollableOutputView(this) { @@ -120,15 +121,20 @@ void HistoryViewCell::layoutSubviews() { } void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) { + if (m_calculationSelected == isSelected && *calculation == m_calculation) { + return; + } + // Memoization m_calculation = *calculation; - m_inputView.setLayout(calculation->createInputLayout()); + m_calculationSelected = isSelected; App * calculationApp = (App *)app(); + Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); + m_inputView.setLayout(calculation->createInputLayout()); /* Both output expressions have to be updated at the same time. Otherwise, * when updating one layout, if the second one still points to a deleted * layout, calling to layoutSubviews() would fail. */ Poincare::Layout leftOutputLayout = Poincare::Layout(); Poincare::Layout rightOutputLayout; - Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); if (display == Calculation::DisplayOutput::ExactOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !isSelected && calculation->toggleDisplayExact())) { rightOutputLayout = calculation->createExactOutputLayout(); } else { diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index d106ce922..e9d9639ff 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -50,6 +50,7 @@ public: private: constexpr static KDCoordinate k_resultWidth = 80; Calculation m_calculation; + bool m_calculationSelected; ScrollableExpressionView m_inputView; Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; HistoryViewCellDataSource * m_dataSource; From a02a1fc5a49b9fae45ef402b04ecb4e8844577e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 25 Apr 2019 15:19:54 +0200 Subject: [PATCH 49/57] [calculation] HistoryViewCell: fix scroll reloading and right or left outputs selection (the order of events here matters) --- apps/calculation/history_controller.cpp | 7 ++--- apps/calculation/history_view_cell.cpp | 26 ++++++++++++------- apps/calculation/history_view_cell.h | 1 + ...ble_exact_approximate_expressions_view.cpp | 19 ++++++-------- ...lable_exact_approximate_expressions_view.h | 5 +++- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index c3a5e1caf..90696bd6d 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -109,12 +109,13 @@ void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int if (previousSelectedCellY == selectedRow()) { return; } + HistoryViewCell * cell = static_cast(t->selectedCell()); if (previousSelectedCellY == -1) { - setSelectedSubviewType(SubviewType::Output); + setSelectedSubviewType(SubviewType::Output, cell); } else if (selectedRow() < previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Output); + setSelectedSubviewType(SubviewType::Output, cell); } else if (selectedRow() > previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Input); + setSelectedSubviewType(SubviewType::Input, cell); } HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell()); if (selectedCell == nullptr) { diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 1f8d915a9..1988fcd6b 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -19,6 +19,7 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, m_selectedSubviewType = subviewType; if (cell) { cell->setHighlighted(cell->isHighlighted()); + cell->cellDidSelectSubview(subviewType); } historyViewCellDidChangeSelection(); } @@ -72,18 +73,23 @@ void HistoryViewCell::reloadCell() { m_scrollableOutputView.evenOddCell()->reloadCell(); layoutSubviews(); - // Reload input scroll + // Reload subviews' scrolls m_inputView.reloadScroll(); + m_scrollableOutputView.reloadScroll(); +} - /* Select the right output according to the calculation display output. This - * will reload the scroll to display the selected output. */ - App * calculationApp = (App *)app(); - Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactAndApproximate) { - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); - } else { - assert(display == Calculation::DisplayOutput::ApproximateOnly || display == Calculation::DisplayOutput::ExactAndApproximateToggle || display == Calculation::DisplayOutput::ExactOnly); - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); +void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type) { + if (type == HistoryViewCellDataSource::SubviewType::Output) { + /* Select the right output according to the calculation display output. This + * will reload the scroll to display the selected output. */ + App * calculationApp = (App *)app(); + Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); + if (display == Calculation::DisplayOutput::ExactAndApproximate) { + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); + } else { + assert(display == Calculation::DisplayOutput::ApproximateOnly || display == Calculation::DisplayOutput::ExactAndApproximateToggle || display == Calculation::DisplayOutput::ExactOnly); + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); + } } } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index e9d9639ff..79c7264c8 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -31,6 +31,7 @@ class HistoryViewCell : public ::EvenOddCell, public Responder { public: HistoryViewCell(Responder * parentResponder = nullptr); void reloadCell() override; + void cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type); void setEven(bool even) override; void setHighlighted(bool highlight) override; void setDataSource(HistoryViewCellDataSource * dataSource) { m_dataSource = dataSource; } diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index 98bbc4d91..ebbf41791 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -125,16 +125,13 @@ void ScrollableExactApproximateExpressionsView::setEqualMessage(I18n::Message eq m_contentCell.approximateSign()->setMessage(equalSignMessage); } -void ScrollableExactApproximateExpressionsView::setSelectedSubviewPosition(SubviewPosition subviewPosition, bool scrollToSelection) { - m_contentCell.setSelectedSubviewPosition(subviewPosition); - if (scrollToSelection) { - if (subviewPosition == SubviewPosition::Left) { - // Scroll to the left extremity - reloadScroll(); - } else { - // Scroll to the right extremity - scrollToContentPoint(KDPoint(m_contentCell.bounds().width(), 0), true); - } +void ScrollableExactApproximateExpressionsView::reloadScroll() { + if (selectedSubviewPosition() == SubviewPosition::Left) { + // Scroll to the left extremity + ScrollableView::reloadScroll(); + } else { + // Scroll to the right extremity + scrollToContentPoint(KDPoint(m_contentCell.bounds().width(), 0), true); } } @@ -157,7 +154,7 @@ bool ScrollableExactApproximateExpressionsView::handleEvent(Ion::Events::Event e if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible) || (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftExpressionIsVisible)) { SubviewPosition otherSubviewPosition = selectedSubviewPosition() == SubviewPosition::Left ? SubviewPosition::Right : SubviewPosition::Left; - setSelectedSubviewPosition(otherSubviewPosition, false); + setSelectedSubviewPosition(otherSubviewPosition); return true; } return ScrollableView::handleEvent(event); diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.h b/apps/shared/scrollable_exact_approximate_expressions_view.h index 10b581861..40ec3c59a 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.h +++ b/apps/shared/scrollable_exact_approximate_expressions_view.h @@ -20,7 +20,10 @@ public: SubviewPosition selectedSubviewPosition() { return m_contentCell.selectedSubviewPosition(); } - void setSelectedSubviewPosition(SubviewPosition subviewPosition, bool reloadScroll = true); + void setSelectedSubviewPosition(SubviewPosition subviewPosition) { + m_contentCell.setSelectedSubviewPosition(subviewPosition); + } + void reloadScroll(); void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; Poincare::Layout layout() const { From 1535d4dbb427b50b9b5e5cda6aad6e3d871d3c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 25 Apr 2019 17:14:16 +0200 Subject: [PATCH 50/57] [calculation] HistoryViewCell: better split reload (highlight, scroll etc) between willDisplayCellForIndex and cellDidSelectSubview --- apps/calculation/history_controller.cpp | 8 +++++++- apps/calculation/history_controller.h | 2 +- apps/calculation/history_view_cell.cpp | 25 ++++++++++++++++--------- apps/calculation/history_view_cell.h | 2 +- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 90696bd6d..786345dfc 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -145,7 +145,7 @@ void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) HistoryViewCell * myCell = (HistoryViewCell *)cell; myCell->setCalculation(m_calculationStore->calculationAtIndex(index), index == selectedRow() && selectedSubviewType() == SubviewType::Output); myCell->setEven(index%2 == 0); - myCell->reloadCell(); + myCell->setHighlighted(myCell->isHighlighted()); } KDCoordinate HistoryController::rowHeight(int j) { @@ -165,4 +165,10 @@ void HistoryController::scrollToCell(int i, int j) { m_selectableTableView.scrollToCell(i, j); } +void HistoryController::historyViewCellDidChangeSelection() { + /* Update the whole table as the height of the selected cell row might have + * changed. */ + m_selectableTableView.reloadData(); +} + } diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index a41c9d501..9486748e5 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -28,7 +28,7 @@ public: void scrollToCell(int i, int j); private: CalculationSelectableTableView * selectableTableView(); - void historyViewCellDidChangeSelection() override { m_selectableTableView.reloadData(); } + void historyViewCellDidChangeSelection() override; constexpr static int k_maxNumberOfDisplayedRows = 5; CalculationSelectableTableView m_selectableTableView; HistoryViewCell m_calculationHistory[k_maxNumberOfDisplayedRows]; diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 1988fcd6b..62c1e97d1 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -69,28 +69,30 @@ Poincare::Layout HistoryViewCell::layout() const { } } -void HistoryViewCell::reloadCell() { - m_scrollableOutputView.evenOddCell()->reloadCell(); - layoutSubviews(); - - // Reload subviews' scrolls +void HistoryViewCell::reloadScroll() { m_inputView.reloadScroll(); m_scrollableOutputView.reloadScroll(); } void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type) { + App * calculationApp = (App *)app(); + Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); if (type == HistoryViewCellDataSource::SubviewType::Output) { /* Select the right output according to the calculation display output. This * will reload the scroll to display the selected output. */ - App * calculationApp = (App *)app(); - Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactAndApproximate) { + if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && m_calculation.toggleDisplayExact())) { m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); } else { - assert(display == Calculation::DisplayOutput::ApproximateOnly || display == Calculation::DisplayOutput::ExactAndApproximateToggle || display == Calculation::DisplayOutput::ExactOnly); + assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !m_calculation.toggleDisplayExact()) || display == Calculation::DisplayOutput::ExactOnly); m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); } } + /* The selected subview has changed. The displayed outputs might have changed. + * For example, for the calculation 1.2+2 --> 3.2, selecting the output would + * display 1.2+2 --> 16/5 = 3.2. */ + setCalculation(&m_calculation, type == HistoryViewCellDataSource::SubviewType::Output); + // Reload scroll when switching from one subview to another + reloadScroll(); } KDColor HistoryViewCell::backgroundColor() const { @@ -152,6 +154,11 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); + + /* The displayed input and outputs have changed. We need to re-layout the cell + * and re-initialize the scroll. */ + layoutSubviews(); + reloadScroll(); } void HistoryViewCell::didBecomeFirstResponder() { diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 79c7264c8..818a0225e 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -30,7 +30,6 @@ private: class HistoryViewCell : public ::EvenOddCell, public Responder { public: HistoryViewCell(Responder * parentResponder = nullptr); - void reloadCell() override; void cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type); void setEven(bool even) override; void setHighlighted(bool highlight) override; @@ -50,6 +49,7 @@ public: Shared::ScrollableExactApproximateExpressionsView * outputView(); private: constexpr static KDCoordinate k_resultWidth = 80; + void reloadScroll(); Calculation m_calculation; bool m_calculationSelected; ScrollableExpressionView m_inputView; From aebc5ce4d0c7d521cc177e6d45fc63b2bbabb37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 26 Apr 2019 09:50:25 +0200 Subject: [PATCH 51/57] [calculation] Calculation: discard Calculation::toggleDisplayExact (this feature will be developed later) --- apps/calculation/calculation.cpp | 7 +++---- apps/calculation/calculation.h | 3 --- apps/calculation/history_view_cell.cpp | 6 +++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index fc8f55f1a..66b7f883b 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -20,8 +20,7 @@ Calculation::Calculation() : m_approximateOutputText(), m_height(-1), m_selectedHeight(-1), - m_equalSign(EqualSign::Unknown), - m_toggleDisplayExact(false) + m_equalSign(EqualSign::Unknown) { } @@ -66,10 +65,10 @@ KDCoordinate Calculation::height(Context * context, bool isSelected) { DisplayOutput display = displayOutput(context); Layout inputLayout = createInputLayout(); KDCoordinate inputHeight = inputLayout.layoutSize().height(); - if (display == DisplayOutput::ExactOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle && m_toggleDisplayExact)) { + if (display == DisplayOutput::ExactOnly) { KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); *memoizedHeight = inputHeight+exactOutputHeight; - } else if (display == DisplayOutput::ApproximateOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle && !m_toggleDisplayExact)) { + } else if (display == DisplayOutput::ApproximateOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle)) { KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); *memoizedHeight = inputHeight+approximateOutputHeight; } else { diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index ac16e8fd2..a063121a1 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -43,8 +43,6 @@ public: bool isEmpty(); void tidy(); DisplayOutput displayOutput(Poincare::Context * context); - bool toggleDisplayExact() const { return m_toggleDisplayExact; } - void setToggleDisplayExact(bool displayExact) { m_toggleDisplayExact = displayExact; } bool shouldOnlyDisplayExactOutput(); EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context); private: @@ -58,7 +56,6 @@ private: KDCoordinate m_height; KDCoordinate m_selectedHeight; EqualSign m_equalSign; - bool m_toggleDisplayExact; }; } diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 62c1e97d1..8002ffc71 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -80,10 +80,10 @@ void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewTyp if (type == HistoryViewCellDataSource::SubviewType::Output) { /* Select the right output according to the calculation display output. This * will reload the scroll to display the selected output. */ - if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && m_calculation.toggleDisplayExact())) { + if (display == Calculation::DisplayOutput::ExactAndApproximate) { m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); } else { - assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !m_calculation.toggleDisplayExact()) || display == Calculation::DisplayOutput::ExactOnly); + assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle) || display == Calculation::DisplayOutput::ExactOnly); m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); } } @@ -143,7 +143,7 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) * layout, calling to layoutSubviews() would fail. */ Poincare::Layout leftOutputLayout = Poincare::Layout(); Poincare::Layout rightOutputLayout; - if (display == Calculation::DisplayOutput::ExactOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && !isSelected && calculation->toggleDisplayExact())) { + if (display == Calculation::DisplayOutput::ExactOnly) { rightOutputLayout = calculation->createExactOutputLayout(); } else { rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); From 7150669f5e98802cb12473679f4485cc8631e2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 26 Apr 2019 10:03:24 +0200 Subject: [PATCH 52/57] [calculation] Change name: 'calculation selected' --> 'calculation expanded' --- apps/calculation/calculation.cpp | 14 +++++++------- apps/calculation/calculation.h | 4 ++-- apps/calculation/history_view_cell.cpp | 10 +++++----- apps/calculation/history_view_cell.h | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 66b7f883b..5049b817f 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -19,7 +19,7 @@ Calculation::Calculation() : m_exactOutputText(), m_approximateOutputText(), m_height(-1), - m_selectedHeight(-1), + m_expandedHeight(-1), m_equalSign(EqualSign::Unknown) { } @@ -59,8 +59,8 @@ void Calculation::setContent(const char * c, Context * context, Expression ansEx PoincareHelpers::Serialize(approximateOutput, m_approximateOutputText, sizeof(m_approximateOutputText)); } -KDCoordinate Calculation::height(Context * context, bool isSelected) { - KDCoordinate * memoizedHeight = isSelected ? &m_selectedHeight : &m_height; +KDCoordinate Calculation::height(Context * context, bool expanded) { + KDCoordinate * memoizedHeight = expanded ? &m_expandedHeight : &m_height; if (*memoizedHeight < 0) { DisplayOutput display = displayOutput(context); Layout inputLayout = createInputLayout(); @@ -68,11 +68,11 @@ KDCoordinate Calculation::height(Context * context, bool isSelected) { if (display == DisplayOutput::ExactOnly) { KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); *memoizedHeight = inputHeight+exactOutputHeight; - } else if (display == DisplayOutput::ApproximateOnly || (!isSelected && display == DisplayOutput::ExactAndApproximateToggle)) { + } else if (display == DisplayOutput::ApproximateOnly || (!expanded && display == DisplayOutput::ExactAndApproximateToggle)) { KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); *memoizedHeight = inputHeight+approximateOutputHeight; } else { - assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && isSelected)); + assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && expanded)); Layout approximateLayout = createApproximateOutputLayout(context); Layout exactLayout = createExactOutputLayout(); KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); @@ -85,7 +85,7 @@ KDCoordinate Calculation::height(Context * context, bool isSelected) { * theses cases. */ if (display != DisplayOutput::ExactAndApproximateToggle) { m_height = *memoizedHeight; - m_selectedHeight = *memoizedHeight; + m_expandedHeight = *memoizedHeight; } } return *memoizedHeight; @@ -128,7 +128,7 @@ bool Calculation::isEmpty() { void Calculation::tidy() { /* Uninitialized all Expression stored to free the Pool */ m_height = -1; - m_selectedHeight = -1; + m_expandedHeight = -1; m_equalSign = EqualSign::Unknown; } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index a063121a1..edc60d05d 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -30,7 +30,7 @@ public: /* c.reset() is the equivalent of c = Calculation() without copy assingment. */ void reset(); void setContent(const char * c, Poincare::Context * context, Poincare::Expression ansExpression); - KDCoordinate height(Poincare::Context * context, bool isSelected = false); + KDCoordinate height(Poincare::Context * context, bool expanded = false); const char * inputText(); const char * exactOutputText(); const char * approximateOutputText(); @@ -54,7 +54,7 @@ private: char m_exactOutputText[Constant::MaxSerializedExpressionSize]; char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; KDCoordinate m_height; - KDCoordinate m_selectedHeight; + KDCoordinate m_expandedHeight; EqualSign m_equalSign; }; diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 8002ffc71..073af802b 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -29,7 +29,7 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, HistoryViewCell::HistoryViewCell(Responder * parentResponder) : Responder(parentResponder), m_calculation(), - m_calculationSelected(false), + m_calculationExpanded(false), m_inputView(this), m_scrollableOutputView(this) { @@ -128,13 +128,13 @@ void HistoryViewCell::layoutSubviews() { )); } -void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) { - if (m_calculationSelected == isSelected && *calculation == m_calculation) { +void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { + if (m_calculationExpanded == expanded && *calculation == m_calculation) { return; } // Memoization m_calculation = *calculation; - m_calculationSelected = isSelected; + m_calculationExpanded = expanded; App * calculationApp = (App *)app(); Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); m_inputView.setLayout(calculation->createInputLayout()); @@ -147,7 +147,7 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool isSelected) rightOutputLayout = calculation->createExactOutputLayout(); } else { rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && isSelected)) { + if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && expanded)) { leftOutputLayout = calculation->createExactOutputLayout(); } } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 818a0225e..dcb4b1289 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -40,7 +40,7 @@ public: Poincare::Layout layout() const override; KDColor backgroundColor() const override; Calculation * calculation() { return &m_calculation; } - void setCalculation(Calculation * calculation, bool isSelected = false); + void setCalculation(Calculation * calculation, bool expanded = false); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews() override; @@ -51,7 +51,7 @@ private: constexpr static KDCoordinate k_resultWidth = 80; void reloadScroll(); Calculation m_calculation; - bool m_calculationSelected; + bool m_calculationExpanded; ScrollableExpressionView m_inputView; Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; HistoryViewCellDataSource * m_dataSource; From 87c5004aec3e3e7ebd3d478cc45c430665696421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 26 Apr 2019 10:14:11 +0200 Subject: [PATCH 53/57] [calculation] HistoryViewCell: clean --- apps/calculation/history_view_cell.cpp | 23 ++++++++++++++--------- apps/calculation/history_view_cell.h | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 073af802b..204d3f6ec 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -74,18 +74,23 @@ void HistoryViewCell::reloadScroll() { m_scrollableOutputView.reloadScroll(); } -void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type) { +void HistoryViewCell::reloadOutputSelection() { App * calculationApp = (App *)app(); Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); + /* Select the right output according to the calculation display output. This + * will reload the scroll to display the selected output. */ + if (display == Calculation::DisplayOutput::ExactAndApproximate) { + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); + } else { + assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle) || display == Calculation::DisplayOutput::ExactOnly); + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); + } +} + +void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type) { + // Init output selection if (type == HistoryViewCellDataSource::SubviewType::Output) { - /* Select the right output according to the calculation display output. This - * will reload the scroll to display the selected output. */ - if (display == Calculation::DisplayOutput::ExactAndApproximate) { - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); - } else { - assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle) || display == Calculation::DisplayOutput::ExactOnly); - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); - } + reloadOutputSelection(); } /* The selected subview has changed. The displayed outputs might have changed. * For example, for the calculation 1.2+2 --> 3.2, selecting the output would diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index dcb4b1289..5a2d00d88 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -50,6 +50,7 @@ public: private: constexpr static KDCoordinate k_resultWidth = 80; void reloadScroll(); + void reloadOutputSelection(); Calculation m_calculation; bool m_calculationExpanded; ScrollableExpressionView m_inputView; From 80ad1e9623ea56547aca3578c33999cfbc588604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 26 Apr 2019 10:41:36 +0200 Subject: [PATCH 54/57] [shared] Clean reloading (highlight, even) of ScrollableExactApproximateExpressionsCell and ScrollableExactApproximateExpressionsView --- ...scrollable_exact_approximate_expressions_cell.cpp | 4 ---- .../scrollable_exact_approximate_expressions_cell.h | 1 - ...scrollable_exact_approximate_expressions_view.cpp | 12 +++++++++--- .../scrollable_exact_approximate_expressions_view.h | 3 ++- apps/solver/solutions_controller.cpp | 1 - 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp index 3894849dd..cfb76a462 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp @@ -21,10 +21,6 @@ void ScrollableExactApproximateExpressionsCell::setEven(bool even) { m_view.evenOddCell()->setEven(even); } -void ScrollableExactApproximateExpressionsCell::reloadCell() { - m_view.evenOddCell()->reloadCell(); -} - void ScrollableExactApproximateExpressionsCell::reloadScroll() { m_view.reloadScroll(); } diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.h b/apps/shared/scrollable_exact_approximate_expressions_cell.h index 3d8a8209f..fd3da4ace 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.h +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.h @@ -17,7 +17,6 @@ public: } void setHighlighted(bool highlight) override; void setEven(bool even) override; - void reloadCell() override; void reloadScroll(); Responder * responder() override { return this; diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index ebbf41791..d878def8b 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -25,6 +25,7 @@ void ScrollableExactApproximateExpressionsView::ContentCell::setHighlighted(bool m_highlighted = highlight; m_leftExpressionView.setBackgroundColor(backgroundColor()); m_rightExpressionView.setBackgroundColor(backgroundColor()); + m_approximateSign.setBackgroundColor(backgroundColor()); if (highlight) { if (m_selectedSubviewPosition == SubviewPosition::Left) { m_leftExpressionView.setBackgroundColor(Palette::Select); @@ -34,15 +35,19 @@ void ScrollableExactApproximateExpressionsView::ContentCell::setHighlighted(bool } } -void ScrollableExactApproximateExpressionsView::ContentCell::reloadCell() { - setHighlighted(isHighlighted()); +void ScrollableExactApproximateExpressionsView::ContentCell::setEven(bool even) { + EvenOddCell::setEven(even); + m_leftExpressionView.setBackgroundColor(backgroundColor()); + m_rightExpressionView.setBackgroundColor(backgroundColor()); m_approximateSign.setBackgroundColor(backgroundColor()); +} + +void ScrollableExactApproximateExpressionsView::ContentCell::reloadTextColor() { if (numberOfSubviews() == 1) { m_rightExpressionView.setTextColor(KDColorBlack); } else { m_rightExpressionView.setTextColor(Palette::GreyVeryDark); } - layoutSubviews(); } KDSize ScrollableExactApproximateExpressionsView::ContentCell::minimalSizeForOptimalDisplay() const { @@ -117,6 +122,7 @@ void ScrollableExactApproximateExpressionsView::setLayouts(Poincare::Layout righ bool updateRightLayout = m_contentCell.rightExpressionView()->setLayout(rightLayout); bool updateLeftLayout = m_contentCell.leftExpressionView()->setLayout(leftLayout); if (updateRightLayout || updateLeftLayout) { + m_contentCell.reloadTextColor(); m_contentCell.layoutSubviews(); } } diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.h b/apps/shared/scrollable_exact_approximate_expressions_view.h index 40ec3c59a..31d2afd87 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.h +++ b/apps/shared/scrollable_exact_approximate_expressions_view.h @@ -35,7 +35,8 @@ private: ContentCell(); KDColor backgroundColor() const override; void setHighlighted(bool highlight) override; - void reloadCell() override; + void setEven(bool even) override; + void reloadTextColor(); KDSize minimalSizeForOptimalDisplay() const override; ExpressionView * rightExpressionView() { return &m_rightExpressionView; diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 346e053e6..77e2138e4 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -203,7 +203,6 @@ void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, } EvenOddCell * evenOddCell = static_cast(cell); evenOddCell->setEven(j%2 == 0); - evenOddCell->reloadCell(); } KDCoordinate SolutionsController::columnWidth(int i) { From 78907aeb4ab8fb3599edb4e4addf9cdb4246d22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 26 Apr 2019 10:46:46 +0200 Subject: [PATCH 55/57] [calculation] Calculation: memoized display output --- apps/calculation/calculation.cpp | 22 ++++++++++++++-------- apps/calculation/calculation.h | 2 ++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 5049b817f..34044090b 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -18,6 +18,7 @@ Calculation::Calculation() : m_inputText(), m_exactOutputText(), m_approximateOutputText(), + m_displayOutput(DisplayOutput::Unknown), m_height(-1), m_expandedHeight(-1), m_equalSign(EqualSign::Unknown) @@ -127,6 +128,7 @@ bool Calculation::isEmpty() { void Calculation::tidy() { /* Uninitialized all Expression stored to free the Pool */ + m_displayOutput = DisplayOutput::Unknown; m_height = -1; m_expandedHeight = -1; m_equalSign = EqualSign::Unknown; @@ -165,29 +167,33 @@ Layout Calculation::createApproximateOutputLayout(Context * context) { } Calculation::DisplayOutput Calculation::displayOutput(Context * context) { - if (shouldOnlyDisplayExactOutput()) { - return DisplayOutput::ExactOnly; + if (m_displayOutput != DisplayOutput::Unknown) { + return m_displayOutput; } - if (exactOutput().recursivelyMatches([](const Expression e, Context & c, bool replaceSymbols) { + if (shouldOnlyDisplayExactOutput()) { + m_displayOutput = DisplayOutput::ExactOnly; + } else if (exactOutput().recursivelyMatches([](const Expression e, Context & c, bool replaceSymbols) { /* If the exact result contains one of the following types, do not * display it. */ ExpressionNode::Type t = e.type(); return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round);}, *context, true)) { - return DisplayOutput::ApproximateOnly; + m_displayOutput = DisplayOutput::ApproximateOnly; } else if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { /* If the exact and approximate results' texts are equal and their layouts * too, do not display the exact result. If the two layouts are not equal * because of the number of significant digits, we display both. */ - return exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; + m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; } else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0) { // If the approximate result is 'unreal' or the exact result is 'undef' - return DisplayOutput::ApproximateOnly; + m_displayOutput = DisplayOutput::ApproximateOnly; } else if (input().isApproximate(*context) || exactOutput().isApproximate(*context)) { - return DisplayOutput::ExactAndApproximateToggle; + m_displayOutput = DisplayOutput::ExactAndApproximateToggle; + } else { + m_displayOutput = DisplayOutput::ExactAndApproximate; } - return DisplayOutput::ExactAndApproximate; + return m_displayOutput; } bool Calculation::shouldOnlyDisplayExactOutput() { diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index edc60d05d..ff00be74c 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -19,6 +19,7 @@ public: }; enum class DisplayOutput : uint8_t { + Unknown, ExactOnly, ApproximateOnly, ExactAndApproximate, @@ -53,6 +54,7 @@ private: char m_inputText[Constant::MaxSerializedExpressionSize]; char m_exactOutputText[Constant::MaxSerializedExpressionSize]; char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; + DisplayOutput m_displayOutput; KDCoordinate m_height; KDCoordinate m_expandedHeight; EqualSign m_equalSign; From 661c7b9943252a1f665b391a8d3f5dedffa4ab09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 26 Apr 2019 14:48:31 +0200 Subject: [PATCH 56/57] [escher] SelectableTableView: when reloading data, we temporary deselect the table. We warn the SelectableTableViewDelegate that the selection change is 'within a temporary selection change' when notifying it of the change. --- apps/calculation/history_controller.cpp | 4 ++-- apps/calculation/history_controller.h | 2 +- apps/code/console_controller.cpp | 5 ++++- apps/code/console_controller.h | 2 +- apps/code/menu_controller.cpp | 4 ++-- apps/code/menu_controller.h | 2 +- apps/home/controller.cpp | 5 ++++- apps/home/controller.h | 2 +- apps/regression/calculation_controller.cpp | 5 ++++- apps/regression/calculation_controller.h | 2 +- apps/sequence/list/list_parameter_controller.cpp | 4 ++-- apps/sequence/list/list_parameter_controller.h | 2 +- apps/shared/expression_model_list_controller.cpp | 4 ++-- apps/shared/expression_model_list_controller.h | 2 +- apps/shared/function_list_controller.cpp | 6 +++--- apps/shared/function_list_controller.h | 2 +- escher/include/escher/selectable_table_view.h | 4 ++-- .../escher/selectable_table_view_delegate.h | 7 ++++++- escher/src/selectable_table_view.cpp | 16 ++++++++-------- escher/src/selectable_table_view_delegate.cpp | 2 +- 20 files changed, 48 insertions(+), 34 deletions(-) diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 786345dfc..9678c533c 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -105,8 +105,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { return false; } -void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { - if (previousSelectedCellY == selectedRow()) { +void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection || previousSelectedCellY == selectedRow()) { return; } HistoryViewCell * cell = static_cast(t->selectedCell()); diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index 9486748e5..67caf5ec9 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -24,7 +24,7 @@ public: void willDisplayCellForIndex(HighlightCell * cell, int index) override; KDCoordinate rowHeight(int j) override; int typeAtLocation(int i, int j) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; void scrollToCell(int i, int j); private: CalculationSelectableTableView * selectableTableView(); diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index d5da9c826..429a0f170 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -245,7 +245,10 @@ void ConsoleController::willDisplayCellAtLocation(HighlightCell * cell, int i, i } } -void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } if (t->selectedRow() == m_consoleStore.numberOfLines()) { m_editCell.setEditing(true); return; diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index d39f6edfe..43779f340 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -52,7 +52,7 @@ public: void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // TextFieldDelegate bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 60dda9eec..78adafc2f 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -272,8 +272,8 @@ void MenuController::willDisplayScriptTitleCellForIndex(HighlightCell * cell, in (static_cast(cell))->textField()->setText(m_scriptStore->scriptAtIndex(index).fullName()); } -void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { - if (selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) { +void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (!withinTemporarySelection && selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) { t->selectCellAtLocation(0, numberOfRows()-1); } } diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 7b94014f4..5848a302a 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -46,7 +46,7 @@ public: void willDisplayScriptTitleCellForIndex(HighlightCell * cell, int index); /* SelectableTableViewDelegate */ - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; /* TextFieldDelegate */ bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 10f0075a5..f177204ec 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -134,7 +134,10 @@ int Controller::numberOfIcons() { return m_container->numberOfApps() - 1; } -void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } /* If the number of apps (including home) is != 3*n+1, when we display the * lowest icons, the other(s) are empty. As no icon is thus redrawn on the * previous ones, the cell is not cleaned. We need to redraw a white rect on diff --git a/apps/home/controller.h b/apps/home/controller.h index ae3be69e5..116b6291a 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -25,7 +25,7 @@ public: virtual HighlightCell * reusableCell(int index) override; virtual int reusableCellCount() override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; private: int numberOfIcons(); class ContentView : public View { diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 0a0ddb1db..145950d19 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -65,7 +65,10 @@ void CalculationController::didBecomeFirstResponder() { TabTableController::didBecomeFirstResponder(); } -void CalculationController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void CalculationController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } /* To prevent selecting cell with no content (top left corner of the table), * as soon as the selected cell is the top left corner, we either reselect * the previous cell or select the tab controller depending on from which cell diff --git a/apps/regression/calculation_controller.h b/apps/regression/calculation_controller.h index e59265a62..00237fa73 100644 --- a/apps/regression/calculation_controller.h +++ b/apps/regression/calculation_controller.h @@ -29,7 +29,7 @@ public: void didBecomeFirstResponder() override; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // AlternateEmptyViewDefaultDelegate bool isEmpty() const override; diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index cc0f25fcf..ac36094be 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -92,8 +92,8 @@ bool ListParameterController::textFieldDidFinishEditing(TextField * textField, c return true; } -void ListParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { - if (previousSelectedCellX == t->selectedColumn() && previousSelectedCellY == t->selectedRow()) { +void ListParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection || (previousSelectedCellX == t->selectedColumn() && previousSelectedCellY == t->selectedRow())) { return; } if (!hasInitialRankRow()) { diff --git a/apps/sequence/list/list_parameter_controller.h b/apps/sequence/list/list_parameter_controller.h index 09ff8c4cd..e43ee5d43 100644 --- a/apps/sequence/list/list_parameter_controller.h +++ b/apps/sequence/list/list_parameter_controller.h @@ -19,7 +19,7 @@ public: bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; Shared::TextFieldDelegateApp * textFieldDelegateApp() override; // ListViewDataSource diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp index fe4a33aa4..1bca31721 100644 --- a/apps/shared/expression_model_list_controller.cpp +++ b/apps/shared/expression_model_list_controller.cpp @@ -16,11 +16,11 @@ ExpressionModelListController::ExpressionModelListController(Responder * parentR m_addNewModel.setMessage(text); } -void ExpressionModelListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void ExpressionModelListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { int currentSelectedRow = selectedRow(); // Update m_cumulatedHeightForSelectedIndex if we scrolled one cell up/down - if (previousSelectedCellY >= 0 && previousSelectedCellY == previousSelectedCellY + 1) { + if (currentSelectedRow >= 0 && currentSelectedRow == previousSelectedCellY + 1) { /* We selected the cell under the previous cell. Shift the memoized cell * heights. */ shiftMemoization(true); diff --git a/apps/shared/expression_model_list_controller.h b/apps/shared/expression_model_list_controller.h index 9fc00367f..82ae4db1a 100644 --- a/apps/shared/expression_model_list_controller.h +++ b/apps/shared/expression_model_list_controller.h @@ -13,7 +13,7 @@ public: protected: static constexpr KDCoordinate k_expressionMargin = 5; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // TableViewDataSource virtual int numberOfExpressionRows(); KDCoordinate memoizedRowHeight(int j); diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp index cc65e5211..9dfa8dce2 100644 --- a/apps/shared/function_list_controller.cpp +++ b/apps/shared/function_list_controller.cpp @@ -214,11 +214,11 @@ void FunctionListController::willExitResponderChain(Responder * nextFirstRespond /* SelectableTableViewDelegate */ -void FunctionListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void FunctionListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { // Update memoization of cell heights - ExpressionModelListController::tableViewDidChangeSelection(t, previousSelectedCellX, previousSelectedCellY); + ExpressionModelListController::tableViewDidChangeSelection(t, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection); // Do not select the cell left of the "addEmptyFunction" cell - if (isAddEmptyRow(selectedRow()) && selectedColumn() == 0) { + if (!withinTemporarySelection && isAddEmptyRow(selectedRow()) && selectedColumn() == 0) { t->selectCellAtLocation(1, numberOfRows()-1); } } diff --git a/apps/shared/function_list_controller.h b/apps/shared/function_list_controller.h index 273edd401..8bc0dd055 100644 --- a/apps/shared/function_list_controller.h +++ b/apps/shared/function_list_controller.h @@ -45,7 +45,7 @@ public: View * view() override { return &m_selectableTableView; } /* SelectableTableViewDelegate*/ - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; /* ExpressionModelListController */ SelectableTableView * selectableTableView() override { return &m_selectableTableView; } diff --git a/escher/include/escher/selectable_table_view.h b/escher/include/escher/selectable_table_view.h index 6e1e7c000..cecd44a74 100644 --- a/escher/include/escher/selectable_table_view.h +++ b/escher/include/escher/selectable_table_view.h @@ -29,8 +29,8 @@ public: virtual bool handleEvent(Ion::Events::Event event) override; virtual void didEnterResponderChain(Responder * previousFirstResponder) override; virtual void willExitResponderChain(Responder * nextFirstResponder) override; - void deselectTable(bool notifySelectableDelegate = true); - bool selectCellAtLocation(int i, int j, bool setFirstResponder = true, bool notifySelectableDelegate = true); + void deselectTable(bool withinTemporarySelection = false); + bool selectCellAtLocation(int i, int j, bool setFirstResponder = true, bool withinTemporarySelection = false); HighlightCell * selectedCell(); protected: SelectableTableViewDataSource * m_selectionDataSource; diff --git a/escher/include/escher/selectable_table_view_delegate.h b/escher/include/escher/selectable_table_view_delegate.h index 33249f3f9..e8f11864a 100644 --- a/escher/include/escher/selectable_table_view_delegate.h +++ b/escher/include/escher/selectable_table_view_delegate.h @@ -5,7 +5,12 @@ class SelectableTableView; class SelectableTableViewDelegate { public: - virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY); + /* withinTemporarySelection flag indicates when the selection change happens + * in a temporary deselection: indeed, when reloading the data of the table, + * we deselect the table before re-layouting the entire table and re-select + * the previous selected cell. We might implement different course of action + * when the selection change is 'real' or within temporary selection. */ + virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false); }; #endif diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 995e7c3e7..226701a6d 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -37,7 +37,7 @@ void SelectableTableView::selectColumn(int i) { void SelectableTableView::reloadData(bool setFirstResponder) { int col = selectedColumn(); int row = selectedRow(); - deselectTable(false); + deselectTable(true); /* FIXME: The problem with calling deselectTable is that at this point in time * the datasource's model is very likely to have changed. Therefore it's * rather complicated to get a pointer to the currently selected cell (in @@ -45,7 +45,7 @@ void SelectableTableView::reloadData(bool setFirstResponder) { /* As a workaround, datasources can reset the highlighted state in their * willDisplayCell callback. */ TableView::layoutSubviews(); - selectCellAtLocation(col, row, setFirstResponder, false); + selectCellAtLocation(col, row, setFirstResponder, true); } void SelectableTableView::didEnterResponderChain(Responder * previousFirstResponder) { @@ -60,18 +60,18 @@ void SelectableTableView::willExitResponderChain(Responder * nextFirstResponder) unhighlightSelectedCell(); } -void SelectableTableView::deselectTable(bool notifySelectableDelegate) { +void SelectableTableView::deselectTable(bool withinTemporarySelection) { unhighlightSelectedCell(); int previousSelectedCellX = selectedColumn(); int previousSelectedCellY = selectedRow(); selectColumn(0); selectRow(-1); - if (m_delegate && notifySelectableDelegate) { - m_delegate->tableViewDidChangeSelection(this, previousSelectedCellX, previousSelectedCellY); + if (m_delegate) { + m_delegate->tableViewDidChangeSelection(this, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection); } } -bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstResponder, bool notifySelectableDelegate) { +bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstResponder, bool withinTemporarySelection) { if (i < 0 || i >= dataSource()->numberOfColumns()) { return false; } @@ -84,8 +84,8 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon selectColumn(i); selectRow(j); - if (m_delegate && notifySelectableDelegate) { - m_delegate->tableViewDidChangeSelection(this, previousX, previousY); + if (m_delegate) { + m_delegate->tableViewDidChangeSelection(this, previousX, previousY, withinTemporarySelection); } /* We need to scroll: diff --git a/escher/src/selectable_table_view_delegate.cpp b/escher/src/selectable_table_view_delegate.cpp index 3a8de3636..25da1557a 100644 --- a/escher/src/selectable_table_view_delegate.cpp +++ b/escher/src/selectable_table_view_delegate.cpp @@ -1,4 +1,4 @@ #include -void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { } From 6c476526d159f92fc1fb5901b92f3d90acee9ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 29 Apr 2019 17:55:28 +0200 Subject: [PATCH 57/57] [calculation] HistoryController: Avoid useless cell reloading (avoid blinking) --- apps/calculation/history_controller.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 9678c533c..3b75f6ee6 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -122,7 +122,6 @@ void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int return; } app()->setFirstResponder(selectedCell); - selectedCell->reloadCell(); } int HistoryController::numberOfRows() {