M .vscode/launch.json => .vscode/launch.json +2 -1
@@ 64,7 64,7 @@
"cwd": "${workspaceFolder}",
"gdbPath": "arm-none-eabi-gdb",
// "debuggerArgs": ["-x", "${workspaceFolder}/.gdbinit-1051"],
- // "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"],
+ "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"],
// "runToEntryPoint": "main",
// "runToMain": true,
"preLaunchCommands": [],
@@ 109,6 109,7 @@
"serverpath": "JLinkGDBServerCLExe",
"cwd": "${workspaceFolder}",
"gdbPath": "arm-none-eabi-gdb",
+ "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"],
"interface": "swd",
"device": "MCIMXRT1051",
"jlinkscript": "${workspaceFolder}/evkbimxrt1050_sdram_init_T6.jlinkscript",
M .vscode/tasks.json => .vscode/tasks.json +38 -0
@@ 169,6 169,44 @@
"detail": "Configure cmake project and build with Ninja",
},
+ {
+ "type": "shell",
+ "label": "Configure PurePhone (RT1051, DEV)",
+ "command": "./configure.sh",
+ "args": [
+ "pure",
+ "rt1051",
+ "RelWithDebInfo",
+ "-DWITH_DEVELOPMENT_FEATURES=ON",
+ "-G",
+ "Ninja"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "group": "build",
+ "detail": "Run cmake configure script",
+ },
+
+ {
+ "type": "shell",
+ "label": "Configure BellHybrid (RT1051, DEV)",
+ "command": "./configure.sh",
+ "args": [
+ "bell",
+ "rt1051",
+ "RelWithDebInfo",
+ "-DWITH_DEVELOPMENT_FEATURES=ON",
+ "-G",
+ "Ninja"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "group": "build",
+ "detail": "Run cmake configure script",
+ },
+
// {
// "type": "shell",
// "label": "Run JLink Server for PurePhone (RT1051)",
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 23,6 23,7 @@
#### UI/UX:
* UI update (Home Screen settings).
+* Improve refreshing of the display.
#### Translations:
* Completed missing translations.
M module-apps/application-call/windows/CallWindow.cpp => module-apps/application-call/windows/CallWindow.cpp +4 -0
@@ 123,6 123,10 @@ namespace gui
app::manager::OnSwitchBehaviour::RunInBackground);
};
+ sendSmsIcon->setVisible(false);
+ microphoneIcon->setVisible(false);
+ speakerIcon->setVisible(false);
+
// define navigation between icons
microphoneIcon->setNavigationItem(NavigationDirection::LEFT, speakerIcon);
microphoneIcon->setNavigationItem(NavigationDirection::RIGHT, speakerIcon);
M module-bsp/board/linux/eink/ED028TC1.c => module-bsp/board/linux/eink/ED028TC1.c +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/**
@@ 119,7 119,7 @@ EinkStatus_e EinkResetAndInitialize()
return EinkOK;
}
-EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer)
+EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, const uint8_t *buffer)
{
uint32_t offset_eink = frame.pos_y * BOARD_EINK_DISPLAY_RES_X + frame.pos_x;
uint32_t offset_buffer = 0;
M module-bsp/board/linux/eink/ED028TC1.h => module-bsp/board/linux/eink/ED028TC1.h +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/**
@@ 90,7 90,7 @@ extern "C"
* @return EinkNoMem - Could not allocate the temporary buffer
* EinkOK - Part of image send successfully
*/
- EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer);
+ EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, const uint8_t *buffer);
/**
* @brief This function sets the waveform to the \ref EinkWaveformINIT to make the display clearing more deep and
M module-bsp/board/linux/eink/LinuxEinkDisplay.cpp => module-bsp/board/linux/eink/LinuxEinkDisplay.cpp +23 -2
@@ 47,9 47,30 @@ namespace hal::eink
displayColorMode = mode;
}
- EinkStatus LinuxEinkDisplay::showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode)
+ EinkStatus LinuxEinkDisplay::showImageUpdate(const std::vector<EinkFrame> &updateFrames,
+ const std::uint8_t *frameBuffer)
{
- return translateStatus(EinkUpdateFrame(EinkFrame_t{0, 0, size.width, size.height}, frameBuffer));
+ for (const EinkFrame &frame : updateFrames) {
+ const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width;
+ const auto status = translateStatus(
+ EinkUpdateFrame({frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, buffer));
+ if (status != EinkStatus::EinkOK)
+ return status;
+ }
+ return EinkStatus::EinkOK;
+ }
+
+ EinkStatus LinuxEinkDisplay::showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode)
+ {
+ return EinkStatus::EinkOK;
+ }
+
+ EinkStatus LinuxEinkDisplay::showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode)
+ {
+ return showImageUpdate(updateFrames, frameBuffer);
}
void LinuxEinkDisplay::prepareEarlyRequest([[maybe_unused]] const EinkRefreshMode refreshMode,
M module-bsp/board/linux/eink/LinuxEinkDisplay.hpp => module-bsp/board/linux/eink/LinuxEinkDisplay.hpp +7 -1
@@ 15,7 15,13 @@ namespace hal::eink
private:
void setMode(const EinkDisplayColorMode mode) noexcept override;
- EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) override;
+ EinkStatus showImageUpdate(const std::vector<EinkFrame> &updateFrames,
+ const std::uint8_t *frameBuffer) override;
+ EinkStatus showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) override;
+ EinkStatus showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode) override;
void prepareEarlyRequest(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour) override;
void dither() override;
M module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp => module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp +22 -19
@@ 138,7 138,7 @@ static uint8_t s_einkMaskLut_2Bpp[16] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 187,7 187,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 236,7 236,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 285,7 285,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 334,7 334,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 383,7 383,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(uint8_t *data
*
* @return
*/
-static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 432,7 432,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(uint8_t *data
*
* @return
*/
-static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 487,7 487,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(uint8_t *data
* It is used when EINK_ROTATE_90_CLOCKWISE is not defined.
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 799,7 799,10 @@ EinkStatus_e EinkDitherDisplay()
return EinkOK;
}
-EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer, EinkBpp_e bpp, EinkDisplayColorMode_e invertColors)
+EinkStatus_e EinkUpdateFrame(EinkFrame_t frame,
+ const uint8_t *buffer,
+ EinkBpp_e bpp,
+ EinkDisplayColorMode_e invertColors)
{
uint8_t buf[10];
uint8_t pixelsInByte = 8 / bpp;
@@ 1004,7 1007,7 @@ EinkStatus_e EinkRefreshImage(EinkFrame_t frame, EinkDisplayTimingsMode_e refres
return EinkOK;
}
-__attribute__((optimize("O3"))) void EinkARGBToLuminance(uint8_t *dataIn,
+__attribute__((optimize("O3"))) void EinkARGBToLuminance(const uint8_t *dataIn,
uint8_t *dataOut,
uint32_t displayWidth,
uint32_t displayHeight)
@@ 1037,7 1040,7 @@ __attribute__((optimize("O3"))) void EinkARGBToLuminance(uint8_t *dataIn,
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1084,7 1087,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1135,7 1138,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1146,7 1149,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1193,7 1196,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoo
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1242,7 1245,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoo
}
__attribute__((optimize("O3"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1253,7 1256,7 @@ __attribute__((optimize("O3"))) static uint8_t *s_EinkTransformAnimationFrameCoo
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1300,7 1303,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1315,7 1318,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
int32_t inputRow = 0;
int32_t inputCol = 0;
- for (inputRow = 0; inputRow < windowHeightPx - 1; ++inputRow) {
+ for (inputRow = 0; inputRow < windowHeightPx; ++inputRow) {
for (inputCol = windowWidthPx - 7; inputCol >= 0; inputCol -= pixelsInByte) {
// HACK: Did not create the loop for accessing pixels and merging them in the single byte for better
// performance.
M module-bsp/board/rt1051/bsp/eink/ED028TC1.h => module-bsp/board/rt1051/bsp/eink/ED028TC1.h +2 -2
@@ 382,7 382,7 @@ extern "C"
* EinkOK - Part of image send successfully
*/
EinkStatus_e EinkUpdateFrame(EinkFrame_t frame,
- uint8_t *buffer,
+ const uint8_t *buffer,
EinkBpp_e bpp,
EinkDisplayColorMode_e invertColors);
@@ 431,7 431,7 @@ extern "C"
* @param displayWidth [in] - display width in pixels
* @param displayHeight [in] - display height in pixels
*/
- void EinkARGBToLuminance(uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight);
+ void EinkARGBToLuminance(const uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight);
#if defined(__cplusplus)
}
M module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp => module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp +59 -29
@@ 4,10 4,10 @@
#include "EinkDisplay.hpp"
#include <board/BoardDefinitions.hpp>
-
#include <log/log.hpp>
#include <gsl/util>
+
#include <stdexcept>
#include <cstdio>
#include <cstring>
@@ 132,17 132,6 @@ namespace hal::eink
delete[] currentWaveform.LUTDData;
}
- EinkStatus EinkDisplay::updateDisplay(std::uint8_t *frameBuffer, [[maybe_unused]] const EinkRefreshMode refreshMode)
- {
- return update(frameBuffer);
- }
-
- EinkStatus EinkDisplay::refreshDisplay(const EinkRefreshMode refreshMode)
- {
- const auto isDeepRefresh = refreshMode == EinkRefreshMode::REFRESH_DEEP;
- return refresh(isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
- }
-
EinkStatus EinkDisplay::prepareDisplay(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour)
{
powerOn();
@@ 160,20 149,75 @@ namespace hal::eink
return setWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
}
- EinkStatus EinkDisplay::showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode)
+ EinkStatus EinkDisplay::updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer)
+ {
+ return translateStatus(
+ EinkUpdateFrame(EinkFrame_t{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height},
+ frameBuffer,
+ getCurrentBitsPerPixelFormat(),
+ translateDisplayColorMode(displayMode)));
+ }
+
+ EinkStatus EinkDisplay::refreshDisplay(EinkFrame frame, const EinkRefreshMode refreshMode)
+ {
+ const auto refreshTimingsMode = refreshMode == EinkRefreshMode::REFRESH_DEEP
+ ? EinkDisplayTimingsDeepCleanMode
+ : EinkDisplayTimingsFastRefreshMode;
+
+ currentWaveform.useCounter += 1;
+ return translateStatus(EinkRefreshImage(
+ EinkFrame_t{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, refreshTimingsMode));
+ }
+
+ EinkStatus EinkDisplay::showImageUpdate(const std::vector<EinkFrame> &updateFrames, const std::uint8_t *frameBuffer)
+ {
+ powerOn();
+
+ for (const EinkFrame &frame : updateFrames) {
+ const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width;
+ if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) {
+ return status;
+ }
+ }
+
+ return EinkStatus::EinkOK;
+ }
+
+ EinkStatus EinkDisplay::showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode)
{
if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KEEP_CURRENT);
status != EinkStatus::EinkOK) {
return status;
}
- if (const auto status = updateDisplay(frameBuffer, refreshMode); status != EinkStatus::EinkOK) {
+ if (const auto status = refreshDisplay(refreshFrame, refreshMode); status != EinkStatus::EinkOK) {
+ return status;
+ }
+
+ return EinkStatus::EinkOK;
+ }
+
+ EinkStatus EinkDisplay::showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode)
+ {
+ if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KEEP_CURRENT);
+ status != EinkStatus::EinkOK) {
return status;
}
- if (const auto status = refreshDisplay(refreshMode); status != EinkStatus::EinkOK) {
+ for (const EinkFrame &frame : updateFrames) {
+ const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width;
+ if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) {
+ return status;
+ }
+ }
+
+ if (const auto status = refreshDisplay(refreshFrame, refreshMode); status != EinkStatus::EinkOK) {
return status;
}
+
return EinkStatus::EinkOK;
}
@@ 218,14 262,6 @@ namespace hal::eink
EinkFillScreenWithColor(EinkDisplayColorFilling_e::EinkDisplayColorWhite);
}
- EinkStatus EinkDisplay::update(std::uint8_t *displayBuffer)
- {
- return translateStatus(EinkUpdateFrame(EinkFrame_t{0, 0, size.width, size.height},
- displayBuffer,
- getCurrentBitsPerPixelFormat(),
- translateDisplayColorMode(displayMode)));
- }
-
EinkBpp_e EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept
{
if ((currentWaveform.mode == EinkWaveformA2) || (currentWaveform.mode == EinkWaveformDU2)) {
@@ 234,12 270,6 @@ namespace hal::eink
return Eink4Bpp;
}
- EinkStatus EinkDisplay::refresh(const EinkDisplayTimingsMode_e refreshMode)
- {
- currentWaveform.useCounter += 1;
- return translateStatus(EinkRefreshImage(EinkFrame_t{0, 0, size.width, size.height}, refreshMode));
- }
-
bool EinkDisplay::isNewWaveformNeeded(const EinkWaveforms_e newMode, const std::int32_t newTemperature) const
{
constexpr auto lenientTemperatureUseCounter = 50; // arbitrary. not documented
M module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp => module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp +9 -6
@@ 26,7 26,13 @@ namespace hal::eink
~EinkDisplay() noexcept;
void setMode(EinkDisplayColorMode mode) noexcept override;
- EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) override;
+ EinkStatus showImageUpdate(const std::vector<EinkFrame> &updateFrames,
+ const std::uint8_t *frameBuffer) override;
+ EinkStatus showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) override;
+ EinkStatus showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode) override;
void prepareEarlyRequest(EinkRefreshMode refreshMode, const WaveformTemperature behaviour) override;
EinkStatus resetAndInit() override;
@@ 48,11 54,8 @@ namespace hal::eink
std::int32_t getLastTemperature() const noexcept;
- EinkStatus update(std::uint8_t *displayBuffer);
- EinkStatus refresh(const EinkDisplayTimingsMode_e refreshMode);
-
- EinkStatus updateDisplay(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode);
- EinkStatus refreshDisplay(const EinkRefreshMode refreshMode);
+ EinkStatus updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer);
+ EinkStatus refreshDisplay(EinkFrame frame, const EinkRefreshMode refreshMode);
EinkStatus prepareDisplay(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour);
FrameSize size;
M module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp => module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp +1 -1
@@ 22,7 22,7 @@ namespace hal::eink
#if defined(EINK_ROTATE_90_CLOCKWISE)
return BOARD_EINK_DISPLAY_RES_X - frame.pos_x - frame.width;
#else
- return BOARD_EINK_DISPLAY_RES_Y - frame.pos_y - frame.height;
+ return frame.pos_y;
#endif
}
M module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp => module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp +11 -2
@@ 1,7 1,10 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+#pragma once
+
#include <memory>
+#include <vector>
#include <devices/Device.hpp>
@@ 65,8 68,14 @@ namespace hal::eink
virtual ~AbstractEinkDisplay() = default;
- virtual void setMode(const EinkDisplayColorMode mode) noexcept = 0;
- virtual EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) = 0;
+ virtual void setMode(const EinkDisplayColorMode mode) noexcept = 0;
+ virtual EinkStatus showImageUpdate(const std::vector<EinkFrame> &updateFrames,
+ const std::uint8_t *frameBuffer) = 0;
+ virtual EinkStatus showImageRefresh(const EinkFrame &refreshFrame, const EinkRefreshMode refreshMode) = 0;
+ virtual EinkStatus showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode) = 0;
virtual void prepareEarlyRequest(EinkRefreshMode refreshMode, const WaveformTemperature behaviour) = 0;
virtual void dither() = 0;
M module-gui/gui/core/BoundingBox.cpp => module-gui/gui/core/BoundingBox.cpp +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "BoundingBox.hpp"
@@ 105,7 105,7 @@ namespace gui
return ss.str();
}
- void BoundingBox::sum(const BoundingBox &box)
+ void BoundingBox::expandSize(const BoundingBox &box)
{
w = box.w > w ? box.w : w;
h = box.h > h ? box.h : h;
M module-gui/gui/core/BoundingBox.hpp => module-gui/gui/core/BoundingBox.hpp +6 -5
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 31,8 31,8 @@ namespace gui
Position x = zero_position, y = zero_position;
Length w = zero_size, h = zero_size;
};
- BoundingBox(Position x = zero_position, Position y = zero_position, Length w = 0, Length h = 0);
- virtual ~BoundingBox() = default;
+ BoundingBox() = default;
+ BoundingBox(Position x, Position y, Length w, Length h);
static bool intersect(const BoundingBox &box1, const BoundingBox &box2, BoundingBox &result);
@@ 43,8 43,9 @@ namespace gui
/// get position in axis - in x get x, in y get y
Position pos(gui::Axis axis) const;
std::string str() const;
- /// logical sum of bounding box by another bounding box values
- void sum(const BoundingBox &box);
+ /// assign width and/or height of bigger bounding box
+ void expandSize(const BoundingBox &box);
+
bool operator==(const BoundingBox &box) const;
bool operator!=(const BoundingBox &box) const;
};
M module-gui/gui/core/Context.cpp => module-gui/gui/core/Context.cpp +140 -43
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/*
@@ 8,35 8,29 @@
* Author: robert
*/
-#include <ios>
-#include <cstring>
-
-#include "BoundingBox.hpp"
#include "Context.hpp"
+#include <cassert>
+#include <cstring>
+#include <ios>
+#include <vector>
+
namespace gui
{
-
- Context::Context() : x{0}, y{0}, w{0}, h{0}, data{nullptr}
- {}
-
- Context::Context(uint16_t width, uint16_t height) : x{0}, y{0}, w{width}, h{height}, data(new uint8_t[w * h])
+ Context::Context(std::uint16_t width, std::uint16_t height) : w{width}, h{height}, data(new std::uint8_t[w * h])
{
- memset(data, 15, w * h);
+ memset(data.get(), clearColor, w * h);
}
- Context::~Context()
+ Context Context::get(std::int16_t gx, std::int16_t gy, std::uint16_t width, std::uint16_t height) const
{
- if (data != nullptr)
- delete[] data;
- }
+ Context retContext = Context(width, height);
- Context *Context::get(int16_t gx, int16_t gy, uint16_t width, uint16_t height)
- {
- Context *retContext = new Context(width, height);
-
- retContext->x = gx;
- retContext->y = gy;
+ // Copy the whole block if context is fully inside and covering the whole width
+ if (gx == 0 && width == w && gy >= 0 && std::uint16_t(gy + height) <= h) {
+ memcpy(retContext.data.get(), data.get() + gy * w, w * height);
+ return retContext;
+ }
// create bounding boxes for the current context and return context
BoundingBox currentBox = BoundingBox(0, 0, w, h);
@@ 48,7 42,7 @@ namespace gui
Length sourceOffset = resultBox.y * w + resultBox.x;
Length destOffset = (resultBox.y - gy) * width + (resultBox.x - gx);
for (Length h = 0; h < resultBox.h; h++) {
- memcpy(retContext->data + destOffset, data + sourceOffset, resultBox.w);
+ memcpy(retContext.data.get() + destOffset, data.get() + sourceOffset, resultBox.w);
sourceOffset += w;
destOffset += width;
}
@@ 58,27 52,38 @@ namespace gui
return retContext;
}
- void Context::insert(int16_t ix, int16_t iy, Context *context)
+ void Context::insert(std::int16_t ix, std::int16_t iy, const Context &context)
{
+ // Copy the whole block if context is fully inside and covering the whole width
+ if (ix == 0 && context.w == w && iy >= 0 && std::uint16_t(iy + context.h) <= h) {
+ memcpy(data.get() + iy * w, context.data.get(), w * context.h);
+ return;
+ }
+
// create bounding boxes for the current context and return context
BoundingBox currentBox = BoundingBox(0, 0, w, h);
- BoundingBox insertBox = BoundingBox(ix, iy, context->w, context->h);
+ BoundingBox insertBox = BoundingBox(ix, iy, context.w, context.h);
BoundingBox resultBox;
// if boxes overlap copy part defined by result from current context to the new context.
if (BoundingBox::intersect(currentBox, insertBox, resultBox)) {
- Length sourceOffset = (resultBox.y - iy) * context->w + (resultBox.x - ix);
+ Length sourceOffset = (resultBox.y - iy) * context.w + (resultBox.x - ix);
Length destOffset = (resultBox.y) * w + (resultBox.x);
for (Length h = 0; h < resultBox.h; h++) {
- memcpy(data + destOffset, context->data + sourceOffset, resultBox.w);
- sourceOffset += context->w;
+ memcpy(data.get() + destOffset, context.data.get() + sourceOffset, resultBox.w);
+ sourceOffset += context.w;
destOffset += w;
}
}
}
- void Context::insertArea(
- int16_t ix, int16_t iy, int16_t iareaX, int16_t iareaY, int16_t iareaW, int16_t iareaH, Context *context)
+ void Context::insertArea(std::int16_t ix,
+ std::int16_t iy,
+ std::int16_t iareaX,
+ std::int16_t iareaY,
+ std::int16_t iareaW,
+ std::int16_t iareaH,
+ const Context &context)
{
// create bounding boxes for the current context and return context
BoundingBox currentBox = BoundingBox(0, 0, w, h);
@@ 87,39 92,98 @@ namespace gui
// if boxes overlap copy part defined by result from current context to the new context.
if (BoundingBox::intersect(currentBox, insertBox, resultBox)) {
- int16_t xBoxOffset = 0;
- int16_t yBoxOffset = 0;
+ std::int16_t xBoxOffset = 0;
+ std::int16_t yBoxOffset = 0;
if (iareaX < 0)
xBoxOffset = iareaX;
if (iareaY < 0)
yBoxOffset = iareaY;
- Length sourceOffset = (resultBox.y - iy - yBoxOffset) * context->w + (resultBox.x - ix - xBoxOffset);
+ Length sourceOffset = (resultBox.y - iy - yBoxOffset) * context.w + (resultBox.x - ix - xBoxOffset);
Length destOffset = (resultBox.y) * w + (resultBox.x);
for (Length h = 0; h < resultBox.h; h++) {
- memcpy(data + destOffset, context->data + sourceOffset, resultBox.w);
- sourceOffset += context->w;
+ memcpy(data.get() + destOffset, context.data.get() + sourceOffset, resultBox.w);
+ sourceOffset += context.w;
destOffset += w;
}
}
}
- void Context::fill(uint8_t colour)
+ struct LRange
+ {
+ LRange(std::uint16_t begin, std::uint16_t end) : begin(begin), end(end)
+ {}
+ void expand(LRange const &other)
+ {
+ begin = std::min(begin, other.begin);
+ end = std::max(end, other.end);
+ }
+ static LRange inversed(std::uint16_t end)
+ {
+ return {end, 0};
+ }
+ std::uint16_t begin, end;
+ };
+
+ inline BoundingBox makeBoundingBox(const LRange &rangeX, const LRange &rangeY)
+ {
+ return {rangeX.begin,
+ rangeY.begin,
+ std::uint16_t(rangeX.end - rangeX.begin),
+ std::uint16_t(rangeY.end - rangeY.begin)};
+ }
+
+ // Currently the algorithm only works properly for width divisible by 8 due to the use of 64b integers.
+ std::deque<BoundingBox> gui::Context::linesDiffs(const gui::Context &ctx1, const gui::Context &ctx2)
+ {
+ using casted_t = std::uint64_t;
+ const std::uint16_t w = ctx1.getW();
+ const std::uint16_t h = ctx1.getH();
+ assert(w == ctx2.getW() && h == ctx2.getH() && w % 8 == 0);
+ const std::uint16_t cw = w / sizeof(casted_t);
+ const auto data1 = reinterpret_cast<const casted_t *>(ctx1.getData());
+ const auto data2 = reinterpret_cast<const casted_t *>(ctx2.getData());
+
+ std::deque<BoundingBox> result;
+ LRange rangeY = LRange::inversed(h);
+ for (std::uint16_t y = 0; y < h; ++y) {
+ const auto begin1 = data1 + y * cw;
+ const auto end1 = begin1 + cw;
+ const auto begin2 = data2 + y * cw;
+ if (std::mismatch(begin1, end1, begin2).first != end1) {
+ if (rangeY.begin == h) { // diff pixels found first time
+ rangeY.begin = y;
+ }
+ }
+ else {
+ if (rangeY.begin != h) { // diff pixels found before
+ rangeY.end = y;
+ result.push_back(makeBoundingBox({0, w}, rangeY));
+ rangeY = LRange::inversed(h);
+ }
+ }
+ }
+ if (rangeY.begin != h) { // diff pixels found before
+ rangeY.end = h;
+ result.push_back(makeBoundingBox({0, w}, rangeY));
+ }
+ return result;
+ }
+
+ void Context::fill(std::uint8_t colour)
{
if (data) {
- memset(data, colour, w * h);
+ memset(data.get(), colour, w * h);
}
- // uint32_t size = 480*600;
- // memset( data, colour, size );
}
std::ostream &operator<<(std::ostream &out, const Context &c)
{
- out << "x:" << c.x << "y:" << c.y << "w:" << c.w << "h:" << c.h << std::endl;
+ out << "w:" << c.w << "h:" << c.h << std::endl;
- uint32_t offset = 0;
- for (uint32_t y = 0; y < c.h; y++) {
- for (uint32_t x = 0; x < c.w; x++) {
- uint32_t value = *(c.data + offset);
+ std::uint32_t offset = 0;
+ for (std::uint32_t y = 0; y < c.h; y++) {
+ for (std::uint32_t x = 0; x < c.w; x++) {
+ std::uint32_t value = *(c.data.get() + offset);
std::cout << std::hex << value;
offset++;
}
@@ 128,4 192,37 @@ namespace gui
return out;
}
+ std::string Context::toAsciiScaled(std::uint16_t scale) const
+ {
+ scale = std::max(std::uint16_t(1), std::min(scale, std::min(w, h)));
+
+ const std::uint16_t pixelsPerChar = scale * scale;
+ const std::uint16_t sw = w / scale;
+ const std::uint16_t sh = h / scale;
+ const std::uint8_t white = 15;
+ const char *chars = " .,-\"*^:;+=!?%#@";
+
+ std::vector<std::uint16_t> accum(sw * sh, 0);
+ for (std::uint16_t j = 0; j < h; ++j) {
+ for (std::uint16_t i = 0; i < w; ++i) {
+ const std::uint8_t c = std::min(data[j * w + i], white);
+ const auto off = (j / scale) * sw + (i / scale);
+ accum[off] += c;
+ }
+ }
+
+ const std::uint16_t sw_nl = sw + 1;
+ std::string result(sw_nl * sh - 1, '\n'); // last new line is not needed
+
+ for (std::uint16_t j = 0; j < sh; ++j) {
+ for (std::uint16_t i = 0; i < sw; ++i) {
+ const auto off_nl = j * sw_nl + i;
+ const auto off = j * sw + i;
+ result[off_nl] = chars[accum[off] / pixelsPerChar];
+ }
+ }
+
+ return result;
+ }
+
} /* namespace gui */
M module-gui/gui/core/Context.hpp => module-gui/gui/core/Context.hpp +53 -36
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/*
@@ 11,83 11,100 @@
#ifndef GUI_CORE_CONTEXT_HPP_
#define GUI_CORE_CONTEXT_HPP_
+#include "BoundingBox.hpp"
+
+#include "module-gui/gui/Common.hpp"
+
#include <cstdint>
+#include <deque>
#include <iostream>
-#include "module-gui/gui/Common.hpp"
+#include <memory>
+#include <string>
namespace gui
{
-
class Context
{
- protected:
- int16_t x, y;
- uint32_t w, h;
- uint8_t *data;
+ static constexpr std::uint8_t clearColor = 15;
public:
- Context();
- Context(uint16_t width, uint16_t height);
- virtual ~Context();
+ Context() = default;
+ Context(std::uint16_t width, std::uint16_t height);
/**
- * @brief Creates new context using provided coordinates. If there is no common part Context with 0 width and 0
- * height is returned.
+ * @brief Creates new context using provided coordinates. If there is no common part Context filled with
+ * clearColor is returned. The size of the returned context is defined by parameters, so it may represent
+ * area outside the original context.
*/
- Context *get(int16_t gx, int16_t gy, uint16_t width, uint16_t height);
+ Context get(std::int16_t gx, std::int16_t gy, std::uint16_t width, std::uint16_t height) const;
/**
* @brief Pastes provided context into current one. Overlapping content will be inserted into current context.
*/
- void insert(int16_t ix, int16_t iy, Context *context);
+ void insert(std::int16_t ix, std::int16_t iy, const Context &context);
/**
* @brief Pastes provided context into current one. Overlapping content will be inserted into current context.
*/
- void insertArea(
- int16_t ix, int16_t iy, int16_t iareaX, int16_t iareaY, int16_t iareaW, int16_t iareaH, Context *context);
+ void insertArea(std::int16_t ix,
+ std::int16_t iy,
+ std::int16_t iareaX,
+ std::int16_t iareaY,
+ std::int16_t iareaW,
+ std::int16_t iareaH,
+ const Context &context);
+
+ /**
+ * @brief Calculate regions of difference between contexts. Each bounding box covers the whole width of the
+ * context. They are disjoint and sorted by y coordinate. The contexts has to have the same sizes.
+ */
+ static std::deque<BoundingBox> linesDiffs(const gui::Context &ctx1, const gui::Context &ctx2);
+
/**
* @brief Fills whole context with specified colour;
*/
- void fill(uint8_t colour);
+ void fill(std::uint8_t colour);
/**
* @brief returns pointer to context's data;
*/
- inline const uint8_t *getData() const
+ inline const std::uint8_t *getData() const
{
- return data;
+ return data.get();
}
- inline uint8_t *getData()
+ inline std::uint8_t *getData()
{
- return data;
+ return data.get();
}
- inline int16_t getX() const
- {
- return x;
- }
- inline int16_t getY() const
- {
- return y;
- }
- inline uint16_t getW() const
+ inline std::uint16_t getW() const
{
return w;
}
- inline uint16_t getH() const
+ inline std::uint16_t getH() const
{
return h;
}
+ inline BoundingBox getBoundingBox() const
+ {
+ return {0, 0, w, h};
+ }
- inline bool addressInData(const uint8_t *ptr) const
+ inline std::uint8_t getPixel(const Point point, uint8_t defaultColor = clearColor) const
{
- return (ptr >= data) && (ptr < data + w * h);
+ return hasPixel(point) ? data[point.y * w + point.x] : defaultColor;
}
- inline bool addressInData(const Point point) const noexcept
+ inline bool hasPixel(const Point point) const noexcept
{
- return (point.x >= 0 && static_cast<uint32_t>(point.x) <= w) &&
- (point.y >= 0 && static_cast<uint32_t>(point.y) <= h);
+ return (point.x >= 0 && static_cast<std::uint32_t>(point.x) < w) &&
+ (point.y >= 0 && static_cast<std::uint32_t>(point.y) < h);
}
friend std::ostream &operator<<(std::ostream &out, const Context &c);
+
+ std::string toAsciiScaled(std::uint16_t scale = 15) const;
+
+ private:
+ std::uint16_t w = 0;
+ std::uint16_t h = 0;
+ std::unique_ptr<uint8_t[]> data;
};
} /* namespace gui */
M module-gui/gui/core/DrawCommand.cpp => module-gui/gui/core/DrawCommand.cpp +15 -18
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "DrawCommand.hpp"
@@ 56,8 56,9 @@ namespace gui
return;
}
+ Context tempContext;
Context *drawingContext = ctx;
- Point position;
+ Point position(0, 0);
if (yaps & (RectangleYap::BottomLeft | RectangleYap::TopLeft)) {
position.x += yapSize;
@@ 76,7 77,8 @@ namespace gui
else {
const auto xCtx = areaX < 0 ? origin.x + areaX : origin.x;
const auto yCtx = areaY < 0 ? origin.y + areaY : origin.y;
- drawingContext = ctx->get(xCtx, yCtx, areaW, areaH);
+ tempContext = ctx->get(xCtx, yCtx, areaW, areaH);
+ drawingContext = &tempContext;
}
if (radius == 0) {
@@ 89,8 91,7 @@ namespace gui
}
if (drawingContext != ctx) {
- ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, drawingContext);
- delete drawingContext;
+ ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, tempContext);
}
}
@@ 116,7 117,7 @@ namespace gui
for (position.y = glyphOrigin.y - glyph->yoffset; position.y < glyphMaxY; ++position.y) {
for (position.x = glyphOrigin.x; position.x < glyphMaxX; ++position.x) {
- if (!ctx->addressInData(position)) {
+ if (!ctx->hasPixel(position)) {
log_warn_glyph(
"drawing out of: {x=%d,y=%d} vs {w=%d,h=%d}", position.x, position.y, ctx->getW(), ctx->getH());
return;
@@ 138,18 139,17 @@ namespace gui
}
// get copy of original context using x,y of draw coordinates and original size of the widget
- Context *drawCtx;
+ Context ctxCopy;
bool copyContext = false;
Point widgetOrigin = {0, 0};
// check if there is a need of making copy of context to use it as background
if ((areaW == width) && (areaH == height)) {
- drawCtx = ctx;
widgetOrigin = origin;
}
else {
copyContext = true;
- drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
+ ctxCopy = ctx->get(origin.x, origin.y, areaW, areaH);
}
// retrieve font used to draw text
@@ 164,6 164,7 @@ namespace gui
FontGlyph *glyph = font->getGlyph(idCurrent);
// do not start drawing outside of draw context.
+ Context *drawCtx = copyContext ? &ctxCopy : ctx;
if ((widgetOrigin.x + position.x + glyph->xoffset >= drawCtx->getW()) ||
(widgetOrigin.x + position.x + glyph->xoffset < 0)) {
LOG_FATAL("Drawing outside context's X boundary for glyph: %" PRIu32, glyph->id);
@@ 188,10 189,9 @@ namespace gui
}
// if drawing was performed in temporary context
- // reinsert drawCtx into bast context
+ // reinsert drawCtx into base context
if (copyContext) {
- ctx->insert(origin.x, origin.y, drawCtx);
- delete drawCtx;
+ ctx->insert(origin.x, origin.y, ctxCopy);
}
}
@@ 269,23 269,20 @@ namespace gui
}
// get copy of original context using x,y of draw coordinates and original size of the widget
- Context *drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
+ Context drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
if (imageMap->getType() == gui::ImageMap::Type::PIXMAP) {
auto pixMap = dynamic_cast<PixMap *>(imageMap);
assert(pixMap);
- drawPixMap(drawCtx, pixMap);
+ drawPixMap(&drawCtx, pixMap);
}
else if (imageMap->getType() == gui::ImageMap::Type::VECMAP) {
auto vecMap = dynamic_cast<VecMap *>(imageMap);
assert(vecMap);
- drawVecMap(drawCtx, vecMap);
+ drawVecMap(&drawCtx, vecMap);
}
// reinsert drawCtx into bast context
ctx->insert(origin.x, origin.y, drawCtx);
-
- // remove draw context
- delete drawCtx;
}
} /* namespace gui */
M module-gui/gui/core/ImageManager.cpp => module-gui/gui/core/ImageManager.cpp +22 -26
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ImageManager.hpp"
@@ 176,31 176,27 @@ namespace gui
// Creation of square with crossed lines as fallback image
constexpr auto squareWidth = 15;
- std::list<std::unique_ptr<gui::DrawCommand>> commands;
- auto rectangle = std::make_unique<DrawRectangle>();
- rectangle->origin = {0, 0};
- rectangle->width = squareWidth;
- rectangle->height = squareWidth;
- rectangle->areaX = 0;
- rectangle->areaY = 0;
- rectangle->areaW = squareWidth;
- rectangle->areaH = squareWidth;
- commands.emplace_back(std::move(rectangle));
-
- auto line1 = std::make_unique<DrawLine>();
- line1->start = {0, 0};
- line1->end = {squareWidth, squareWidth};
- commands.emplace_back(std::move(line1));
-
- auto line2 = std::make_unique<DrawLine>();
- line2->start = {squareWidth - 1, 0};
- line2->end = {0, squareWidth - 1};
- commands.emplace_back(std::move(line2));
-
- auto renderContext = std::make_unique<Context>(squareWidth, squareWidth);
- Renderer().render(renderContext.get(), commands);
-
- return new PixMap(squareWidth, squareWidth, renderContext->getData());
+ DrawRectangle rectangle;
+ rectangle.origin = {0, 0};
+ rectangle.width = squareWidth;
+ rectangle.height = squareWidth;
+ rectangle.areaX = 0;
+ rectangle.areaY = 0;
+ rectangle.areaW = squareWidth;
+ rectangle.areaH = squareWidth;
+
+ DrawLine line1;
+ line1.start = {0, 0};
+ line1.end = {squareWidth, squareWidth};
+
+ DrawLine line2;
+ line2.start = {squareWidth - 1, 0};
+ line2.end = {0, squareWidth - 1};
+
+ Context renderContext(squareWidth, squareWidth);
+ Renderer().render(renderContext, rectangle, line1, line2);
+
+ return new PixMap(squareWidth, squareWidth, renderContext.getData());
}
auto ImageManager::getImageMapList(std::string ext1, std::string ext2)
M module-gui/gui/core/RawFont.cpp => module-gui/gui/core/RawFont.cpp +13 -15
@@ 239,24 239,22 @@ namespace gui
unsupported->xadvance =
unsupported->width + (2 * unsupported->xoffset); // use xoffset as margins on the left/right of the glyph
// populate with a bitmap (glyph)
- auto commandRect = std::make_unique<DrawRectangle>();
- commandRect->origin = {0, 0};
- commandRect->width = unsupported->width;
- commandRect->height = unsupported->height;
- commandRect->areaX = 0;
- commandRect->areaY = 0;
- commandRect->areaW = unsupported->width;
- commandRect->areaH = unsupported->height;
- commandRect->penWidth = unsupported->xoffset;
-
- auto renderCtx = std::make_unique<Context>(unsupported->width, unsupported->height);
- std::list<std::unique_ptr<gui::DrawCommand>> commands;
- commands.emplace_back(std::move(commandRect));
- Renderer().render(renderCtx.get(), commands);
+ DrawRectangle commandRect;
+ commandRect.origin = {0, 0};
+ commandRect.width = unsupported->width;
+ commandRect.height = unsupported->height;
+ commandRect.areaX = 0;
+ commandRect.areaY = 0;
+ commandRect.areaW = unsupported->width;
+ commandRect.areaH = unsupported->height;
+ commandRect.penWidth = unsupported->xoffset;
+
+ Context renderCtx(unsupported->width, unsupported->height);
+ Renderer().render(renderCtx, commandRect);
auto size = unsupported->width * unsupported->height;
unsupported->data = new uint8_t[size];
- std::memcpy(unsupported->data, renderCtx->getData(), size);
+ std::memcpy(unsupported->data, renderCtx.getData(), size);
}
void RawFont::setFallbackFont(RawFont *fallback)
M module-gui/gui/core/Renderer.cpp => module-gui/gui/core/Renderer.cpp +3 -3
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
// for memset
@@ 10,12 10,12 @@
namespace gui
{
- void Renderer::changeColorScheme(const std::unique_ptr<ColorScheme> &scheme)
+ void Renderer::changeColorScheme(const std::unique_ptr<ColorScheme> &scheme) const
{
renderer::PixelRenderer::updateColorScheme(scheme);
}
- void Renderer::render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands)
+ void Renderer::render(Context *ctx, const std::list<std::unique_ptr<DrawCommand>> &commands) const
{
if (ctx == nullptr) {
return;
M module-gui/gui/core/Renderer.hpp => module-gui/gui/core/Renderer.hpp +9 -5
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 34,10 34,14 @@ namespace gui
*/
public:
- virtual ~Renderer() = default;
-
- void render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands);
- void changeColorScheme(const std::unique_ptr<ColorScheme> &scheme);
+ void changeColorScheme(const std::unique_ptr<ColorScheme> &scheme) const;
+ void render(Context *ctx, const std::list<std::unique_ptr<DrawCommand>> &commands) const;
+
+ template <typename... Commands>
+ void render(Context &ctx, const Commands &...commands) const
+ {
+ (..., commands.draw(&ctx));
+ }
};
} /* namespace gui */
M module-gui/gui/core/renderers/RectangleRenderer.cpp => module-gui/gui/core/renderers/RectangleRenderer.cpp +2 -15
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "RectangleRenderer.hpp"
@@ 13,19 13,6 @@
namespace gui::renderer
{
- namespace
- {
- std::uint8_t getPixelColor(Context *ctx, const Point &point, std::uint8_t defaultColor)
- {
- const auto contextWidth = ctx->getW();
- const auto position = point.y * contextWidth + point.x;
- if (!ctx->addressInData(ctx->getData() + position)) {
- return defaultColor;
- }
- return *(ctx->getData() + position);
- }
- } // namespace
-
auto RectangleRenderer::DrawableStyle::from(const DrawRectangle &command) -> DrawableStyle
{
return DrawableStyle{command.penWidth,
@@ 183,7 170,7 @@ namespace gui::renderer
while (!q.empty()) {
const auto currPoint = q.front();
q.pop();
- if (const auto color = getPixelColor(ctx, currPoint, PixelRenderer::getColor(borderColor.intensity));
+ if (const auto color = ctx->getPixel(currPoint, PixelRenderer::getColor(borderColor.intensity));
color == PixelRenderer::getColor(borderColor.intensity) ||
color == PixelRenderer::getColor(fillColor.intensity)) {
continue;
M module-gui/gui/widgets/Item.cpp => module-gui/gui/widgets/Item.cpp +1 -1
@@ 136,7 136,7 @@ namespace gui
{
BoundingBox oldArea = widgetArea;
widgetArea = area;
- widgetMaximumArea.sum(widgetArea);
+ widgetMaximumArea.expandSize(widgetArea);
contentChanged = false;
updateDrawArea();
onDimensionChanged(oldArea, widgetArea);
M module-gui/gui/widgets/Rect.cpp => module-gui/gui/widgets/Rect.cpp +1 -1
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/*
M module-gui/test/test-catch/test-context.cpp => module-gui/test/test-catch/test-context.cpp +37 -51
@@ 14,77 14,63 @@
TEST_CASE("test context size and position")
{
- auto ctx = new gui::Context(30, 30);
- ctx->fill(0);
+ auto ctx = gui::Context(30, 30);
+ ctx.fill(0);
- gui::Context *test = ctx->get(17, 17, 30, 30);
+ gui::Context test = ctx.get(17, 17, 30, 30);
- delete test;
- test = ctx->get(-17, -17, 30, 30);
- delete test;
-
- delete ctx;
+ test = ctx.get(-17, -17, 30, 30);
}
TEST_CASE("insertContextTest")
{
- gui::Context *dstCtx;
- gui::Context *insCtx;
+ gui::Context dstCtx;
+ gui::Context insCtx;
SECTION("RECTANGLE INSIDE")
{
- dstCtx = new gui::Context(30, 30);
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(28), static_cast<unsigned short>(28));
- insCtx->fill(15);
- dstCtx->insert(1, 1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(28, 28);
+ insCtx.fill(15);
+ dstCtx.insert(1, 1, insCtx);
}
SECTION("2 COLUMNS ON RIGHT SIDE, TOP AND BOTTOM ROW UNTOUCHED")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(28), static_cast<unsigned short>(28));
- insCtx->fill(15);
- dstCtx->insert(28, 1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(28, 28);
+ insCtx.fill(15);
+ dstCtx.insert(28, 1, insCtx);
}
SECTION("2 COLUMNS ON LEFT SIDE, TOP AND BOTTOM ROW UNTOUCHED")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(28), static_cast<unsigned short>(28));
- insCtx->fill(15);
- dstCtx->insert(-26, 1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(28, 28);
+ insCtx.fill(15);
+ dstCtx.insert(-26, 1, insCtx);
}
SECTION("2 COLUMNS ON RIGHT SIDE")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(32), static_cast<unsigned short>(32));
- insCtx->fill(15);
- dstCtx->insert(28, -1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(32, 32);
+ insCtx.fill(15);
+ dstCtx.insert(28, -1, insCtx);
}
SECTION("2 COLUMNS ON LEFT SIDE")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(32), static_cast<unsigned short>(32));
- insCtx->fill(15);
- dstCtx->insert(-30, -1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(32, 32);
+ insCtx.fill(15);
+ dstCtx.insert(-30, -1, insCtx);
}
}
@@ 124,7 110,7 @@ TEST_CASE("Draw vector image test")
"ffffffffffffffffffffffffffffffff\n"
"ffffffffffffffffffffffffffffffff\n";
- auto context = std::make_unique<gui::Context>(static_cast<unsigned short>(32), static_cast<unsigned short>(32));
+ auto context = gui::Context(32, 32);
gui::ImageManager::getInstance().init(".");
auto id = gui::ImageManager::getInstance().getImageMapID("plus_32px_W_M");
@@ 138,13 124,13 @@ TEST_CASE("Draw vector image test")
drawCommand.imageID = id;
drawCommand.areaH = 32;
drawCommand.areaW = 32;
- drawCommand.draw(context.get());
+ drawCommand.draw(&context);
std::string dump;
- uint32_t offset = 0;
- for (uint32_t y = 0; y < context->getH(); y++) {
- for (uint32_t x = 0; x < context->getW(); x++) {
- uint32_t value = *(context->getData() + offset);
+ std::uint32_t offset = 0;
+ for (std::uint32_t y = 0; y < context.getH(); y++) {
+ for (std::uint32_t x = 0; x < context.getW(); x++) {
+ std::uint32_t value = *(context.getData() + offset);
std::stringstream stream;
stream << std::hex << value;
dump.append(stream.str());
M module-services/service-eink/ServiceEink.cpp => module-services/service-eink/ServiceEink.cpp +244 -6
@@ 22,6 22,8 @@
#include <gsl/util>
#include "Utils.hpp"
+#include <service-gui/Common.hpp>
+
namespace service::eink
{
namespace
@@ 67,6 69,12 @@ namespace service::eink
connect(typeid(ImageMessage),
[this](sys::Message *request) -> sys::MessagePointer { return handleImageMessage(request); });
+ connect(typeid(RefreshMessage),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleRefreshMessage(request); });
+
+ connect(typeid(CancelRefreshMessage),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleCancelRefreshMessage(request); });
+
connect(typeid(PrepareDisplayEarlyRequest),
[this](sys::Message *request) -> sys::MessagePointer { return handlePrepareEarlyRequest(request); });
@@ 175,27 183,257 @@ namespace service::eink
display->setMode(hal::eink::EinkDisplayColorMode::EinkDisplayColorModeStandard);
}
internal::StaticData::get().setInvertedMode(invertedModeRequested);
+
+ previousContext.reset();
+ previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ }
+
+#if DEBUG_EINK_REFRESH == 1
+ inline std::string debug_toString(const gui::BoundingBox &bb)
+ {
+ return bb.str();
+ }
+
+ inline std::string debug_toString(const hal::eink::EinkFrame &f)
+ {
+ std::stringstream ss;
+ ss << '{' << f.pos_x << ' ' << f.pos_y << ' ' << f.size.width << ' ' << f.size.height << "}";
+ return ss.str();
+ }
+
+ template <typename Container>
+ inline void debug_handleImageMessage(const char *name, const Container &container)
+ {
+ std::stringstream ss;
+ for (auto const &el : container)
+ ss << debug_toString(el) << ' ';
+ LOG_INFO("%s: %s", name, ss.str().c_str());
+ }
+#endif
+
+ // Merge boxes if the gap between them is smaller than the threshold
+ template <typename BoxesContainer>
+ inline auto mergeBoundingBoxes(const BoxesContainer &boxes, std::uint16_t gapThreshold)
+ {
+ std::vector<gui::BoundingBox> mergedBoxes;
+ if (boxes.empty())
+ return mergedBoxes;
+ mergedBoxes.reserve(boxes.size());
+ gui::BoundingBox merged = boxes.front();
+ for (std::size_t i = 1; i < boxes.size(); ++i) {
+ const auto &bb = boxes[i];
+ const auto gap = bb.y - (merged.y + merged.h);
+ if (gap < gapThreshold) {
+ merged.h = (bb.y + bb.h) - merged.y;
+ }
+ else {
+ mergedBoxes.push_back(merged);
+ merged = bb;
+ }
+ }
+ mergedBoxes.push_back(merged);
+ return mergedBoxes;
}
+ // Enlarge each box to match alignment-wide grid in y coordinate
+ template <typename BoxesContainer>
+ inline auto makeAlignedFrames(const BoxesContainer &boxes, std::uint16_t alignment)
+ {
+ std::vector<hal::eink::EinkFrame> frames;
+ if (boxes.empty())
+ return frames;
+ frames.reserve(boxes.size());
+ auto a = alignment;
+ for (const auto &bb : boxes) {
+ auto f = hal::eink::EinkFrame{
+ std::uint16_t(bb.x), std::uint16_t(bb.y), {std::uint16_t(bb.w), std::uint16_t(bb.h)}};
+ auto y = f.pos_y;
+ auto h = f.size.height;
+ f.pos_y = y / a * a;
+ f.size.height = (y - f.pos_y + h + (a - 1)) / a * a;
+ frames.push_back(f);
+ }
+ return frames;
+ }
+
+ // Return frames representing difference of contexts
+ inline auto calculateUpdateFrames(const gui::Context &context, const gui::Context &previousContext)
+ {
+ std::vector<hal::eink::EinkFrame> updateFrames;
+ // Each bounding box cover the whole width of the context. They are disjoint and sorted by y coordinate.
+ const auto diffBoundingBoxes = gui::Context::linesDiffs(context, previousContext);
+ if (!diffBoundingBoxes.empty()) {
+ const std::uint16_t gapThreshold = context.getH() / 4;
+ const std::uint16_t alignment = 8;
+
+ const auto mergedBoxes = mergeBoundingBoxes(diffBoundingBoxes, gapThreshold);
+ updateFrames = makeAlignedFrames(mergedBoxes, alignment);
+
+#if DEBUG_EINK_REFRESH == 1
+ debug_handleImageMessage("Diff boxes", diffBoundingBoxes);
+ debug_handleImageMessage("Merged boxes", mergedBoxes);
+#endif
+ }
+ return updateFrames;
+ }
+
+ inline auto expandFrame(hal::eink::EinkFrame &frame, const hal::eink::EinkFrame &other)
+ {
+ const auto x = std::min(frame.pos_x, other.pos_x);
+ const auto y = std::min(frame.pos_y, other.pos_y);
+ const auto xmax1 = frame.pos_x + frame.size.width;
+ const auto xmax2 = other.pos_x + other.size.width;
+ const auto w = std::max(xmax1, xmax2) - x;
+ const auto ymax1 = frame.pos_y + frame.size.height;
+ const auto ymax2 = other.pos_y + other.size.height;
+ const auto h = std::max(ymax1, ymax2) - y;
+ frame.pos_x = x;
+ frame.pos_y = y;
+ frame.size.width = w;
+ frame.size.height = h;
+ }
+
+#if DEBUG_EINK_REFRESH == 1
+ TickType_t tick1, tick2, tick3;
+#endif
+
sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
{
+#if DEBUG_EINK_REFRESH == 1
+ tick1 = xTaskGetTickCount();
+#endif
+
const auto message = static_cast<service::eink::ImageMessage *>(request);
if (isInState(State::Suspended)) {
LOG_WARN("Received image while suspended, ignoring");
return sys::MessageNone{};
}
- utils::time::Scoped measurement("ImageMessage");
displayPowerOffTimer.stop();
auto displayPowerOffTimerReload = gsl::finally([this]() { displayPowerOffTimer.start(); });
- eInkSentinel->HoldMinimumFrequency();
- auto status = display->showImage(message->getData(), translateToEinkRefreshMode(message->getRefreshMode()));
- if (status != hal::eink::EinkStatus::EinkOK) {
- LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data());
+ const gui::Context &ctx = *message->getContext();
+ auto refreshMode = translateToEinkRefreshMode(message->getRefreshMode());
+
+ // Calculate update and refresh frames based on areas changed since last update
+ std::vector<hal::eink::EinkFrame> updateFrames;
+ hal::eink::EinkFrame refreshFrame{0, 0, {ctx.getW(), ctx.getH()}};
+ bool isRefreshRequired = true;
+ if (!previousContext) {
+ updateFrames = {refreshFrame};
+ previousContext.reset(new gui::Context(ctx.get(0, 0, ctx.getW(), ctx.getH())));
}
+ else {
+ updateFrames = calculateUpdateFrames(ctx, *previousContext);
+ if (refreshMode > previousRefreshMode) {
+ previousContext->insert(0, 0, ctx);
+ }
+ else if (updateFrames.empty()) {
+ isRefreshRequired = false;
+ }
+ else {
+ refreshFrame = updateFrames.front();
+ refreshFrame.size.height =
+ updateFrames.back().pos_y + updateFrames.back().size.height - updateFrames.front().pos_y;
+ previousContext->insert(0, 0, ctx);
+ }
+ }
+
+ // If parts of the screen were changed, update eink
+ bool isImageUpdated = false;
+ if (!updateFrames.empty()) {
+
+#if DEBUG_EINK_REFRESH == 1
+ debug_handleImageMessage("Update frames", updateFrames);
+ debug_handleImageMessage("Refresh frame", std::vector<hal::eink::EinkFrame>({refreshFrame}));
+#endif
+
+ eInkSentinel->HoldMinimumFrequency();
+
+ auto status = display->showImageUpdate(updateFrames, ctx.getData());
+ if (status == hal::eink::EinkStatus::EinkOK) {
+ isImageUpdated = true;
+ }
+ else {
+ previousContext.reset();
+ refreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data());
+ }
+ }
+
+ previousRefreshMode = refreshMode;
+
+#if DEBUG_EINK_REFRESH == 1
+ LOG_INFO("Update contextId: %d, mode: %d", (int)message->getContextId(), (int)refreshMode);
+ tick2 = xTaskGetTickCount();
+ LOG_INFO("Time to update: %d", (int)(tick2 - tick1));
+#endif
+
+ // Refresh is required if:
+ // - there are parts of the screen that were changed
+ // - deep refresh is requested after fast refresh even if nothing has changed
+ // - previous refresh was canceled
+ if (isImageUpdated || isRefreshRequired || isRefreshFramesSumValid) {
+ if (refreshMode > refreshModeSum) {
+ refreshModeSum = refreshMode;
+ }
+ if (!isRefreshFramesSumValid) {
+ refreshFramesSum = refreshFrame;
+ isRefreshFramesSumValid = true;
+ }
+ else {
+ expandFrame(refreshFramesSum, refreshFrame);
+ }
+ einkDisplayState = EinkDisplayState::NeedRefresh;
+ auto msg = std::make_shared<service::eink::RefreshMessage>(
+ message->getContextId(), refreshFrame, refreshMode, message->sender);
+ bus.sendUnicast(msg, this->GetName());
+ return sys::MessageNone{};
+ }
+ else {
+ einkDisplayState = EinkDisplayState::Idle;
+ return std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
+ }
+ }
- return std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
+ sys::MessagePointer ServiceEink::handleRefreshMessage(sys::Message *request)
+ {
+ const auto message = static_cast<service::eink::RefreshMessage *>(request);
+
+ if (einkDisplayState == EinkDisplayState::NeedRefresh) {
+ const auto status = display->showImageRefresh(refreshFramesSum, refreshModeSum);
+ if (status != hal::eink::EinkStatus::EinkOK) {
+ previousContext.reset();
+ previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data());
+ }
+
+ einkDisplayState = EinkDisplayState::Idle;
+ isRefreshFramesSumValid = false;
+ refreshModeSum = hal::eink::EinkRefreshMode::REFRESH_NONE;
+
+#if DEBUG_EINK_REFRESH == 1
+ LOG_INFO("Refresh contextId: %d, mode: %d", message->getContextId(), (int)message->getRefreshMode());
+#endif
+ }
+
+#if DEBUG_EINK_REFRESH == 1
+ tick3 = xTaskGetTickCount();
+ LOG_INFO("Time to refresh: %d", (int)(tick3 - tick1));
+#endif
+
+ auto msg = std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
+ bus.sendUnicast(msg, message->getOriginalSender());
+
+ return sys::MessageNone{};
+ }
+
+ sys::MessagePointer ServiceEink::handleCancelRefreshMessage(sys::Message *request)
+ {
+ LOG_INFO("Refresh cancel");
+ if (einkDisplayState == EinkDisplayState::NeedRefresh)
+ einkDisplayState = EinkDisplayState::Canceled;
+ return sys::MessageNone{};
}
sys::MessagePointer ServiceEink::handlePrepareEarlyRequest(sys::Message *message)
M module-services/service-eink/ServiceEink.hpp => module-services/service-eink/ServiceEink.hpp +17 -0
@@ 3,6 3,7 @@
#pragma once
+#include <module-gui/gui/core/Context.hpp>
#include <system/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
@@ 48,6 49,13 @@ namespace service::eink
Suspended
};
+ enum class EinkDisplayState
+ {
+ Idle,
+ NeedRefresh,
+ Canceled
+ };
+
void setState(State state) noexcept;
bool isInState(State state) const noexcept;
@@ 58,6 66,8 @@ namespace service::eink
sys::MessagePointer handleEinkModeChangedMessage(sys::Message *message);
sys::MessagePointer handleImageMessage(sys::Message *message);
+ sys::MessagePointer handleRefreshMessage(sys::Message *message);
+ sys::MessagePointer handleCancelRefreshMessage(sys::Message *message);
sys::MessagePointer handlePrepareEarlyRequest(sys::Message *message);
void initStaticData();
@@ 68,6 78,13 @@ namespace service::eink
sys::TimerHandle displayPowerOffTimer;
std::shared_ptr<EinkSentinel> eInkSentinel;
std::unique_ptr<settings::Settings> settings;
+
+ std::unique_ptr<::gui::Context> previousContext;
+ hal::eink::EinkRefreshMode previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ EinkDisplayState einkDisplayState = EinkDisplayState::Idle;
+ hal::eink::EinkFrame refreshFramesSum;
+ hal::eink::EinkRefreshMode refreshModeSum = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ bool isRefreshFramesSumValid = false;
};
} // namespace service::eink
M module-services/service-eink/messages/ImageMessage.cpp => module-services/service-eink/messages/ImageMessage.cpp +27 -2
@@ 10,9 10,9 @@ namespace service::eink
: contextId{contextId}, context{context}, refreshMode{refreshMode}
{}
- auto ImageMessage::getData() noexcept -> std::uint8_t *
+ auto ImageMessage::getContext() noexcept -> ::gui::Context *
{
- return context->getData();
+ return context;
}
auto ImageMessage::getRefreshMode() const noexcept -> ::gui::RefreshModes
@@ 32,4 32,29 @@ namespace service::eink
{
return contextId;
}
+
+ RefreshMessage::RefreshMessage(int contextId,
+ hal::eink::EinkFrame refreshFrame,
+ hal::eink::EinkRefreshMode refreshMode,
+ const std::string &originalSender)
+ : contextId(contextId), refreshFrame(refreshFrame), refreshMode(refreshMode), originalSender(originalSender)
+ {}
+
+ auto RefreshMessage::getContextId() const noexcept -> int
+ {
+ return contextId;
+ }
+ auto RefreshMessage::getRefreshFrame() noexcept -> hal::eink::EinkFrame
+ {
+ return refreshFrame;
+ }
+ auto RefreshMessage::getRefreshMode() const noexcept -> hal::eink::EinkRefreshMode
+ {
+ return refreshMode;
+ }
+ auto RefreshMessage::getOriginalSender() const noexcept -> const std::string &
+ {
+ return originalSender;
+ }
+
} // namespace service::eink
M module-services/service-eink/messages/ImageMessage.hpp => module-services/service-eink/messages/ImageMessage.hpp +29 -3
@@ 1,14 1,16 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include "EinkMessage.hpp"
-#include <cstdint>
+#include <hal/eink/AbstractEinkDisplay.hpp>
#include <module-gui/gui/core/Context.hpp>
#include <module-gui/gui/Common.hpp>
+#include <cstdint>
+
namespace service::eink
{
class ImageMessage : public EinkMessage
@@ 17,7 19,7 @@ namespace service::eink
ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode);
[[nodiscard]] auto getContextId() const noexcept -> int;
- [[nodiscard]] auto getData() noexcept -> std::uint8_t *;
+ [[nodiscard]] auto getContext() noexcept -> ::gui::Context *;
[[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;
private:
@@ 42,4 44,28 @@ namespace service::eink
private:
int contextId;
};
+
+ class RefreshMessage : public EinkMessage
+ {
+ public:
+ RefreshMessage(int contextId,
+ hal::eink::EinkFrame refreshBox,
+ hal::eink::EinkRefreshMode refreshMode,
+ const std::string &originalSender);
+
+ [[nodiscard]] auto getContextId() const noexcept -> int;
+ [[nodiscard]] auto getRefreshFrame() noexcept -> hal::eink::EinkFrame;
+ [[nodiscard]] auto getRefreshMode() const noexcept -> hal::eink::EinkRefreshMode;
+ [[nodiscard]] auto getOriginalSender() const noexcept -> const std::string &;
+
+ private:
+ int contextId;
+ hal::eink::EinkFrame refreshFrame;
+ hal::eink::EinkRefreshMode refreshMode;
+ std::string originalSender;
+ };
+
+ class CancelRefreshMessage : public EinkMessage
+ {};
+
} // namespace service::eink
M module-services/service-gui/ServiceGUI.cpp => module-services/service-gui/ServiceGUI.cpp +16 -4
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ServiceGUI.hpp"
@@ 184,11 184,18 @@ namespace service::gui
cache.invalidate();
}
const auto context = contextPool->peekContext(contextId);
+#if DEBUG_EINK_REFRESH == 1
+ LOG_INFO("Rendering finished, send, contextId: %d, mode: %d", contextId, (int)refreshMode);
+#endif
sendOnDisplay(context, contextId, refreshMode);
}
else {
cache.cache({contextId, refreshMode});
contextPool->returnContext(contextId);
+#if DEBUG_EINK_REFRESH == 1
+ LOG_INFO("Rendering finished, cancel, contextId: %d, mode: %d", contextId, (int)refreshMode);
+#endif
+ sendCancelRefresh();
}
return sys::MessageNone{};
}
@@ 196,12 203,17 @@ namespace service::gui
void ServiceGUI::sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode)
{
isDisplaying = true;
- std::shared_ptr<service::eink::ImageMessage> imageMsg;
- imageMsg = std::make_shared<service::eink::ImageMessage>(contextId, context, refreshMode);
- bus.sendUnicast(imageMsg, service::name::eink);
+ auto msg = std::make_shared<service::eink::ImageMessage>(contextId, context, refreshMode);
+ bus.sendUnicast(std::move(msg), service::name::eink);
scheduleContextRelease(contextId);
}
+ void ServiceGUI::sendCancelRefresh()
+ {
+ auto msg = std::make_shared<service::eink::CancelRefreshMessage>();
+ bus.sendUnicast(std::move(msg), service::name::eink);
+ }
+
void ServiceGUI::scheduleContextRelease(int contextId)
{
// Whenever the response from ServiceEink doesn't come, the context has to be released automatically after a
M module-services/service-gui/WorkerGUI.cpp => module-services/service-gui/WorkerGUI.cpp +8 -1
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "WorkerGUI.hpp"
@@ 9,6 9,7 @@
#include <service-gui/ServiceGUI.hpp>
#include <memory>
+#include <sstream>
#include "messages/RenderingFinished.hpp"
namespace service::gui
@@ 53,7 54,13 @@ namespace service::gui
void WorkerGUI::render(DrawCommandsQueue::CommandList &commands, ::gui::RefreshModes refreshMode)
{
const auto [contextId, context] = guiService->contextPool->borrowContext(); // Waits for the context.
+
renderer.render(context, commands);
+
+#if DEBUG_EINK_REFRESH == 1
+ LOG_INFO("Render ContextId: %d\n%s", contextId, context->toAsciiScaled().c_str());
+#endif
+
onRenderingFinished(contextId, refreshMode);
}
M module-services/service-gui/service-gui/ServiceGUI.hpp => module-services/service-gui/service-gui/ServiceGUI.hpp +1 -0
@@ 59,6 59,7 @@ namespace service::gui
void notifyRenderColorSchemeChange(::gui::ColorScheme &&scheme);
void enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item);
void sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode);
+ void sendCancelRefresh();
void scheduleContextRelease(int contextId);
bool isNextFrameReady() const noexcept;
bool isAnyFrameBeingRenderedOrDisplayed() const noexcept;
M module-utils/log/api/log/debug.hpp => module-utils/log/api/log/debug.hpp +7 -6
@@ 4,19 4,20 @@
#pragma once
#define DEBUG_APPLICATION_MANAGEMENT 0 /// show verbose logs in ApplicationManager
-#define DEBUG_SCOPED_TIMINGS 0 /// show timings in measured functions
-#define DEBUG_CELLULAR_UART 0 /// show full modem uart communication
#define DEBUG_BLUETOOTH_HCI_COMS 0 /// show communication with BT module - transactions
#define DEBUG_BLUETOOTH_HCI_BYTES 0 /// show communication with BT module - all the HCI bytes
-#define DEBUG_SERVICE_MESSAGES 0 /// show messages prior to handling in service
+#define DEBUG_CELLULAR_UART 0 /// show full modem uart communication
#define DEBUG_DB_MODEL_DATA 0 /// show messages prior to handling in service
-#define DEBUG_SIM_IMPORT_DATA 0 /// show messages connected to sim data imports
+#define DEBUG_EINK_REFRESH 0 /// show refresh information
#define DEBUG_FONT 0 /// show Font debug messages
#define DEBUG_GUI_TEXT 0 /// show basic debug messages for gui::Text - warning this can be hard on cpu
#define DEBUG_GUI_TEXT_LINES 0 /// show extended debug messages for gui::Text - lines building
#define DEBUG_GUI_TEXT_CURSOR 0 /// show extended debug messages for gui::Text - cursor handling
#define DEBUG_INPUT_EVENTS 0 /// show input events prints in system
-#define DEBUG_TIMER 0 /// debug timers system utility
+#define DEBUG_MISSING_ASSETS 0 /// show debug concerning missing assets
+#define DEBUG_SCOPED_TIMINGS 0 /// show timings in measured functions
#define DEBUG_SETTINGS_DB 0 /// show extensive settings logs for all applications
#define DEBUG_SERVICE_CELLULAR 0 /// show various logs in cellular service
-#define DEBUG_MISSING_ASSETS 0 /// show debug concerning missing assets
+#define DEBUG_SERVICE_MESSAGES 0 /// show messages prior to handling in service
+#define DEBUG_SIM_IMPORT_DATA 0 /// show messages connected to sim data imports
+#define DEBUG_TIMER 0 /// debug timers system utility
M pure_changelog.md => pure_changelog.md +1 -0
@@ 7,6 7,7 @@
* Changed USB logging
* Separated system volume from Bluetooth device volume for A2DP
* Made windows flow in SIM cards settings more robust
+* Improve refreshing of the display.
### Fixed
* Fixed wrong time displayed on password locked screen with 'Quotes' or 'Logo' wallpaper