init
This commit is contained in:
commit
2c2202e60d
2491 files changed
+1295990
No files matched your search
+27
@@ -0,0 +1,27 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/touch/SDL2TouchController.hpp>
|
||||
#include <platform/hal/simulator/sdl2/HALSDL2.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void SDL2TouchController::init()
|
||||
{
|
||||
}
|
||||
|
||||
bool SDL2TouchController::sampleTouch(int32_t& x, int32_t& y)
|
||||
{
|
||||
return static_cast<HALSDL2*>(HAL::getInstance())->doSampleTouch(x, y);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+1536
File diff suppressed because it is too large.
Load diff
+53
@@ -0,0 +1,53 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/hal/simulator/sdl2/HALSDL2.hpp>
|
||||
#include <touchgfx/hal/Types.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
uint16_t HALSDL2::icon[] = {
|
||||
0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd,
|
||||
0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x3dbd,
|
||||
0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bc, 0x45bd, 0x3dbd, 0x45bc,
|
||||
0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x45bd,
|
||||
0x3dbd, 0x45bc, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x3d9c, 0x3dbc, 0x3d9c, 0x459c, 0x3dbc, 0x3d9c, 0x459c, 0x3dbc, 0x45bd, 0x3dbd,
|
||||
0x3dbd, 0x45bd, 0x45bd, 0x461e, 0x461e, 0x461e, 0x4e1f, 0x461e, 0x461e, 0x4e1e, 0x461f, 0x461e, 0x4e1e, 0x461e, 0x461f, 0x4e1e, 0x461e, 0x461e, 0x4e1f, 0x461e, 0x461e, 0x4e1e, 0x461f, 0x461e, 0x461e, 0x461e, 0x461f, 0x461e, 0x461e, 0x45bd, 0x3dbd, 0x45bd,
|
||||
0x3dbd, 0x45bd, 0x3d7c, 0x2c16, 0x2394, 0x23b4, 0x23b5, 0x23b4, 0x23b4, 0x23b5, 0x23b4, 0x2394, 0x23b4, 0x23b4, 0x23b5, 0x23b4, 0x23b4, 0x23b5, 0x23b4, 0x23b4, 0x23b5, 0x23b4, 0x23b4, 0x23b5, 0x23b4, 0x23b4, 0x23b4, 0x2395, 0x3416, 0x3d7c, 0x3dbd, 0x45bc,
|
||||
0x3d9c, 0x461e, 0x2b93, 0x18e8, 0x52ae, 0x4a8e, 0x528e, 0x528e, 0x4a8e, 0x528e, 0x528e, 0x52ae, 0x52ae, 0x52ae, 0x4a8e, 0x528e, 0x528e, 0x4a8e, 0x528e, 0x528e, 0x528e, 0x4a8e, 0x528e, 0x528e, 0x528e, 0x528e, 0x4a8e, 0x52ae, 0x18e7, 0x2b93, 0x461e, 0x3d9c,
|
||||
0x3d9c, 0x461e, 0x1af1, 0x6b50, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6350, 0x1af1, 0x461f, 0x3d9c,
|
||||
0x3d9c, 0x463f, 0x1af2, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x5b71, 0x092a, 0x322d, 0xef9e, 0xffff, 0xffff, 0xf7df, 0x5e1d, 0x45bc, 0x765d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af2, 0x463f, 0x459c,
|
||||
0x459c, 0x463f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xb5d8, 0x00c8, 0x0088, 0x7c54, 0xffff, 0xffff, 0x9ebe, 0x2d9c, 0x359d, 0xbf3f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af1, 0x463f, 0x3d9c,
|
||||
0x3d9c, 0x461f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x42cf, 0x0087, 0x326d, 0xffff, 0xefbf, 0x45dd, 0x359d, 0x6e3d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af1, 0x461f, 0x459c,
|
||||
0x3d9c, 0x463f, 0x1af2, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xc65a, 0x0047, 0x9d57, 0xffff, 0x8e9e, 0x359c, 0x3d9d, 0xcf5f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af2, 0x463f, 0x3d9c,
|
||||
0x459c, 0x463f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x8495, 0xffdf, 0xe7bf, 0x45bd, 0x359c, 0x765e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af1, 0x463f, 0x459c,
|
||||
0x3d9c, 0x461f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x7e7d, 0x359d, 0x45bd, 0xdf9f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af1, 0x461f, 0x3d9c,
|
||||
0x3d9c, 0x463f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x5e1d, 0x359d, 0x4ddd, 0xf7ff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af2, 0x463e, 0x459c,
|
||||
0x459c, 0x463f, 0x1af2, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6dc, 0xffff, 0xc73f, 0x3d9c, 0x359c, 0x96be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af2, 0x461f, 0x3d9c,
|
||||
0x3d9c, 0x461f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xdefc, 0x0109, 0xd69b, 0xffff, 0x6e3d, 0x359d, 0x45bd, 0xe7bf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af1, 0x463f, 0x459c,
|
||||
0x3d9c, 0x4e3f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x5b71, 0x0047, 0x4b10, 0xffff, 0xd77f, 0x3dbd, 0x359d, 0x867e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af1, 0x463f, 0x3d9c,
|
||||
0x3d9c, 0x463f, 0x1af1, 0x6b70, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xc65a, 0x0109, 0x00c8, 0x6391, 0xffff, 0xffff, 0x869e, 0x359c, 0x3dbd, 0xd77f, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x6b70, 0x1af2, 0x461e, 0x459c,
|
||||
0x3d9c, 0x461f, 0x1af2, 0x6b70, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x324d, 0x0047, 0x092a, 0xe73d, 0xffff, 0xffff, 0xe7bf, 0x459c, 0x2d5c, 0x661d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0x6b50, 0x1af2, 0x463f, 0x3d9c,
|
||||
0x459c, 0x4e3f, 0x1af1, 0x7391, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69b, 0xc65a, 0xe73d, 0xffff, 0xffff, 0xffff, 0xffff, 0xefff, 0xd7df, 0xdfdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x7391, 0x1af1, 0x463e, 0x459d,
|
||||
0x3d9c, 0x461e, 0x2332, 0x31ab, 0x9d16, 0x9cf6, 0x9cf6, 0x9cf6, 0x9cf6, 0x9cf6, 0x9cf6, 0xa577, 0xad77, 0xa557, 0x94f6, 0x9cf6, 0x9cf6, 0x9cf6, 0xa516, 0xa536, 0xa516, 0x9cf6, 0x9cf6, 0x9cf6, 0x9cf6, 0x9cf6, 0x94f6, 0x9d16, 0x31cb, 0x2332, 0x461e, 0x3d9c,
|
||||
0x3d9c, 0x461e, 0x2b73, 0x08c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00a8, 0x00a8, 0x00a8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00a8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x00c8, 0x08c8, 0x00c8, 0x08a7, 0x2b73, 0x461e, 0x3d9d,
|
||||
0x3d9d, 0x45fe, 0x2b94, 0x10a7, 0x1909, 0x18e8, 0x1908, 0x10e8, 0x18e8, 0x1908, 0x10e8, 0x18e8, 0x1908, 0x18e8, 0x18e8, 0x1908, 0x1908, 0x10e8, 0x18e8, 0x1908, 0x10e8, 0x18e8, 0x1908, 0x10e8, 0x1908, 0x10e8, 0x1908, 0x1909, 0x10a7, 0x2b94, 0x45fe, 0x3d9c,
|
||||
0x45bd, 0x45dd, 0x3d7c, 0x3415, 0x2bf5, 0x3416, 0x2bf5, 0x3416, 0x33f5, 0x2c16, 0x3415, 0x2bf5, 0x2c15, 0x2c15, 0x2bf5, 0x33f5, 0x2c15, 0x33f5, 0x3415, 0x2c15, 0x2bf5, 0x3415, 0x33f5, 0x2c15, 0x33f5, 0x33f5, 0x2c16, 0x2bf5, 0x3416, 0x3d7c, 0x45dd, 0x45bd,
|
||||
0x3dbd, 0x3dbd, 0x45dd, 0x45fe, 0x461e, 0x45fe, 0x461e, 0x45fe, 0x461e, 0x45fe, 0x45fe, 0x461e, 0x45fe, 0x461e, 0x461e, 0x45fe, 0x461e, 0x45fe, 0x461e, 0x45ff, 0x461e, 0x45fe, 0x461e, 0x461e, 0x45fe, 0x461e, 0x461e, 0x45fe, 0x45fe, 0x45dd, 0x3dbd, 0x3dbd,
|
||||
0x3dbd, 0x45bc, 0x3dbd, 0x3d9c, 0x3dbd, 0x3dbd, 0x3d9d, 0x3dbc, 0x3d9d, 0x3dbd, 0x3dbd, 0x3d9d, 0x459d, 0x3dbd, 0x3d9d, 0x3dbd, 0x3d9d, 0x3dbd, 0x3d9d, 0x3d9d, 0x3dbd, 0x3d9d, 0x3d9d, 0x3dbd, 0x3d9d, 0x3d9d, 0x3dbd, 0x3dbc, 0x3d9c, 0x45bd, 0x3dbd, 0x45bc,
|
||||
0x45bd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd,
|
||||
0x3dbd, 0x3dbd, 0x45bc, 0x3dbd, 0x45bc, 0x45bc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbc, 0x45bd, 0x3dbd, 0x3dbc, 0x45bd, 0x45bc, 0x3dbc, 0x45bd, 0x45bc, 0x3dbc, 0x45bd, 0x45bc, 0x3dbd, 0x45bc, 0x45bd, 0x3dbd, 0x3dbc, 0x45bd, 0x3dbd, 0x3dbc,
|
||||
0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x3dbc, 0x3dbd, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbc, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbd, 0x3dbd, 0x3dbd, 0x3dbd, 0x3dbd, 0x45bd, 0x3dbc, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd,
|
||||
0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x45bc, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bc, 0x3dbd, 0x3dbd, 0x45bd, 0x45bd, 0x45bc, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbc, 0x45bd, 0x45bd, 0x45bc, 0x45bd, 0x3dbd, 0x45bd, 0x45bd, 0x3dbd, 0x45bd, 0x3dbd, 0x45bd, 0x3dbd
|
||||
};
|
||||
|
||||
} // namespace touchgfx
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <assert.h>
|
||||
#include <touchgfx/hal/OSWrappers.hpp>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
static SDL_mutex* s_FrameBufferLock = NULL;
|
||||
static bool framebufferLocked = false;
|
||||
|
||||
void OSWrappers::initialize()
|
||||
{
|
||||
atexit(deinitialize);
|
||||
// Setup framebuffer lock
|
||||
s_FrameBufferLock = SDL_CreateMutex();
|
||||
framebufferLocked = false;
|
||||
}
|
||||
|
||||
void OSWrappers::deinitialize()
|
||||
{
|
||||
if (s_FrameBufferLock)
|
||||
{
|
||||
SDL_DestroyMutex(s_FrameBufferLock);
|
||||
s_FrameBufferLock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void OSWrappers::takeFrameBufferSemaphore()
|
||||
{
|
||||
assert(!framebufferLocked && "Framebuffers already locked");
|
||||
framebufferLocked = true;
|
||||
SDL_LockMutex(s_FrameBufferLock);
|
||||
}
|
||||
|
||||
void OSWrappers::giveFrameBufferSemaphore()
|
||||
{
|
||||
assert(framebufferLocked && "Framebuffers not previously locked");
|
||||
framebufferLocked = false;
|
||||
SDL_UnlockMutex(s_FrameBufferLock);
|
||||
}
|
||||
|
||||
void OSWrappers::waitForVSync()
|
||||
{
|
||||
}
|
||||
|
||||
void OSWrappers::tryTakeFrameBufferSemaphore()
|
||||
{
|
||||
}
|
||||
|
||||
void OSWrappers::giveFrameBufferSemaphoreFromISR()
|
||||
{
|
||||
}
|
||||
|
||||
void OSWrappers::taskYield()
|
||||
{
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/CacheableContainer.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
CacheableContainer::CacheableContainer()
|
||||
: Container(),
|
||||
cachedBitmapId(BITMAP_INVALID),
|
||||
cachedImage(),
|
||||
isCachedMode(false),
|
||||
childWasInvalidated(false)
|
||||
{
|
||||
// Set this Container as parent for the cachedImage. This will
|
||||
// make sure the cachedImage is clipped correctly.
|
||||
cachedImage.setParent(this);
|
||||
cachedImage.setXY(0, 0);
|
||||
}
|
||||
|
||||
void CacheableContainer::setCacheBitmap(BitmapId bitmapId)
|
||||
{
|
||||
const Bitmap bitmap(bitmapId);
|
||||
|
||||
cachedBitmapId = BITMAP_INVALID;
|
||||
|
||||
/* Accept only dynamic bitmaps */
|
||||
if (!Bitmap::dynamicBitmapGetAddress(bitmapId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Retrieve the auxiliary LCD implementation attached to HA */
|
||||
LCD* auxLCD = HAL::getInstance()->getAuxiliaryLCD();
|
||||
|
||||
/* Dynamic bitmap and framebuffer should be of the same format */
|
||||
if (bitmap.getFormat() == HAL::lcd().framebufferFormat() ||
|
||||
(auxLCD && bitmap.getFormat() == auxLCD->framebufferFormat()))
|
||||
{
|
||||
cachedBitmapId = bitmapId;
|
||||
cachedImage.setBitmap(Bitmap(cachedBitmapId));
|
||||
}
|
||||
}
|
||||
|
||||
BitmapId CacheableContainer::getCacheBitmap() const
|
||||
{
|
||||
return cachedBitmapId;
|
||||
}
|
||||
|
||||
void CacheableContainer::updateCache()
|
||||
{
|
||||
updateCache(Rect());
|
||||
}
|
||||
|
||||
void CacheableContainer::updateCache(const Rect& invalidatedArea)
|
||||
{
|
||||
if (isCachedMode && (cachedBitmapId != BITMAP_INVALID))
|
||||
{
|
||||
/* will call Container::draw(invalidatedArea) to render all widgets into the dynamic bitmap */
|
||||
HAL::getInstance()->drawDrawableInDynamicBitmap(*this, cachedBitmapId, invalidatedArea);
|
||||
childWasInvalidated = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CacheableContainer::enableCachedMode(bool enable)
|
||||
{
|
||||
isCachedMode = enable;
|
||||
}
|
||||
|
||||
void CacheableContainer::invalidateRect(Rect& invalidatedArea) const
|
||||
{
|
||||
Container::invalidateRect(invalidatedArea);
|
||||
|
||||
if (isCachedMode && (cachedBitmapId != BITMAP_INVALID))
|
||||
{
|
||||
const_cast<CacheableContainer*>(this)->childWasInvalidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CacheableContainer::setSolidRect(const Rect& solidRect) const
|
||||
{
|
||||
return Bitmap::dynamicBitmapSetSolidRect(cachedBitmapId, solidRect);
|
||||
}
|
||||
|
||||
bool CacheableContainer::isChildInvalidated() const
|
||||
{
|
||||
return childWasInvalidated;
|
||||
}
|
||||
|
||||
void CacheableContainer::setupDrawChain(const Rect& invalidatedArea, Drawable** nextPreviousElement)
|
||||
{
|
||||
if (isCachedMode && (cachedBitmapId != BITMAP_INVALID) && isVisible())
|
||||
{
|
||||
// Make sure cachedImage is same size as container and position in the same coordinates
|
||||
cachedImage.setWidthHeight(getWidth(), getHeight());
|
||||
cachedImage.setupDrawChain(invalidatedArea, nextPreviousElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
Container::setupDrawChain(invalidatedArea, nextPreviousElement);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,339 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/Container.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
bool Container::contains(const Drawable& d)
|
||||
{
|
||||
bool found = false;
|
||||
Drawable* head = firstChild;
|
||||
while (head && !found)
|
||||
{
|
||||
found = (head == &d);
|
||||
head = head->nextSibling;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void Container::add(Drawable& d)
|
||||
{
|
||||
assert(&d != this && "Cannot add Drawable to self");
|
||||
assert(d.parent == 0 && "Cannot add Drawable multiple times");
|
||||
|
||||
// Initialize d to have this as parent and no sibling.
|
||||
d.parent = this;
|
||||
d.nextSibling = 0;
|
||||
|
||||
// Check if d is the first child to be added (container is empty)
|
||||
if (!firstChild)
|
||||
{
|
||||
firstChild = &d;
|
||||
}
|
||||
else
|
||||
{
|
||||
Drawable* head = firstChild;
|
||||
// Skip to end of currently added children
|
||||
while (head->nextSibling)
|
||||
{
|
||||
assert(head != &d && "Cannot add Drawable multiple times");
|
||||
head = head->nextSibling;
|
||||
}
|
||||
assert(head != &d && "Cannot add Drawable multiple times");
|
||||
// Make last child now point to d.
|
||||
head->nextSibling = &d;
|
||||
}
|
||||
}
|
||||
|
||||
void Container::remove(Drawable& d)
|
||||
{
|
||||
if (!firstChild)
|
||||
{
|
||||
// No children
|
||||
return;
|
||||
}
|
||||
|
||||
if (&d == firstChild)
|
||||
{
|
||||
// d is first child.
|
||||
d.parent = 0;
|
||||
if (!d.nextSibling)
|
||||
{
|
||||
// d was only child, so now this container is empty
|
||||
firstChild = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
firstChild = d.nextSibling;
|
||||
d.nextSibling = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Drawable* tmp = firstChild;
|
||||
|
||||
while (tmp)
|
||||
{
|
||||
if (tmp->nextSibling == &d)
|
||||
{
|
||||
tmp->nextSibling = d.nextSibling;
|
||||
d.parent = 0;
|
||||
d.nextSibling = 0;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = tmp->nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Container::removeAll()
|
||||
{
|
||||
while (firstChild)
|
||||
{
|
||||
Drawable* d = firstChild;
|
||||
firstChild = firstChild->nextSibling;
|
||||
d->parent = 0;
|
||||
d->nextSibling = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Container::unlink()
|
||||
{
|
||||
firstChild = 0;
|
||||
}
|
||||
|
||||
void Container::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
// The draw function of Container is not normally used. Containers do not per default
|
||||
// appear in the draw chain, since they are normally invisible themselves. However,
|
||||
// if someone decides to call draw on a container, at least do something useful (draw children).
|
||||
if (!isVisible() || !firstChild)
|
||||
{
|
||||
// Nothing to draw
|
||||
return;
|
||||
}
|
||||
|
||||
const Rect tmp = invalidatedArea;
|
||||
Drawable* d = firstChild;
|
||||
while (d)
|
||||
{
|
||||
if (d->isVisible())
|
||||
{
|
||||
Rect drawableRegion = tmp & d->getRect();
|
||||
if (!drawableRegion.isEmpty())
|
||||
{
|
||||
// This child has a non-empty intersection with the invalidated area.
|
||||
// Convert region to the Drawable's coordinate system and draw.
|
||||
drawableRegion.x -= d->getX();
|
||||
drawableRegion.y -= d->getY();
|
||||
d->draw(drawableRegion);
|
||||
}
|
||||
}
|
||||
d = d->nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void Container::getLastChild(int16_t x, int16_t y, Drawable** last)
|
||||
{
|
||||
// This function is used to obtain the drawable that should receive a click/drag/gesture event.
|
||||
// Find the last child (ie. the last child that was added, ie. the "front-most" drawable) covering
|
||||
// the specified coords.
|
||||
if (isTouchable())
|
||||
{
|
||||
// If the container itself is touchable, result so far is "this". Might be overridden by a child.
|
||||
*last = this;
|
||||
}
|
||||
|
||||
Drawable* d = firstChild;
|
||||
while (d)
|
||||
{
|
||||
// Iterate over children.
|
||||
if (d->isVisible() && d->getRect().intersect(x, y))
|
||||
{
|
||||
const int16_t xadj = x - d->getX();
|
||||
const int16_t yadj = y - d->getY();
|
||||
d->getLastChild(xadj, yadj, last);
|
||||
}
|
||||
d = d->nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void Container::getLastChildNear(int16_t x, int16_t y, Drawable** last, int16_t* fingerAdjustmentX, int16_t* fingerAdjustmentY)
|
||||
{
|
||||
const int fingerSize = HAL::getInstance()->getFingerSize();
|
||||
*fingerAdjustmentX = 0;
|
||||
*fingerAdjustmentY = 0;
|
||||
|
||||
*last = 0;
|
||||
Container::getLastChild(x, y, last);
|
||||
|
||||
const int fingerSizeDistance = 3; // Up to this number is not multi-sampled
|
||||
if (fingerSize > fingerSizeDistance)
|
||||
{
|
||||
const Rect meAbsRect = getAbsoluteRect();
|
||||
|
||||
uint32_t bestDistance = 0xFFFFFFFF;
|
||||
Drawable* previous = 0; // Speed up calculations if we hit the same drawable on next sample
|
||||
if (*last)
|
||||
{
|
||||
// Touched a drawable, but perhaps there is a better alternative
|
||||
previous = *last;
|
||||
const Rect absRect = (*last)->getAbsoluteRect();
|
||||
const int dx = (x + meAbsRect.x) - (absRect.x + (absRect.width / 2));
|
||||
const int dy = (y + meAbsRect.y) - (absRect.y + (absRect.height / 2));
|
||||
bestDistance = dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
const int samplePoints[2][4][2] = { { { 0, -1 }, { -1, 0 }, { 1, 0 }, { 0, 1 } }, // above, left, right, below
|
||||
{ { -1, -1 }, { 1, -1 }, { -1, 1 }, { 1, 1 } } }; // up-left, up-right, down-left and down-right
|
||||
const int maxRings = 3;
|
||||
const int numRings = MIN(maxRings, (fingerSize - 1) / fingerSizeDistance);
|
||||
for (int ring = 0; ring < numRings; ring++)
|
||||
{
|
||||
// For each 'ring' "distance" increases up to "fingerSize":
|
||||
const int distance = fingerSize * (ring + 1) / numRings;
|
||||
for (int sampleIndex = 0; sampleIndex < 4; sampleIndex++)
|
||||
{
|
||||
const int* xy = samplePoints[ring % 2][sampleIndex];
|
||||
const int16_t deltaX = xy[0] * distance;
|
||||
const int16_t deltaY = xy[1] * distance;
|
||||
if (rect.intersect(x + deltaX, y + deltaY))
|
||||
{
|
||||
Drawable* drawable = 0;
|
||||
Container::getLastChild(x + deltaX, y + deltaY, &drawable);
|
||||
if (drawable && drawable != previous)
|
||||
{
|
||||
previous = drawable;
|
||||
const Rect absRect = drawable->getAbsoluteRect();
|
||||
// Find distance to center of drawable
|
||||
const int dx = (x + meAbsRect.x) - (absRect.x + (absRect.width / 2));
|
||||
const int dy = (y + meAbsRect.y) - (absRect.y + (absRect.height / 2));
|
||||
const uint32_t dist = dx * dx + dy * dy;
|
||||
// Check if this drawable center is closer than the previous
|
||||
if (dist < bestDistance)
|
||||
{
|
||||
bestDistance = dist;
|
||||
*last = drawable;
|
||||
*fingerAdjustmentX = deltaX;
|
||||
*fingerAdjustmentY = deltaY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect Container::getSolidRect() const
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
|
||||
Rect Container::getContainedArea() const
|
||||
{
|
||||
Drawable* d = firstChild;
|
||||
Rect contained;
|
||||
while (d)
|
||||
{
|
||||
contained.expandToFit(d->getRect());
|
||||
d = d->nextSibling;
|
||||
}
|
||||
return contained;
|
||||
}
|
||||
|
||||
void Container::moveChildrenRelative(int16_t deltaX, int16_t deltaY)
|
||||
{
|
||||
Drawable* d = firstChild;
|
||||
while (d)
|
||||
{
|
||||
d->moveRelative(deltaX, deltaY);
|
||||
d = d->nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void Container::forEachChild(GenericCallback<Drawable&>* function)
|
||||
{
|
||||
Drawable* d = firstChild;
|
||||
while (d)
|
||||
{
|
||||
function->execute(*d);
|
||||
d = d->nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
void Container::insert(Drawable* previous, Drawable& d)
|
||||
{
|
||||
if (!firstChild)
|
||||
{
|
||||
// Insert as only element
|
||||
add(d);
|
||||
return;
|
||||
}
|
||||
else if (!previous)
|
||||
{
|
||||
// Insert as head element
|
||||
d.nextSibling = firstChild;
|
||||
firstChild = &d;
|
||||
d.parent = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Drawable* tmp = firstChild;
|
||||
while (tmp)
|
||||
{
|
||||
if (tmp == previous)
|
||||
{
|
||||
d.nextSibling = tmp->nextSibling;
|
||||
tmp->nextSibling = &d;
|
||||
d.parent = this;
|
||||
return;
|
||||
}
|
||||
tmp = tmp->nextSibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Container::setupDrawChain(const Rect& invalidatedArea, Drawable** nextPreviousElement)
|
||||
{
|
||||
// This function adds the children of this container to the list of drawables to draw.
|
||||
if (!isVisible())
|
||||
{
|
||||
// If this container itself is not visible, do not add anyone to draw chain.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firstChild)
|
||||
{
|
||||
// If this container is empty, do not add anyone.
|
||||
return;
|
||||
}
|
||||
Drawable* d = firstChild;
|
||||
while (d)
|
||||
{
|
||||
if (d->isVisible())
|
||||
{
|
||||
// Only drawables intersecting with the specified invalidated area will be added.
|
||||
Rect drawableRegion = invalidatedArea & d->getRect();
|
||||
if (!drawableRegion.isEmpty())
|
||||
{
|
||||
drawableRegion.x -= d->getX();
|
||||
drawableRegion.y -= d->getY();
|
||||
d->setupDrawChain(drawableRegion, nextPreviousElement);
|
||||
}
|
||||
}
|
||||
d = d->nextSibling;
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,244 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Callback.hpp>
|
||||
#include <touchgfx/containers/ListLayout.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
class AdjustElements
|
||||
{
|
||||
public:
|
||||
AdjustElements(Drawable* d = 0, Direction dir = SOUTH)
|
||||
: insertedCoord(0),
|
||||
newElementPassed(false),
|
||||
newElement(d),
|
||||
direction(dir)
|
||||
{
|
||||
}
|
||||
|
||||
void handleInsert(Drawable& d)
|
||||
{
|
||||
if (!newElementPassed)
|
||||
{
|
||||
if (newElement == &d)
|
||||
{
|
||||
newElementPassed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (direction == SOUTH)
|
||||
{
|
||||
insertedCoord = d.getY() + d.getHeight();
|
||||
}
|
||||
else if (direction == EAST)
|
||||
{
|
||||
insertedCoord = d.getX() + d.getWidth();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (direction == SOUTH)
|
||||
{
|
||||
d.setY(d.getY() + newElement->getHeight());
|
||||
}
|
||||
else if (direction == EAST)
|
||||
{
|
||||
d.setX(d.getX() + newElement->getWidth());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleRemove(Drawable& d)
|
||||
{
|
||||
if (!newElementPassed)
|
||||
{
|
||||
if (newElement == &d)
|
||||
{
|
||||
newElementPassed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (direction == SOUTH)
|
||||
{
|
||||
d.setY(d.getY() - newElement->getHeight());
|
||||
}
|
||||
else if (direction == EAST)
|
||||
{
|
||||
d.setX(d.getX() - newElement->getWidth());
|
||||
}
|
||||
}
|
||||
if (newElement != &d)
|
||||
{
|
||||
if (direction == SOUTH)
|
||||
{
|
||||
if (d.getWidth() > insertedCoord)
|
||||
{
|
||||
insertedCoord = d.getWidth();
|
||||
}
|
||||
}
|
||||
else if (direction == EAST)
|
||||
{
|
||||
if (d.getHeight() > insertedCoord)
|
||||
{
|
||||
insertedCoord = d.getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int16_t insertedCoord;
|
||||
bool newElementPassed;
|
||||
friend class ListLayout;
|
||||
Drawable* newElement;
|
||||
Direction direction;
|
||||
};
|
||||
|
||||
void ListLayout::internalAddElementAt(Drawable& d, int16_t coord)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case SOUTH:
|
||||
if (rect.width < d.getWidth())
|
||||
{
|
||||
rect.width = d.getWidth();
|
||||
}
|
||||
rect.height += d.getHeight();
|
||||
d.setXY(0, coord);
|
||||
offset += d.getHeight();
|
||||
break;
|
||||
case EAST:
|
||||
if (rect.height < d.getHeight())
|
||||
{
|
||||
rect.height = d.getHeight();
|
||||
}
|
||||
rect.width += d.getWidth();
|
||||
d.setXY(coord, 0);
|
||||
offset += d.getWidth();
|
||||
break;
|
||||
case NORTH:
|
||||
case WEST:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ListLayout::internalAddElement(Drawable& d)
|
||||
{
|
||||
internalAddElementAt(d, offset);
|
||||
}
|
||||
|
||||
void ListLayout::internalRemoveElement(Drawable& d, int16_t coord)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case SOUTH:
|
||||
if (rect.width > coord)
|
||||
{
|
||||
rect.width = coord;
|
||||
}
|
||||
rect.height -= d.getHeight();
|
||||
d.setX(0);
|
||||
d.setY(0);
|
||||
offset -= d.getHeight();
|
||||
break;
|
||||
case EAST:
|
||||
if (rect.height > coord)
|
||||
{
|
||||
rect.height = coord;
|
||||
}
|
||||
rect.width -= d.getWidth();
|
||||
d.setX(0);
|
||||
d.setY(0);
|
||||
offset -= d.getWidth();
|
||||
break;
|
||||
case NORTH:
|
||||
case WEST:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ListLayout::setDirection(const Direction d)
|
||||
{
|
||||
assert((d == SOUTH || d == EAST) && "Chosen direction not supported");
|
||||
if (direction != d)
|
||||
{
|
||||
direction = d;
|
||||
offset = 0;
|
||||
setWidthHeight(0, 0);
|
||||
Callback<ListLayout, Drawable&> function(this, &ListLayout::internalAddElement);
|
||||
forEachChild(&function);
|
||||
if (parent)
|
||||
{
|
||||
parent->childGeometryChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListLayout::add(Drawable& d)
|
||||
{
|
||||
internalAddElement(d);
|
||||
Container::add(d);
|
||||
if (parent)
|
||||
{
|
||||
parent->childGeometryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ListLayout::removeAll()
|
||||
{
|
||||
offset = 0;
|
||||
setWidthHeight(0, 0);
|
||||
Container::removeAll();
|
||||
if (parent)
|
||||
{
|
||||
parent->childGeometryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ListLayout::insert(Drawable* previous, Drawable& d)
|
||||
{
|
||||
if (!firstChild)
|
||||
{
|
||||
// List is empty, just add the new entry
|
||||
add(d);
|
||||
return;
|
||||
}
|
||||
Container::insert(previous, d);
|
||||
AdjustElements tmp(&d, direction);
|
||||
Callback<AdjustElements, Drawable&> function(&tmp, &AdjustElements::handleInsert);
|
||||
forEachChild(&function);
|
||||
internalAddElementAt(d, tmp.insertedCoord);
|
||||
if (parent)
|
||||
{
|
||||
parent->childGeometryChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ListLayout::remove(Drawable& d)
|
||||
{
|
||||
AdjustElements tmp(&d, direction);
|
||||
Callback<AdjustElements, Drawable&> function(&tmp, &AdjustElements::handleRemove);
|
||||
forEachChild(&function);
|
||||
if (tmp.newElementPassed)
|
||||
{
|
||||
internalRemoveElement(d, tmp.insertedCoord);
|
||||
Container::remove(d);
|
||||
}
|
||||
if (parent)
|
||||
{
|
||||
parent->childGeometryChanged();
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,110 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Bitmap.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/containers/ModalWindow.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ModalWindow::ModalWindow()
|
||||
: Container(),
|
||||
backgroundShade(),
|
||||
windowContainer(),
|
||||
windowBackground()
|
||||
{
|
||||
ModalWindow::setWidthHeight(HAL::DISPLAY_WIDTH, HAL::DISPLAY_HEIGHT);
|
||||
|
||||
const int defaultShadeAlpha = 96;
|
||||
const colortype defaultShadeColor = Color::getColorFromRGB(0x0, 0x0, 0x0);
|
||||
|
||||
backgroundShade.setPosition(0, 0, getWidth(), getHeight());
|
||||
backgroundShade.setColor(defaultShadeColor);
|
||||
backgroundShade.setTouchable(true);
|
||||
ModalWindow::setShadeAlpha(defaultShadeAlpha);
|
||||
Container::add(backgroundShade);
|
||||
|
||||
Container::add(windowContainer);
|
||||
windowContainer.add(windowBackground);
|
||||
}
|
||||
|
||||
void ModalWindow::setBackground(const BitmapId& bmpId)
|
||||
{
|
||||
windowBackground.setBitmap(Bitmap(bmpId));
|
||||
windowBackground.setXY(0, 0);
|
||||
|
||||
windowContainer.setPosition((getWidth() - windowBackground.getWidth()) / 2, (getHeight() - windowBackground.getHeight()) / 2, windowBackground.getWidth(), windowBackground.getHeight());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void ModalWindow::setBackground(const BitmapId& bmpId, int16_t backgroundX, int16_t backgroundY)
|
||||
{
|
||||
setBackground(bmpId);
|
||||
windowContainer.setXY(backgroundX, backgroundY);
|
||||
}
|
||||
|
||||
uint16_t ModalWindow::getBackgroundWidth() const
|
||||
{
|
||||
return windowBackground.getWidth();
|
||||
}
|
||||
|
||||
uint16_t ModalWindow::getBackgroundHeight() const
|
||||
{
|
||||
return windowBackground.getHeight();
|
||||
}
|
||||
|
||||
void ModalWindow::add(Drawable& d)
|
||||
{
|
||||
windowContainer.add(d);
|
||||
}
|
||||
|
||||
void ModalWindow::remove(Drawable& d)
|
||||
{
|
||||
windowContainer.remove(d);
|
||||
}
|
||||
|
||||
void ModalWindow::setShadeAlpha(uint8_t alpha)
|
||||
{
|
||||
backgroundShade.setAlpha(alpha);
|
||||
backgroundShade.invalidate();
|
||||
}
|
||||
|
||||
uint8_t ModalWindow::getShadeAlpha() const
|
||||
{
|
||||
return backgroundShade.getAlpha();
|
||||
}
|
||||
|
||||
void ModalWindow::setShadeColor(colortype color)
|
||||
{
|
||||
backgroundShade.setColor(color);
|
||||
backgroundShade.invalidate();
|
||||
}
|
||||
|
||||
colortype ModalWindow::getShadeColor() const
|
||||
{
|
||||
return backgroundShade.getColor();
|
||||
}
|
||||
|
||||
void ModalWindow::show()
|
||||
{
|
||||
setVisible(true);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void ModalWindow::hide()
|
||||
{
|
||||
invalidate();
|
||||
setVisible(false);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+706
@@ -0,0 +1,706 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/EasingEquations.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/ScrollableContainer.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ScrollableContainer::ScrollableContainer()
|
||||
: Container(),
|
||||
scrollbarPadding(0),
|
||||
scrollbarWidth(2),
|
||||
scrollbarAlpha(120),
|
||||
scrollbarColor(Color::getColorFromRGB(0xFF, 0xFF, 0xFF)),
|
||||
maxVelocity(SCROLLBAR_MAX_VELOCITY),
|
||||
accelDirection(GestureEvent::SWIPE_HORIZONTAL),
|
||||
xSlider(0, 0, scrollbarColor, scrollbarAlpha),
|
||||
ySlider(0, 0, scrollbarColor, scrollbarAlpha),
|
||||
pressedDrawable(0),
|
||||
lastDraggableChild(0),
|
||||
scrolledXDistance(0),
|
||||
scrolledYDistance(0),
|
||||
scrollThreshold(5),
|
||||
pressedX(0),
|
||||
pressedY(0),
|
||||
isPressed(false),
|
||||
isScrolling(false),
|
||||
scrollableX(true),
|
||||
scrollableY(true),
|
||||
scrollbarsVisible(true),
|
||||
scrollbarsPermanentlyVisible(false),
|
||||
scrollDuration(0),
|
||||
beginningValue(0),
|
||||
targetValue(0),
|
||||
animationCounter(0),
|
||||
animate(false),
|
||||
fingerAdjustmentX(0),
|
||||
fingerAdjustmentY(0),
|
||||
hasIssuedCancelEvent(false),
|
||||
scrollDurationSpeedup(7),
|
||||
scrollDurationSlowdown(1)
|
||||
{
|
||||
xSlider.setVisible(false);
|
||||
ySlider.setVisible(false);
|
||||
setTouchable(true);
|
||||
}
|
||||
|
||||
void ScrollableContainer::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
if (event.getType() == ClickEvent::PRESSED)
|
||||
{
|
||||
isPressed = true;
|
||||
|
||||
if (animate)
|
||||
{
|
||||
// Stop scroll animation
|
||||
animate = false;
|
||||
animationCounter = 0;
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
}
|
||||
|
||||
getLastChildNear(event.getX(), event.getY(), &pressedDrawable, &fingerAdjustmentX, &fingerAdjustmentY);
|
||||
|
||||
if (pressedDrawable == this)
|
||||
{
|
||||
pressedDrawable = 0;
|
||||
}
|
||||
|
||||
if (pressedDrawable)
|
||||
{
|
||||
hasIssuedCancelEvent = false;
|
||||
pressedX = event.getX();
|
||||
pressedY = event.getY();
|
||||
const Rect r = pressedDrawable->getAbsoluteRect();
|
||||
const Rect me = getAbsoluteRect();
|
||||
const ClickEvent relative(event.getType(), event.getX() + fingerAdjustmentX - (r.x - me.x), event.getY() + fingerAdjustmentY - (r.y - me.y));
|
||||
pressedDrawable->handleClickEvent(relative);
|
||||
lastDraggableChild = pressedDrawable;
|
||||
}
|
||||
}
|
||||
else if (event.getType() == ClickEvent::CANCEL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else // if (event.getType() == ClickEvent::RELEASED)
|
||||
{
|
||||
if (pressedDrawable)
|
||||
{
|
||||
const Rect r = pressedDrawable->getAbsoluteRect();
|
||||
const Rect me = getAbsoluteRect();
|
||||
const ClickEvent relative(event.getType(), event.getX() + fingerAdjustmentX - (r.x - me.x), event.getY() + fingerAdjustmentY - (r.y - me.y));
|
||||
pressedDrawable->handleClickEvent(relative);
|
||||
}
|
||||
|
||||
pressedDrawable = 0;
|
||||
lastDraggableChild = 0;
|
||||
isPressed = false;
|
||||
}
|
||||
|
||||
isScrolling = false;
|
||||
|
||||
// Redraw scrollbars.
|
||||
xSlider.setVisible((isPressed && scrollbarsVisible) || scrollbarsPermanentlyVisible);
|
||||
ySlider.setVisible((isPressed && scrollbarsVisible) || scrollbarsPermanentlyVisible);
|
||||
invalidateScrollbars();
|
||||
}
|
||||
|
||||
void ScrollableContainer::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
DragEvent actualDrag = event;
|
||||
bool acceptInitialScroll = false;
|
||||
|
||||
bool canScrollX = false;
|
||||
bool canScrollY = false;
|
||||
isScrollableXY(canScrollX, canScrollY);
|
||||
|
||||
if ((pressedDrawable != 0) && (pressedDrawable != this))
|
||||
{
|
||||
// Also send this drag event to the appropriate child widget
|
||||
const Rect r = pressedDrawable->getAbsoluteRect();
|
||||
const Rect me = getAbsoluteRect();
|
||||
const int16_t oldX = event.getOldX() + fingerAdjustmentX - (r.x - me.x);
|
||||
const int16_t oldY = event.getOldY() + fingerAdjustmentY - (r.y - me.y);
|
||||
const int16_t newX = canScrollX ? oldX : event.getNewX() + fingerAdjustmentX - (r.x - me.x);
|
||||
const int16_t newY = canScrollY ? oldY : event.getNewY() + fingerAdjustmentY - (r.y - me.y);
|
||||
|
||||
// but only in the direction(s) where the scrollable container itself
|
||||
// cannot scroll.
|
||||
if ((!canScrollX && newX != oldX) || (!canScrollY && newY != oldY))
|
||||
{
|
||||
const DragEvent relative(DragEvent::DRAGGED, oldX, oldY, newX, newY);
|
||||
pressedDrawable->handleDragEvent(relative);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are not currently scrolling, the drag event delta must
|
||||
// be larger than the threshold value, otherwise the event is ignored.
|
||||
if (!isScrolling)
|
||||
{
|
||||
// Only consider the delta in directions that are actually scrollable.
|
||||
// Note: Do not use the delta from received event since that only reflects
|
||||
// change since last drag. What we want to check here is if the total
|
||||
// delta from the point of click has now exceeded the threshold.
|
||||
actualDrag = DragEvent(DragEvent::DRAGGED, pressedX + fingerAdjustmentX, pressedY + fingerAdjustmentY, event.getNewX() + fingerAdjustmentX, event.getNewY() + fingerAdjustmentY);
|
||||
if (canScrollX)
|
||||
{
|
||||
// Can scroll in X.
|
||||
if (abs(actualDrag.getDeltaX()) > scrollThreshold)
|
||||
{
|
||||
acceptInitialScroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (canScrollY)
|
||||
{
|
||||
// Can scroll in Y.
|
||||
if (abs(actualDrag.getDeltaY()) > scrollThreshold)
|
||||
{
|
||||
acceptInitialScroll = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptInitialScroll)
|
||||
{
|
||||
isScrolling = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Discard this drag event. However, if the new coordinates no longer matches the drawable which received the PRESSED click event
|
||||
// issue a CANCEL event to that drawable.
|
||||
if (pressedDrawable && !hasIssuedCancelEvent)
|
||||
{
|
||||
Drawable* child = 0;
|
||||
Container::getLastChild(event.getNewX() + fingerAdjustmentX, event.getNewY() + fingerAdjustmentY, &child);
|
||||
if (pressedDrawable != child)
|
||||
{
|
||||
const ClickEvent ce(ClickEvent::CANCEL, 0, 0);
|
||||
pressedDrawable->handleClickEvent(ce);
|
||||
hasIssuedCancelEvent = true;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send cancel events to child in focus
|
||||
if (pressedDrawable && !hasIssuedCancelEvent)
|
||||
{
|
||||
const ClickEvent ce(ClickEvent::CANCEL, 0, 0);
|
||||
pressedDrawable->handleClickEvent(ce);
|
||||
hasIssuedCancelEvent = true;
|
||||
}
|
||||
|
||||
int16_t deltaX = 0;
|
||||
int16_t deltaY = 0;
|
||||
|
||||
if (scrollableX)
|
||||
{
|
||||
if (acceptInitialScroll)
|
||||
{
|
||||
// Initial drag which is past threshold, only scroll one pixel in initial event.
|
||||
if (actualDrag.getDeltaX() > 0)
|
||||
{
|
||||
deltaX = 1;
|
||||
}
|
||||
else if (actualDrag.getDeltaX() < 0)
|
||||
{
|
||||
deltaX = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scroll entire delta
|
||||
deltaX = actualDrag.getDeltaX();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not scrollable
|
||||
deltaX = 0;
|
||||
}
|
||||
|
||||
if (scrollableY)
|
||||
{
|
||||
if (acceptInitialScroll)
|
||||
{
|
||||
// Initial drag which is past threshold, only scroll one pixel in initial event.
|
||||
if (actualDrag.getDeltaY() > 0)
|
||||
{
|
||||
deltaY = 1;
|
||||
}
|
||||
else if (actualDrag.getDeltaY() < 0)
|
||||
{
|
||||
deltaY = -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scroll entire delta
|
||||
deltaY = actualDrag.getDeltaY();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not scrollable
|
||||
deltaY = 0;
|
||||
}
|
||||
|
||||
doScroll(deltaX, deltaY);
|
||||
}
|
||||
|
||||
void ScrollableContainer::handleGestureEvent(const GestureEvent& event)
|
||||
{
|
||||
bool canScrollX = false;
|
||||
bool canScrollY = false;
|
||||
isScrollableXY(canScrollX, canScrollY);
|
||||
|
||||
if ((canScrollX && (event.getType() == GestureEvent::SWIPE_HORIZONTAL)) ||
|
||||
(canScrollY && (event.getType() == GestureEvent::SWIPE_VERTICAL)))
|
||||
{
|
||||
int16_t velocityAbsolute = abs(event.getVelocity());
|
||||
|
||||
// Ignore gestures with velocity lower than threshold
|
||||
if (velocityAbsolute < scrollThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Force velocity within limits
|
||||
velocityAbsolute = MIN(velocityAbsolute, maxVelocity);
|
||||
velocityAbsolute = MAX(velocityAbsolute, SCROLLBAR_MIN_VELOCITY);
|
||||
|
||||
// Try to set some reasonable values for how long the resulting scroll should be, and how many ticks is should take
|
||||
scrollDuration = velocityAbsolute * scrollDurationSpeedup / scrollDurationSlowdown;
|
||||
targetValue = ((event.getVelocity() > 0) ? 1 : -1) * (velocityAbsolute - 4) * 72;
|
||||
const int16_t distance = abs(targetValue);
|
||||
scrollDuration = MIN(scrollDuration, distance);
|
||||
|
||||
// Get ready to animate scroll: Initialize values
|
||||
beginningValue = (event.getType() == GestureEvent::SWIPE_VERTICAL) ? getContainedArea().y : getContainedArea().x;
|
||||
animate = true;
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
accelDirection = event.getType();
|
||||
|
||||
if (pressedDrawable && !hasIssuedCancelEvent)
|
||||
{
|
||||
const ClickEvent ce(ClickEvent::CANCEL, 0, 0);
|
||||
pressedDrawable->handleClickEvent(ce);
|
||||
hasIssuedCancelEvent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect ScrollableContainer::getXScrollbar() const
|
||||
{
|
||||
Rect res;
|
||||
if (scrollableX)
|
||||
{
|
||||
const Rect contained = getContainedArea();
|
||||
const int scrollSpace = (scrollableY && (contained.height > rect.height)) ? (2 * scrollbarPadding + scrollbarWidth + SCROLLBAR_LINE) : 0;
|
||||
|
||||
if (contained.width > rect.width)
|
||||
{
|
||||
int leftPadding = (-1 * contained.x * rect.width) / contained.width;
|
||||
const int rightPadding = ((contained.right() - rect.width) * rect.width) / contained.width;
|
||||
const int startWidth = rect.width - (2 * scrollbarPadding + 2 * SCROLLBAR_LINE + scrollSpace);
|
||||
int width = startWidth;
|
||||
width -= (leftPadding + rightPadding);
|
||||
if (width < scrollbarWidth * 2)
|
||||
{
|
||||
// If the contained area is very large, the scrollbar width may become zero or even negative.
|
||||
const int diff = scrollbarWidth * 2 - width;
|
||||
width = scrollbarWidth * 2; // Force scrollbar width to a minimum
|
||||
// Distribute the deviation error based on current scrollbar X position (the amount subtracted from scrollbar xpos increases gradually).
|
||||
leftPadding -= (diff * leftPadding) / startWidth;
|
||||
}
|
||||
res = Rect(leftPadding + scrollbarPadding + SCROLLBAR_LINE, rect.height - (scrollbarWidth + scrollbarPadding + SCROLLBAR_LINE), width, scrollbarWidth);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Rect ScrollableContainer::getYScrollbar() const
|
||||
{
|
||||
Rect res;
|
||||
if (scrollableY)
|
||||
{
|
||||
const Rect contained = getContainedArea();
|
||||
const int scrollSpace = (scrollableX && (contained.width > rect.width)) ? (2 * scrollbarPadding + scrollbarWidth + SCROLLBAR_LINE) : 0;
|
||||
|
||||
if (contained.height > rect.height)
|
||||
{
|
||||
int topPadding = (-1 * contained.y * rect.height) / contained.height;
|
||||
const int bottomPadding = ((contained.bottom() - rect.height) * rect.height) / contained.height;
|
||||
const int startHeight = rect.height - (2 * scrollbarPadding + 2 * SCROLLBAR_LINE + scrollSpace);
|
||||
int height = startHeight;
|
||||
height -= (topPadding + bottomPadding);
|
||||
if (height < scrollbarWidth * 2)
|
||||
{
|
||||
// If the contained area is very large, the scrollbar height may become zero or even negative.
|
||||
const int diff = scrollbarWidth * 2 - height;
|
||||
height = scrollbarWidth * 2; // Force scrollbar height to a minimum
|
||||
// Distribute the deviation error based on current scrollbar Y position (the amount subtracted from scrollbar ypos increases gradually).
|
||||
topPadding -= (diff * topPadding) / startHeight;
|
||||
}
|
||||
res = Rect(rect.width - (scrollbarWidth + scrollbarPadding + 2 * SCROLLBAR_LINE), topPadding + scrollbarPadding + SCROLLBAR_LINE, scrollbarWidth, height);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Rect ScrollableContainer::getXBorder(const Rect& xBar, const Rect& yBar) const
|
||||
{
|
||||
Rect border;
|
||||
if (!xBar.isEmpty())
|
||||
{
|
||||
const int scrollSpace = (!yBar.isEmpty()) ? (2 * scrollbarPadding + scrollbarWidth + SCROLLBAR_LINE) : 0;
|
||||
border = Rect(scrollbarPadding, xBar.y - SCROLLBAR_LINE, rect.width - (2 * scrollbarPadding + scrollSpace), scrollbarWidth + 2 * SCROLLBAR_LINE);
|
||||
}
|
||||
return border;
|
||||
}
|
||||
|
||||
Rect ScrollableContainer::getYBorder(const Rect& xBar, const Rect& yBar) const
|
||||
{
|
||||
Rect border;
|
||||
if (!yBar.isEmpty())
|
||||
{
|
||||
const int scrollSpace = (!xBar.isEmpty()) ? (2 * scrollbarPadding + scrollbarWidth + SCROLLBAR_LINE) : 0;
|
||||
border = Rect(yBar.x - SCROLLBAR_LINE, scrollbarPadding, scrollbarWidth + 2 * SCROLLBAR_LINE, rect.height - (2 * scrollbarPadding + scrollSpace));
|
||||
}
|
||||
return border;
|
||||
}
|
||||
|
||||
void ScrollableContainer::invalidateScrollbars()
|
||||
{
|
||||
const Rect xBar = getXScrollbar();
|
||||
const Rect yBar = getYScrollbar();
|
||||
|
||||
Rect xBorder = getXBorder(xBar, yBar);
|
||||
Rect yBorder = getYBorder(xBar, yBar);
|
||||
|
||||
// The two if statements ensure that the two sliders are invalidated thereby hides them, before they are set to size zero.
|
||||
if (xSlider.getY() > xBorder.y)
|
||||
{
|
||||
xSlider.invalidate();
|
||||
}
|
||||
if (ySlider.getX() > yBorder.x)
|
||||
{
|
||||
ySlider.invalidate();
|
||||
}
|
||||
|
||||
xSlider.setPosition(xBar.x, xBar.y, xBar.width, xBar.height);
|
||||
ySlider.setPosition(yBar.x, yBar.y, yBar.width, yBar.height);
|
||||
|
||||
// x-/yBorder is given the coordinates zero and the witdh of the visiable area for the scrollable container,
|
||||
// to ensure that the entire area where for the scrollable bars is and have been is invalidated correct.
|
||||
xBorder.x = 0;
|
||||
xBorder.width = rect.width;
|
||||
yBorder.height = rect.height;
|
||||
yBorder.y = 0;
|
||||
|
||||
if (!xBorder.isEmpty())
|
||||
{
|
||||
invalidateRect(xBorder);
|
||||
}
|
||||
|
||||
if (!yBorder.isEmpty())
|
||||
{
|
||||
invalidateRect(yBorder);
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollableContainer::doScroll(int16_t deltaX, int16_t deltaY)
|
||||
{
|
||||
if (!deltaX && !deltaY)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool couldScroll = false;
|
||||
const Rect contained = getContainedArea();
|
||||
if (contained.width > rect.width)
|
||||
{
|
||||
if (deltaX > 0)
|
||||
{
|
||||
if (contained.x + deltaX > 0)
|
||||
{
|
||||
deltaX = -contained.x;
|
||||
}
|
||||
}
|
||||
else if (deltaX < 0)
|
||||
{
|
||||
if (contained.right() + deltaX < rect.width)
|
||||
{
|
||||
deltaX = rect.width - contained.right();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaX = 0;
|
||||
}
|
||||
|
||||
if (contained.height > rect.height)
|
||||
{
|
||||
if (deltaY > 0)
|
||||
{
|
||||
if (contained.y + deltaY > 0)
|
||||
{
|
||||
deltaY = -contained.y;
|
||||
}
|
||||
}
|
||||
else if (deltaY < 0)
|
||||
{
|
||||
if (contained.bottom() + deltaY < rect.height)
|
||||
{
|
||||
deltaY = rect.height - contained.bottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaY = 0;
|
||||
}
|
||||
|
||||
if (deltaX || deltaY)
|
||||
{
|
||||
moveChildrenRelative(deltaX, deltaY);
|
||||
|
||||
invalidateScrollbars();
|
||||
couldScroll = true;
|
||||
}
|
||||
return couldScroll;
|
||||
}
|
||||
|
||||
void ScrollableContainer::childGeometryChanged()
|
||||
{
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
const Rect contained = getChildrenContainedArea();
|
||||
if (contained.y > 0)
|
||||
{
|
||||
// Make sure we haven't scrolled above the top
|
||||
deltaY = contained.y;
|
||||
}
|
||||
else if (contained.bottom() < rect.height)
|
||||
{
|
||||
// Make sure we haven't scrolled below the bottom
|
||||
deltaY = contained.bottom() - rect.height;
|
||||
if (contained.y > deltaY)
|
||||
{
|
||||
deltaY = contained.y;
|
||||
}
|
||||
}
|
||||
|
||||
if (contained.x > 0)
|
||||
{
|
||||
// Make sure we haven't scrolled too far to the left
|
||||
deltaX = contained.x;
|
||||
}
|
||||
else if (contained.right() < rect.width)
|
||||
{
|
||||
// Make sure we haven't scrolled too far to the right
|
||||
deltaX = contained.right() - rect.width;
|
||||
if (contained.x > deltaX)
|
||||
{
|
||||
deltaX = contained.x;
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll if needed, and update scrollbars.
|
||||
if (deltaX != 0 || deltaY != 0)
|
||||
{
|
||||
moveChildrenRelative(-deltaX, -deltaY);
|
||||
invalidateScrollbars();
|
||||
}
|
||||
else if (scrollbarsPermanentlyVisible)
|
||||
{
|
||||
invalidateScrollbars();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollableContainer::add(Drawable& d)
|
||||
{
|
||||
remove(xSlider);
|
||||
remove(ySlider);
|
||||
|
||||
Container::add(d);
|
||||
Container::add(xSlider);
|
||||
Container::add(ySlider);
|
||||
|
||||
if (scrollbarsPermanentlyVisible)
|
||||
{
|
||||
invalidateScrollbars();
|
||||
}
|
||||
}
|
||||
|
||||
Rect ScrollableContainer::getContainedArea() const
|
||||
{
|
||||
Rect contained(0, 0, rect.width, rect.height);
|
||||
contained.expandToFit(getChildrenContainedArea());
|
||||
return contained;
|
||||
}
|
||||
|
||||
Rect ScrollableContainer::getChildrenContainedArea() const
|
||||
{
|
||||
Rect contained;
|
||||
for (Drawable* d = firstChild; d; d = d->getNextSibling())
|
||||
{
|
||||
if ((d != &xSlider) && (d != &ySlider) && (d->isVisible()))
|
||||
{
|
||||
contained.expandToFit(d->getRect());
|
||||
}
|
||||
}
|
||||
return contained;
|
||||
}
|
||||
|
||||
void ScrollableContainer::reset()
|
||||
{
|
||||
moveChildrenRelative(-scrolledXDistance, -scrolledYDistance);
|
||||
invalidateScrollbars();
|
||||
}
|
||||
|
||||
void ScrollableContainer::moveChildrenRelative(int16_t deltaX, int16_t deltaY)
|
||||
{
|
||||
for (Drawable* d = firstChild; d; d = d->getNextSibling())
|
||||
{
|
||||
if ((d != &xSlider) && (d != &ySlider))
|
||||
{
|
||||
d->moveRelative(deltaX, deltaY);
|
||||
}
|
||||
}
|
||||
scrolledXDistance += deltaX;
|
||||
scrolledYDistance += deltaY;
|
||||
}
|
||||
|
||||
void ScrollableContainer::handleTickEvent()
|
||||
{
|
||||
if (!animate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Calculate new position or stop animation
|
||||
animationCounter++;
|
||||
if (animationCounter <= scrollDuration)
|
||||
{
|
||||
// Calculate value in [beginningValue ; (beginningValue+targetValue)]
|
||||
int16_t calculatedValue = EasingEquations::cubicEaseOut(animationCounter, beginningValue, targetValue, scrollDuration);
|
||||
|
||||
// Note: Result of "calculatedValue & 1" is compiler dependent for negative values of calculatedValue
|
||||
if (calculatedValue % 2)
|
||||
{
|
||||
// Optimization: calculatedValue is odd, add 1/-1 to move drawables modulo 32 bits in framebuffer
|
||||
calculatedValue += (calculatedValue > 0) ? 1 : -1;
|
||||
}
|
||||
|
||||
// Convert to delta value relative to current X or Y
|
||||
const int16_t scrollX = (accelDirection == GestureEvent::SWIPE_VERTICAL) ? 0 : (calculatedValue - getContainedArea().x);
|
||||
const int16_t scrollY = (accelDirection == GestureEvent::SWIPE_HORIZONTAL) ? 0 : (calculatedValue - getContainedArea().y);
|
||||
|
||||
// Perform the actual animation step, stop animation if
|
||||
// scrolling was not possible (doScroll invalidates children)
|
||||
animate = doScroll(scrollX, scrollY);
|
||||
}
|
||||
else
|
||||
{
|
||||
animate = false;
|
||||
}
|
||||
|
||||
if (!animate)
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
animationCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollbarsColor(colortype color)
|
||||
{
|
||||
scrollbarColor = color;
|
||||
xSlider.setColor(scrollbarColor);
|
||||
ySlider.setColor(scrollbarColor);
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollbarsAlpha(uint8_t alpha)
|
||||
{
|
||||
scrollbarAlpha = alpha;
|
||||
xSlider.setAlpha(scrollbarAlpha);
|
||||
ySlider.setAlpha(scrollbarAlpha);
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollbarPadding(uint8_t padding)
|
||||
{
|
||||
scrollbarPadding = padding;
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollbarWidth(uint8_t width)
|
||||
{
|
||||
scrollbarWidth = width;
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollbarsVisible(bool newVisible)
|
||||
{
|
||||
scrollbarsVisible = newVisible;
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollbarsPermanentlyVisible(bool permanentlyVisible /*= true*/)
|
||||
{
|
||||
scrollbarsPermanentlyVisible = permanentlyVisible;
|
||||
xSlider.setVisible(true);
|
||||
ySlider.setVisible(true);
|
||||
invalidateScrollbars();
|
||||
}
|
||||
|
||||
bool ScrollableContainer::getIsScrolling() const
|
||||
{
|
||||
return isScrolling;
|
||||
}
|
||||
|
||||
bool ScrollableContainer::getIsPressed() const
|
||||
{
|
||||
return isPressed;
|
||||
}
|
||||
|
||||
int16_t ScrollableContainer::getScrolledX() const
|
||||
{
|
||||
return scrolledXDistance;
|
||||
}
|
||||
|
||||
int16_t ScrollableContainer::getScrolledY() const
|
||||
{
|
||||
return scrolledYDistance;
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollDurationSpeedup(uint16_t speedup)
|
||||
{
|
||||
scrollDurationSpeedup = MAX(1, speedup);
|
||||
}
|
||||
|
||||
uint16_t ScrollableContainer::getScrollDurationSpeedup() const
|
||||
{
|
||||
return scrollDurationSpeedup;
|
||||
}
|
||||
|
||||
void ScrollableContainer::setScrollDurationSlowdown(uint16_t slowdown)
|
||||
{
|
||||
scrollDurationSlowdown = MAX(1, slowdown);
|
||||
}
|
||||
|
||||
uint16_t ScrollableContainer::getScrollDurationSlowdown() const
|
||||
{
|
||||
return scrollDurationSlowdown;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,405 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/containers/SlideMenu.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
SlideMenu::SlideMenu()
|
||||
: Container(),
|
||||
menuContainer(),
|
||||
stateChangeButton(),
|
||||
background(),
|
||||
onStateChangeButtonClicked(this, &SlideMenu::stateChangeButtonClickedHandler),
|
||||
animationEndedCallback(this, &SlideMenu::animationEndedHandler),
|
||||
stateChangedCallback(0),
|
||||
stateChangedAnimationEndedCallback(0),
|
||||
currentState(COLLAPSED),
|
||||
expandDirection(EAST),
|
||||
animationEquation(EasingEquations::cubicEaseInOut),
|
||||
visiblePixelsWhenCollapsed(0),
|
||||
hiddenPixelsWhenExpanded(0),
|
||||
expandedStateTimeout(200),
|
||||
expandedStateTimer(0),
|
||||
animationDuration(10)
|
||||
{
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
|
||||
stateChangeButton.setAction(onStateChangeButtonClicked);
|
||||
menuContainer.setMoveAnimationEndedAction(animationEndedCallback);
|
||||
|
||||
Container::add(menuContainer);
|
||||
menuContainer.add(background);
|
||||
menuContainer.add(stateChangeButton);
|
||||
}
|
||||
|
||||
SlideMenu::~SlideMenu()
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
}
|
||||
|
||||
void SlideMenu::setup(ExpandDirection newExpandDirection, const Bitmap& backgroundBMP, const Bitmap& stateChangeButtonBMP, const Bitmap& stateChangeButtonPressedBMP)
|
||||
{
|
||||
int16_t backgroundX = 0;
|
||||
int16_t backgroundY = 0;
|
||||
int16_t buttonX = 0;
|
||||
int16_t buttonY = 0;
|
||||
|
||||
switch (newExpandDirection)
|
||||
{
|
||||
case SOUTH:
|
||||
backgroundX = 0;
|
||||
backgroundY = 0;
|
||||
buttonX = (backgroundBMP.getWidth() - stateChangeButtonBMP.getWidth()) / 2;
|
||||
buttonY = backgroundBMP.getHeight();
|
||||
|
||||
setVisiblePixelsWhenCollapsed(stateChangeButtonBMP.getHeight());
|
||||
break;
|
||||
case NORTH:
|
||||
backgroundX = 0;
|
||||
backgroundY = stateChangeButtonBMP.getHeight();
|
||||
buttonX = (backgroundBMP.getWidth() - stateChangeButtonBMP.getWidth()) / 2;
|
||||
buttonY = 0;
|
||||
|
||||
setVisiblePixelsWhenCollapsed(stateChangeButtonBMP.getHeight());
|
||||
break;
|
||||
case EAST:
|
||||
backgroundX = 0;
|
||||
backgroundY = 0;
|
||||
buttonX = backgroundBMP.getWidth();
|
||||
buttonY = (backgroundBMP.getHeight() - stateChangeButtonBMP.getHeight()) / 2;
|
||||
|
||||
setVisiblePixelsWhenCollapsed(stateChangeButtonBMP.getWidth());
|
||||
break;
|
||||
case WEST:
|
||||
backgroundX = stateChangeButtonBMP.getWidth();
|
||||
backgroundY = 0;
|
||||
buttonX = 0;
|
||||
buttonY = (backgroundBMP.getHeight() - stateChangeButtonBMP.getHeight()) / 2;
|
||||
|
||||
setVisiblePixelsWhenCollapsed(stateChangeButtonBMP.getWidth());
|
||||
break;
|
||||
}
|
||||
|
||||
setup(newExpandDirection, backgroundBMP, stateChangeButtonBMP, stateChangeButtonPressedBMP, backgroundX, backgroundY, buttonX, buttonY);
|
||||
}
|
||||
|
||||
void SlideMenu::setup(ExpandDirection newExpandDirection, const Bitmap& backgroundBMP, int16_t backgroundX, int16_t backgroundY)
|
||||
{
|
||||
setExpandDirection(newExpandDirection);
|
||||
|
||||
background.setBitmap(backgroundBMP);
|
||||
background.setXY(backgroundX, backgroundY);
|
||||
|
||||
const Rect boundingRect = background.getRect();
|
||||
// boundingRect.expandToFit(background.getRect());
|
||||
|
||||
menuContainer.setWidth(boundingRect.right());
|
||||
menuContainer.setHeight(boundingRect.bottom());
|
||||
|
||||
setWidthHeight(menuContainer);
|
||||
|
||||
setExpandDirection(expandDirection);
|
||||
setState(currentState);
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SlideMenu::setup(ExpandDirection newExpandDirection, const Bitmap& backgroundBMP, const Bitmap& stateChangeButtonBMP, const Bitmap& stateChangeButtonPressedBMP, int16_t backgroundX, int16_t backgroundY, int16_t stateChangeButtonX, int16_t stateChangeButtonY)
|
||||
{
|
||||
setExpandDirection(newExpandDirection);
|
||||
|
||||
background.setBitmap(backgroundBMP);
|
||||
background.setXY(backgroundX, backgroundY);
|
||||
|
||||
stateChangeButton.setBitmaps(stateChangeButtonBMP, stateChangeButtonPressedBMP);
|
||||
stateChangeButton.setXY(stateChangeButtonX, stateChangeButtonY);
|
||||
|
||||
Rect boundingRect;
|
||||
boundingRect.expandToFit(background.getRect());
|
||||
boundingRect.expandToFit(stateChangeButton.getRect());
|
||||
|
||||
menuContainer.setWidth(boundingRect.right());
|
||||
menuContainer.setHeight(boundingRect.bottom());
|
||||
|
||||
setWidthHeight(menuContainer);
|
||||
|
||||
setExpandDirection(expandDirection);
|
||||
setState(currentState);
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void SlideMenu::setExpandDirection(ExpandDirection newExpandDirection)
|
||||
{
|
||||
expandDirection = newExpandDirection;
|
||||
|
||||
setState(currentState);
|
||||
}
|
||||
|
||||
SlideMenu::ExpandDirection SlideMenu::getExpandDirection() const
|
||||
{
|
||||
return expandDirection;
|
||||
}
|
||||
|
||||
void SlideMenu::setVisiblePixelsWhenCollapsed(int16_t visiblePixels)
|
||||
{
|
||||
visiblePixelsWhenCollapsed = visiblePixels;
|
||||
|
||||
setState(currentState);
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getVisiblePixelsWhenCollapsed() const
|
||||
{
|
||||
return visiblePixelsWhenCollapsed;
|
||||
}
|
||||
|
||||
void SlideMenu::setHiddenPixelsWhenExpanded(int16_t hiddenPixels)
|
||||
{
|
||||
hiddenPixelsWhenExpanded = hiddenPixels;
|
||||
|
||||
setState(currentState);
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getHiddenPixelsWhenExpanded() const
|
||||
{
|
||||
return hiddenPixelsWhenExpanded;
|
||||
}
|
||||
|
||||
void SlideMenu::setExpandedStateTimeout(uint16_t timeout)
|
||||
{
|
||||
expandedStateTimeout = timeout;
|
||||
}
|
||||
|
||||
uint16_t SlideMenu::getExpandedStateTimeout() const
|
||||
{
|
||||
return expandedStateTimeout;
|
||||
}
|
||||
|
||||
void SlideMenu::setAnimationDuration(uint16_t duration)
|
||||
{
|
||||
animationDuration = duration;
|
||||
}
|
||||
|
||||
uint16_t SlideMenu::getAnimationDuration() const
|
||||
{
|
||||
return animationDuration;
|
||||
}
|
||||
|
||||
void SlideMenu::setAnimationEasingEquation(EasingEquation animationEasingEquation)
|
||||
{
|
||||
animationEquation = animationEasingEquation;
|
||||
}
|
||||
|
||||
EasingEquation SlideMenu::getAnimationEasingEquation() const
|
||||
{
|
||||
return animationEquation;
|
||||
}
|
||||
|
||||
void SlideMenu::setState(State newState)
|
||||
{
|
||||
if (newState == COLLAPSED)
|
||||
{
|
||||
menuContainer.moveTo(getCollapsedXCoordinate(), getCollapsedYCoordinate());
|
||||
}
|
||||
else
|
||||
{
|
||||
menuContainer.moveTo(getExpandedXCoordinate(), getExpandedYCoordinate());
|
||||
}
|
||||
currentState = newState;
|
||||
}
|
||||
|
||||
void SlideMenu::animateToState(State newState)
|
||||
{
|
||||
if (animationDuration == 0)
|
||||
{
|
||||
setState(newState);
|
||||
}
|
||||
else if (newState != currentState)
|
||||
{
|
||||
if (currentState == COLLAPSED)
|
||||
{
|
||||
menuContainer.startMoveAnimation(getExpandedXCoordinate(), getExpandedYCoordinate(), animationDuration, animationEquation, animationEquation);
|
||||
currentState = EXPANDED;
|
||||
}
|
||||
else
|
||||
{
|
||||
menuContainer.startMoveAnimation(getCollapsedXCoordinate(), getCollapsedYCoordinate(), animationDuration, animationEquation, animationEquation);
|
||||
currentState = COLLAPSED;
|
||||
}
|
||||
|
||||
// Disable stateChangeButton while animating
|
||||
stateChangeButton.setTouchable(false);
|
||||
}
|
||||
}
|
||||
|
||||
SlideMenu::State SlideMenu::getState()
|
||||
{
|
||||
return currentState;
|
||||
}
|
||||
|
||||
void SlideMenu::resetExpandedStateTimer()
|
||||
{
|
||||
expandedStateTimer = 0;
|
||||
}
|
||||
|
||||
uint16_t SlideMenu::getExpandedStateTimer() const
|
||||
{
|
||||
return expandedStateTimer;
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getBackgroundX() const
|
||||
{
|
||||
return background.getX();
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getBackgroundY() const
|
||||
{
|
||||
return background.getY();
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getStateChangeButtonX() const
|
||||
{
|
||||
return stateChangeButton.getX();
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getStateChangeButtonY() const
|
||||
{
|
||||
return stateChangeButton.getY();
|
||||
}
|
||||
|
||||
void SlideMenu::setStateChangedCallback(GenericCallback<const SlideMenu&>& callback)
|
||||
{
|
||||
stateChangedCallback = &callback;
|
||||
}
|
||||
|
||||
void SlideMenu::setStateChangedAnimationEndedCallback(GenericCallback<const SlideMenu&>& callback)
|
||||
{
|
||||
stateChangedAnimationEndedCallback = &callback;
|
||||
}
|
||||
|
||||
void SlideMenu::handleTickEvent()
|
||||
{
|
||||
if ((expandedStateTimeout != 0) && (currentState == EXPANDED) && !menuContainer.isMoveAnimationRunning())
|
||||
{
|
||||
expandedStateTimer++;
|
||||
|
||||
if (expandedStateTimer > expandedStateTimeout)
|
||||
{
|
||||
animateToState(COLLAPSED);
|
||||
if ((stateChangedCallback != 0) && stateChangedCallback->isValid())
|
||||
{
|
||||
stateChangedCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlideMenu::add(Drawable& d)
|
||||
{
|
||||
menuContainer.add(d);
|
||||
}
|
||||
|
||||
void SlideMenu::remove(Drawable& d)
|
||||
{
|
||||
menuContainer.remove(d);
|
||||
}
|
||||
|
||||
void SlideMenu::stateChangeButtonClickedHandler(const AbstractButton& /*button*/)
|
||||
{
|
||||
if (currentState == COLLAPSED)
|
||||
{
|
||||
animateToState(EXPANDED);
|
||||
}
|
||||
else
|
||||
{
|
||||
animateToState(COLLAPSED);
|
||||
}
|
||||
|
||||
if ((stateChangedCallback != 0) && stateChangedCallback->isValid())
|
||||
{
|
||||
stateChangedCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void SlideMenu::animationEndedHandler(const MoveAnimator<Container>& /*container*/)
|
||||
{
|
||||
resetExpandedStateTimer();
|
||||
stateChangeButton.setTouchable(true);
|
||||
|
||||
if ((stateChangedAnimationEndedCallback != 0) && stateChangedAnimationEndedCallback->isValid())
|
||||
{
|
||||
stateChangedAnimationEndedCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getCollapsedXCoordinate()
|
||||
{
|
||||
switch (expandDirection)
|
||||
{
|
||||
case EAST:
|
||||
return -menuContainer.getWidth() + visiblePixelsWhenCollapsed;
|
||||
case WEST:
|
||||
return getWidth() - visiblePixelsWhenCollapsed;
|
||||
case SOUTH:
|
||||
case NORTH:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getCollapsedYCoordinate()
|
||||
{
|
||||
switch (expandDirection)
|
||||
{
|
||||
case SOUTH:
|
||||
return -menuContainer.getHeight() + visiblePixelsWhenCollapsed;
|
||||
case NORTH:
|
||||
return getHeight() - visiblePixelsWhenCollapsed;
|
||||
case EAST:
|
||||
case WEST:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getExpandedXCoordinate()
|
||||
{
|
||||
switch (expandDirection)
|
||||
{
|
||||
case EAST:
|
||||
return -hiddenPixelsWhenExpanded;
|
||||
case WEST:
|
||||
return hiddenPixelsWhenExpanded;
|
||||
case SOUTH:
|
||||
case NORTH:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int16_t SlideMenu::getExpandedYCoordinate()
|
||||
{
|
||||
switch (expandDirection)
|
||||
{
|
||||
case SOUTH:
|
||||
return -hiddenPixelsWhenExpanded;
|
||||
case NORTH:
|
||||
return hiddenPixelsWhenExpanded;
|
||||
case EAST:
|
||||
case WEST:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,239 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/Slider.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Slider::Slider()
|
||||
: Container(),
|
||||
sliderOrientation(HORIZONTAL),
|
||||
currentValue(0),
|
||||
valueRangeMin(0),
|
||||
valueRangeMax(100),
|
||||
background(),
|
||||
backgroundSelected(),
|
||||
indicator(),
|
||||
backgroundSelectedViewPort(),
|
||||
indicatorMinPosition(0),
|
||||
indicatorMaxPosition(1),
|
||||
startValueCallback(0),
|
||||
stopValueCallback(0),
|
||||
newValueCallback(0)
|
||||
{
|
||||
setTouchable(true);
|
||||
|
||||
// The backgroundSelectedViewPort is a container into which the bitmap for the "filled" background
|
||||
// is placed. Containers are viewports, so the dimensions of this container controls how
|
||||
// much of the filled background is visible.
|
||||
backgroundSelectedViewPort.add(backgroundSelected);
|
||||
|
||||
Container::add(background);
|
||||
Container::add(backgroundSelectedViewPort);
|
||||
Container::add(indicator);
|
||||
|
||||
// Default value range
|
||||
Slider::updateIndicatorPosition(indicatorMinPosition);
|
||||
}
|
||||
|
||||
void Slider::setBitmaps(const Bitmap& sliderBackground, const Bitmap& sliderBackgroundSelected, const Bitmap& indicatorBitmap)
|
||||
{
|
||||
assert(sliderBackground.getWidth() == sliderBackgroundSelected.getWidth() &&
|
||||
sliderBackground.getHeight() == sliderBackgroundSelected.getHeight() &&
|
||||
"Slider::setBitmaps - background and backgroundFilled must have same dimensions");
|
||||
|
||||
// Invalidate before and after in case size changes from previous bitmaps
|
||||
background.invalidate(); // Redraw slider
|
||||
indicator.invalidate(); // Redraw indicator
|
||||
|
||||
background.setBitmap(sliderBackground);
|
||||
backgroundSelected.setBitmap(sliderBackgroundSelected);
|
||||
indicator.setBitmap(indicatorBitmap);
|
||||
backgroundSelectedViewPort.setWidthHeight(backgroundSelected);
|
||||
|
||||
setValue(currentValue); // Update size of backgroundSelectedViewPort
|
||||
|
||||
background.invalidate(); // Redraw slider
|
||||
indicator.invalidate(); // Redraw indicator
|
||||
}
|
||||
|
||||
void Slider::setBitmaps(const BitmapId sliderBackground, const BitmapId sliderBackgroundSelected, const BitmapId indicatorBitmap)
|
||||
{
|
||||
setBitmaps(Bitmap(sliderBackground), Bitmap(sliderBackgroundSelected), Bitmap(indicatorBitmap));
|
||||
}
|
||||
|
||||
void Slider::setupHorizontalSlider(int16_t backgroundX, int16_t backgroundY, int16_t indicatorY, int16_t indicatorMinX, int16_t indicatorMaxX)
|
||||
{
|
||||
assert(indicatorMinX < indicatorMaxX && "Slider::setupHorizontalSlider - indicatorMinX must be smaller than indicatorMaxX");
|
||||
|
||||
sliderOrientation = HORIZONTAL;
|
||||
|
||||
background.setXY(backgroundX, backgroundY);
|
||||
backgroundSelectedViewPort.setPosition(background);
|
||||
backgroundSelected.setXY(0, 0);
|
||||
indicator.setY(indicatorY);
|
||||
|
||||
const int16_t backgroundWidth = backgroundX + background.getWidth();
|
||||
const int16_t indicatorWidth = indicatorMaxX + indicator.getWidth();
|
||||
const int16_t newWidth = MAX(backgroundWidth, indicatorWidth);
|
||||
|
||||
const int16_t backgroundHeight = backgroundY + background.getHeight();
|
||||
const int16_t indicatorHeight = indicatorY + indicator.getHeight();
|
||||
const int16_t newHeight = MAX(backgroundHeight, indicatorHeight);
|
||||
|
||||
indicatorMinPosition = indicatorMinX;
|
||||
indicatorMaxPosition = indicatorMaxX;
|
||||
|
||||
setWidthHeight(newWidth, newHeight);
|
||||
|
||||
setValue(currentValue);
|
||||
}
|
||||
|
||||
void Slider::setupVerticalSlider(int16_t backgroundX, int16_t backgroundY, int16_t indicatorX, int16_t indicatorMinY, int16_t indicatorMaxY)
|
||||
{
|
||||
assert(indicatorMinY < indicatorMaxY && "Slider::setupVerticalSlider - indicatorMinY must be smaller than indicatorMaxY");
|
||||
|
||||
sliderOrientation = VERTICAL;
|
||||
|
||||
background.setXY(backgroundX, backgroundY);
|
||||
backgroundSelectedViewPort.setPosition(background);
|
||||
indicator.setX(indicatorX);
|
||||
|
||||
const int16_t backgroundWidth = backgroundX + background.getWidth();
|
||||
const int16_t indicatorWidth = indicatorX + indicator.getWidth();
|
||||
const int16_t newWidth = MAX(backgroundWidth, indicatorWidth);
|
||||
|
||||
const int16_t backgroundHeight = backgroundY + background.getHeight();
|
||||
const int16_t indicatorHeight = indicatorMaxY + indicator.getHeight();
|
||||
const int16_t newHeight = MAX(backgroundHeight, indicatorHeight);
|
||||
|
||||
indicatorMinPosition = indicatorMinY;
|
||||
indicatorMaxPosition = indicatorMaxY;
|
||||
|
||||
setWidthHeight(newWidth, newHeight);
|
||||
|
||||
setValue(currentValue);
|
||||
}
|
||||
|
||||
void Slider::setValue(int16_t value)
|
||||
{
|
||||
updateIndicatorPosition(valueToPosition(value));
|
||||
}
|
||||
|
||||
void Slider::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
if ((event.getType() == ClickEvent::PRESSED) || (event.getType() == ClickEvent::RELEASED))
|
||||
{
|
||||
// Communicate the start value if a listener is registered
|
||||
if ((event.getType() == ClickEvent::PRESSED) && (startValueCallback != 0) && startValueCallback->isValid())
|
||||
{
|
||||
startValueCallback->execute(*this, currentValue);
|
||||
}
|
||||
|
||||
updateIndicatorPosition((sliderOrientation == HORIZONTAL ? event.getX() : event.getY()) - getIndicatorRadius());
|
||||
|
||||
// Communicate the stop value if a listener is registered
|
||||
if ((event.getType() == ClickEvent::RELEASED) && (stopValueCallback != 0) && stopValueCallback->isValid())
|
||||
{
|
||||
stopValueCallback->execute(*this, currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Slider::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
updateIndicatorPosition((sliderOrientation == HORIZONTAL ? event.getNewX() : event.getNewY()) - getIndicatorRadius());
|
||||
}
|
||||
|
||||
int16_t Slider::valueToPosition(int value) const
|
||||
{
|
||||
value = MIN(valueRangeMax, value);
|
||||
value = MAX(value, valueRangeMin);
|
||||
const int16_t relativePosition = muldiv(value - valueRangeMin, getIndicatorPositionRangeSize(), getValueRangeSize());
|
||||
return sliderOrientation == VERTICAL ? indicatorMaxPosition - relativePosition : indicatorMinPosition + relativePosition;
|
||||
}
|
||||
|
||||
int Slider::positionToValue(int16_t position) const
|
||||
{
|
||||
position = MIN(indicatorMaxPosition, position);
|
||||
position = MAX(position, indicatorMinPosition);
|
||||
const int16_t relativePosition = sliderOrientation == VERTICAL ? indicatorMaxPosition - position : position - indicatorMinPosition;
|
||||
return valueRangeMin + muldiv(relativePosition, getValueRangeSize(), getIndicatorPositionRangeSize());
|
||||
}
|
||||
|
||||
void Slider::updateIndicatorPosition(int16_t position)
|
||||
{
|
||||
// Cut off positions outside the slider area
|
||||
position = MAX(position, indicatorMinPosition);
|
||||
position = MIN(position, indicatorMaxPosition);
|
||||
|
||||
if (sliderOrientation == HORIZONTAL)
|
||||
{
|
||||
indicator.moveTo(position, indicator.getY());
|
||||
|
||||
backgroundSelectedViewPort.invalidate();
|
||||
backgroundSelectedViewPort.setWidth((position - backgroundSelectedViewPort.getX()) + getIndicatorRadius());
|
||||
backgroundSelectedViewPort.invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
indicator.moveTo(indicator.getX(), position);
|
||||
|
||||
backgroundSelectedViewPort.invalidate();
|
||||
const int16_t newViewPortHeight = background.getRect().bottom() - (position + getIndicatorRadius());
|
||||
backgroundSelectedViewPort.setPosition(backgroundSelectedViewPort.getX(), position + getIndicatorRadius(), backgroundSelectedViewPort.getWidth(), newViewPortHeight);
|
||||
backgroundSelected.setY(-(backgroundSelected.getHeight() - newViewPortHeight));
|
||||
backgroundSelectedViewPort.invalidate();
|
||||
}
|
||||
|
||||
currentValue = positionToValue(position);
|
||||
|
||||
// Communicate the new value if a listener is registered
|
||||
if ((newValueCallback != 0) && newValueCallback->isValid())
|
||||
{
|
||||
newValueCallback->execute(*this, currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t Slider::getIndicatorRadius() const
|
||||
{
|
||||
return (sliderOrientation == HORIZONTAL ? indicator.getWidth() : indicator.getHeight()) / 2;
|
||||
}
|
||||
|
||||
void Slider::setValueRange(int16_t minValue, int16_t maxValue, int16_t newValue)
|
||||
{
|
||||
assert(minValue < maxValue && "Slider::setValueRange - minValue must be smaller than maxValue");
|
||||
|
||||
valueRangeMin = minValue;
|
||||
valueRangeMax = maxValue;
|
||||
|
||||
setValue(newValue);
|
||||
}
|
||||
|
||||
void Slider::setValueRange(int16_t minValue, int16_t maxValue)
|
||||
{
|
||||
const int16_t newValue = currentValue < minValue ? minValue : (currentValue > maxValue ? maxValue : currentValue);
|
||||
setValueRange(minValue, maxValue, newValue);
|
||||
}
|
||||
|
||||
int Slider::getIndicatorPositionRangeSize() const
|
||||
{
|
||||
return indicatorMaxPosition - indicatorMinPosition;
|
||||
}
|
||||
|
||||
int Slider::getValueRangeSize() const
|
||||
{
|
||||
return valueRangeMax - valueRangeMin;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+527
@@ -0,0 +1,527 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/Bitmap.hpp>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/EasingEquations.hpp>
|
||||
#include <touchgfx/containers/Container.hpp>
|
||||
#include <touchgfx/containers/SwipeContainer.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
SwipeContainer::SwipeContainer()
|
||||
: Container(),
|
||||
currentState(NO_ANIMATION),
|
||||
animationCounter(0),
|
||||
swipeCutoff(80),
|
||||
dragX(0),
|
||||
animateDistance(0),
|
||||
startX(0),
|
||||
endElasticWidth(30),
|
||||
animationDuration(20),
|
||||
pageDelta(0),
|
||||
pages(EAST),
|
||||
pageIndicator()
|
||||
{
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
|
||||
setTouchable(true);
|
||||
|
||||
Container::add(pages);
|
||||
Container::add(pageIndicator);
|
||||
}
|
||||
|
||||
SwipeContainer::~SwipeContainer()
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
}
|
||||
|
||||
void SwipeContainer::add(Drawable& page)
|
||||
{
|
||||
pages.add(page);
|
||||
|
||||
pageIndicator.setNumberOfPages(getNumberOfPages() + 1);
|
||||
setSelectedPage(getSelectedPage());
|
||||
|
||||
setWidthHeight(page);
|
||||
}
|
||||
|
||||
void SwipeContainer::remove(Drawable& page)
|
||||
{
|
||||
Drawable* tmp = pages.getFirstChild();
|
||||
|
||||
if (pageIndicator.getNumberOfPages() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// looks for the child matching page
|
||||
// to ensure that the page indicator only counts down if a page is removed
|
||||
while (tmp)
|
||||
{
|
||||
if (tmp == &page)
|
||||
{
|
||||
pages.remove(page);
|
||||
pageIndicator.setNumberOfPages(getNumberOfPages() - 1);
|
||||
|
||||
const uint8_t numPages = getNumberOfPages();
|
||||
if (numPages == 0)
|
||||
{
|
||||
invalidate();
|
||||
setWidthHeight(0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t curPage = getSelectedPage();
|
||||
setSelectedPage(MIN(curPage, numPages - 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = tmp->getNextSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::setEndSwipeElasticWidth(uint16_t width)
|
||||
{
|
||||
endElasticWidth = width;
|
||||
}
|
||||
|
||||
void SwipeContainer::setSwipeCutoff(uint16_t cutoff)
|
||||
{
|
||||
swipeCutoff = cutoff;
|
||||
}
|
||||
|
||||
void SwipeContainer::setPageIndicatorBitmaps(const Bitmap& normalPage, const Bitmap& highlightedPage)
|
||||
{
|
||||
pageIndicator.setBitmaps(normalPage, highlightedPage);
|
||||
}
|
||||
|
||||
void SwipeContainer::setPageIndicatorXY(int16_t x, int16_t y)
|
||||
{
|
||||
pageIndicator.setXY(x, y);
|
||||
}
|
||||
|
||||
void SwipeContainer::setPageIndicatorXYWithCenteredX(int16_t x, int16_t y)
|
||||
{
|
||||
setPageIndicatorCenteredX(x);
|
||||
pageIndicator.setY(y);
|
||||
}
|
||||
|
||||
void SwipeContainer::setPageIndicatorCenteredX()
|
||||
{
|
||||
setPageIndicatorCenteredX(getWidth() / 2);
|
||||
}
|
||||
|
||||
void SwipeContainer::setPageIndicatorCenteredX(int16_t x)
|
||||
{
|
||||
pageIndicator.setX(x - pageIndicator.getWidth() / 2);
|
||||
}
|
||||
|
||||
void SwipeContainer::setSelectedPage(uint8_t pageIndex)
|
||||
{
|
||||
if (pageIndex < getNumberOfPages())
|
||||
{
|
||||
pageIndicator.setCurrentPage(pageIndex);
|
||||
adjustPages();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SwipeContainer::getSelectedPage() const
|
||||
{
|
||||
return pageIndicator.getCurrentPage();
|
||||
}
|
||||
|
||||
void SwipeContainer::handleTickEvent()
|
||||
{
|
||||
if (currentState == ANIMATE_SWIPE_CANCELLED_LEFT)
|
||||
{
|
||||
animateSwipeCancelledLeft();
|
||||
}
|
||||
else if (currentState == ANIMATE_SWIPE_CANCELLED_RIGHT)
|
||||
{
|
||||
animateSwipeCancelledRight();
|
||||
}
|
||||
else if (currentState == ANIMATE_LEFT)
|
||||
{
|
||||
animateLeft();
|
||||
}
|
||||
else if (currentState == ANIMATE_RIGHT)
|
||||
{
|
||||
animateRight();
|
||||
}
|
||||
else if (currentState == ANIMATE_LEFT_WITH_BUTTON)
|
||||
{
|
||||
animateLeftWithButton();
|
||||
}
|
||||
else if (currentState == ANIMATE_RIGHT_WITH_BUTTON)
|
||||
{
|
||||
animateRightWithButton();
|
||||
}
|
||||
else if (currentState == ANIMATE_TO_PAGE)
|
||||
{
|
||||
animateToPage();
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
// If an animation is already in progress do not
|
||||
// react to clicks
|
||||
if (currentState != NO_ANIMATION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getType() == ClickEvent::RELEASED)
|
||||
{
|
||||
// Save current position for use during animation
|
||||
animateDistance = dragX;
|
||||
startX = pages.getX();
|
||||
const uint8_t curPage = getSelectedPage();
|
||||
if (dragX < 0)
|
||||
{
|
||||
if (curPage == getNumberOfPages() - 1 || dragX > -swipeCutoff)
|
||||
{
|
||||
currentState = ANIMATE_SWIPE_CANCELLED_LEFT;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = ANIMATE_LEFT;
|
||||
}
|
||||
}
|
||||
else if (dragX > 0)
|
||||
{
|
||||
if (curPage == 0 || dragX < swipeCutoff)
|
||||
{
|
||||
currentState = ANIMATE_SWIPE_CANCELLED_RIGHT;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = ANIMATE_RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
// If an animation is already in progress do not
|
||||
// react to drags
|
||||
if (currentState != NO_ANIMATION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dragX += event.getDeltaX();
|
||||
|
||||
// Do not show too much background next to end pages
|
||||
const uint8_t curPage = getSelectedPage();
|
||||
if (curPage == 0 && dragX > endElasticWidth)
|
||||
{
|
||||
dragX = static_cast<int16_t>(endElasticWidth);
|
||||
}
|
||||
else if (curPage == getNumberOfPages() - 1 && dragX < -endElasticWidth)
|
||||
{
|
||||
dragX = -static_cast<int16_t>(endElasticWidth);
|
||||
}
|
||||
|
||||
adjustPages();
|
||||
}
|
||||
|
||||
void SwipeContainer::handleGestureEvent(const GestureEvent& event)
|
||||
{
|
||||
// Do not accept gestures while animating
|
||||
if (currentState != NO_ANIMATION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getType() == GestureEvent::SWIPE_HORIZONTAL)
|
||||
{
|
||||
// Save current position for use during animation
|
||||
animateDistance = dragX;
|
||||
startX = pages.getX();
|
||||
|
||||
const uint8_t curPage = getSelectedPage();
|
||||
if (event.getVelocity() < 0 && curPage < getNumberOfPages() - 1)
|
||||
{
|
||||
currentState = ANIMATE_LEFT;
|
||||
}
|
||||
else if (event.getVelocity() > 0 && curPage > 0)
|
||||
{
|
||||
currentState = ANIMATE_RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::adjustPages()
|
||||
{
|
||||
pages.moveTo(-static_cast<int16_t>(getSelectedPage() * getWidth()) + dragX, 0);
|
||||
}
|
||||
|
||||
void SwipeContainer::animateSwipeCancelledLeft()
|
||||
{
|
||||
const uint8_t duration = 14;
|
||||
|
||||
if (animationCounter <= duration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::backEaseOut(animationCounter, 0, -animateDistance, duration);
|
||||
dragX = animateDistance + delta;
|
||||
|
||||
adjustPages();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
adjustPages();
|
||||
}
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
void SwipeContainer::animateSwipeCancelledRight()
|
||||
{
|
||||
const uint8_t duration = 14;
|
||||
|
||||
if (animationCounter <= duration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::backEaseOut(animationCounter, 0, animateDistance, duration);
|
||||
dragX = animateDistance - delta;
|
||||
|
||||
adjustPages();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
adjustPages();
|
||||
}
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
void SwipeContainer::animateLeft()
|
||||
{
|
||||
const uint8_t duration = 10;
|
||||
|
||||
if (animationCounter <= duration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::cubicEaseOut(animationCounter, 0, getWidth() + animateDistance, duration);
|
||||
dragX = animateDistance - delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
pageIndicator.goRight();
|
||||
}
|
||||
adjustPages();
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
void SwipeContainer::animateRight()
|
||||
{
|
||||
const uint8_t duration = 10;
|
||||
|
||||
if (animationCounter <= duration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::cubicEaseOut(animationCounter, 0, getWidth() - animateDistance, duration);
|
||||
dragX = animateDistance + delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
pageIndicator.goLeft();
|
||||
}
|
||||
adjustPages();
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
SwipeContainer::PageIndicator::PageIndicator()
|
||||
: Container(),
|
||||
unselectedPages(),
|
||||
selectedPage(),
|
||||
numberOfPages(0),
|
||||
currentPage(0)
|
||||
{
|
||||
unselectedPages.setXY(0, 0);
|
||||
selectedPage.setXY(0, 0);
|
||||
|
||||
Container::add(unselectedPages);
|
||||
Container::add(selectedPage);
|
||||
}
|
||||
|
||||
void SwipeContainer::PageIndicator::setNumberOfPages(uint8_t size)
|
||||
{
|
||||
numberOfPages = size;
|
||||
|
||||
if (unselectedPages.getBitmapId() != BITMAP_INVALID)
|
||||
{
|
||||
const int dotWidth = Bitmap(unselectedPages.getBitmap()).getWidth();
|
||||
unselectedPages.setWidth(dotWidth * size);
|
||||
|
||||
// adjust size of container according to the actual bitmaps
|
||||
invalidate();
|
||||
setWidthHeight(unselectedPages);
|
||||
setCurrentPage(MIN(size, currentPage));
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::PageIndicator::setBitmaps(const Bitmap& normalPage, const Bitmap& highlightedPage)
|
||||
{
|
||||
selectedPage.setBitmap(highlightedPage);
|
||||
unselectedPages.setBitmap(normalPage);
|
||||
if (numberOfPages > 0)
|
||||
{
|
||||
setNumberOfPages(numberOfPages);
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::PageIndicator::goRight()
|
||||
{
|
||||
setCurrentPage((currentPage + 1) % numberOfPages);
|
||||
}
|
||||
|
||||
void SwipeContainer::PageIndicator::goLeft()
|
||||
{
|
||||
setCurrentPage((currentPage + numberOfPages - 1) % numberOfPages);
|
||||
}
|
||||
|
||||
void SwipeContainer::PageIndicator::setCurrentPage(uint8_t page)
|
||||
{
|
||||
if (page < numberOfPages && page != currentPage)
|
||||
{
|
||||
currentPage = page;
|
||||
const int dotWidth = Bitmap(unselectedPages.getBitmap()).getWidth();
|
||||
selectedPage.moveTo(page * dotWidth, selectedPage.getY());
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SwipeContainer::PageIndicator::getNumberOfPages() const
|
||||
{
|
||||
return numberOfPages;
|
||||
}
|
||||
|
||||
uint8_t SwipeContainer::PageIndicator::getCurrentPage() const
|
||||
{
|
||||
return currentPage;
|
||||
}
|
||||
|
||||
void SwipeContainer::goNextPage(uint8_t duration)
|
||||
{
|
||||
animationDuration = duration;
|
||||
if (getSelectedPage() < getNumberOfPages() - 1)
|
||||
{
|
||||
currentState = ANIMATE_LEFT_WITH_BUTTON;
|
||||
}
|
||||
}
|
||||
|
||||
void SwipeContainer::goPreviousPage(uint8_t duration)
|
||||
{
|
||||
animationDuration = duration;
|
||||
if (getSelectedPage() > 0)
|
||||
{
|
||||
currentState = ANIMATE_RIGHT_WITH_BUTTON;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SwipeContainer::getAnimationDuration() const
|
||||
{
|
||||
return animationDuration;
|
||||
}
|
||||
|
||||
void SwipeContainer::setAnimationDuration(uint8_t newDuration)
|
||||
{
|
||||
animationDuration = newDuration > 0 ? newDuration : 0;
|
||||
}
|
||||
|
||||
void SwipeContainer::animateLeftWithButton()
|
||||
{
|
||||
if (animationCounter <= animationDuration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::cubicEaseOut(animationCounter, 0, getWidth() + animateDistance, animationDuration);
|
||||
dragX = animateDistance - delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
pageIndicator.goRight();
|
||||
}
|
||||
adjustPages();
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
void SwipeContainer::animateRightWithButton()
|
||||
{
|
||||
if (animationCounter <= animationDuration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::cubicEaseOut(animationCounter, 0, getWidth() - animateDistance, animationDuration);
|
||||
dragX = animateDistance + delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
pageIndicator.goLeft();
|
||||
}
|
||||
adjustPages();
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
void SwipeContainer::animateToPage()
|
||||
{
|
||||
if (animationCounter <= animationDuration)
|
||||
{
|
||||
const int16_t delta = EasingEquations::cubicEaseOut(animationCounter, 0, getWidth() * pageDelta + animateDistance, animationDuration);
|
||||
dragX = animateDistance - delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Final step: stop the animation
|
||||
currentState = NO_ANIMATION;
|
||||
animationCounter = 0;
|
||||
dragX = 0;
|
||||
pageIndicator.setCurrentPage(getSelectedPage() + pageDelta);
|
||||
}
|
||||
adjustPages();
|
||||
animationCounter++;
|
||||
}
|
||||
|
||||
void SwipeContainer::goToPage(uint8_t page, uint8_t duration)
|
||||
{
|
||||
animationDuration = duration;
|
||||
if (page < getNumberOfPages() && page != getSelectedPage())
|
||||
{
|
||||
pageDelta = page - getSelectedPage();
|
||||
currentState = ANIMATE_TO_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/containers/ZoomAnimationImage.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ZoomAnimationImage::ZoomAnimationImage()
|
||||
: Container(),
|
||||
currentState(NO_ANIMATION),
|
||||
animationCounter(0),
|
||||
zoomAnimationDelay(0),
|
||||
smallBmp(),
|
||||
largeBmp(),
|
||||
image(),
|
||||
scalableImage(),
|
||||
currentZoomMode(FIXED_LEFT_AND_TOP),
|
||||
zoomAnimationStartWidth(0),
|
||||
zoomAnimationStartHeight(0),
|
||||
zoomAnimationEndWidth(0),
|
||||
zoomAnimationEndHeight(0),
|
||||
zoomAnimationStartX(0),
|
||||
zoomAnimationStartY(0),
|
||||
zoomAnimationDeltaX(0),
|
||||
zoomAnimationDeltaY(0),
|
||||
moveAnimationEndX(0),
|
||||
moveAnimationEndY(0),
|
||||
animationDuration(0),
|
||||
zoomAnimationWidthEquation(EasingEquations::linearEaseNone),
|
||||
zoomAnimationHeightEquation(EasingEquations::linearEaseNone),
|
||||
moveAnimationXEquation(EasingEquations::linearEaseNone),
|
||||
moveAnimationYEquation(EasingEquations::linearEaseNone),
|
||||
animationEndedAction(0)
|
||||
{
|
||||
image.setXY(0, 0);
|
||||
image.setVisible(false);
|
||||
|
||||
scalableImage.setScalingAlgorithm(ScalableImage::NEAREST_NEIGHBOR);
|
||||
scalableImage.setXY(0, 0);
|
||||
scalableImage.setVisible(false);
|
||||
|
||||
Container::add(image);
|
||||
Container::add(scalableImage);
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::startZoomAnimation(int16_t endWidth, int16_t endHeight, uint16_t duration, ZoomMode zoomMode, EasingEquation widthProgressionEquation, EasingEquation heightProgressionEquation)
|
||||
{
|
||||
setCurrentState(ANIMATE_ZOOM);
|
||||
|
||||
startTimerAndSetParameters(endWidth, endHeight, duration, zoomMode, widthProgressionEquation, heightProgressionEquation);
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::startZoomAndMoveAnimation(int16_t endX, int16_t endY, int16_t endWidth, int16_t endHeight, uint16_t duration, ZoomMode zoomMode, EasingEquation xProgressionEquation, EasingEquation yProgressionEquation, EasingEquation widthProgressionEquation, EasingEquation heightProgressionEquation)
|
||||
{
|
||||
moveAnimationEndX = endX;
|
||||
moveAnimationEndY = endY;
|
||||
|
||||
moveAnimationXEquation = xProgressionEquation;
|
||||
moveAnimationYEquation = yProgressionEquation;
|
||||
|
||||
setCurrentState(ANIMATE_ZOOM_AND_MOVE);
|
||||
|
||||
startTimerAndSetParameters(endWidth, endHeight, duration, zoomMode, widthProgressionEquation, heightProgressionEquation);
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::cancelZoomAnimation()
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
setCurrentState(NO_ANIMATION);
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::handleTickEvent()
|
||||
{
|
||||
if ((currentState == ANIMATE_ZOOM) || (currentState == ANIMATE_ZOOM_AND_MOVE))
|
||||
{
|
||||
animationCounter++;
|
||||
if (animationCounter >= zoomAnimationDelay)
|
||||
{
|
||||
// Adjust the used animationCounter for the startup delay
|
||||
const uint16_t actualAnimationCounter = animationCounter - zoomAnimationDelay;
|
||||
|
||||
const int16_t deltaWidth = zoomAnimationWidthEquation(actualAnimationCounter, 0, zoomAnimationEndWidth - zoomAnimationStartWidth, animationDuration);
|
||||
const int16_t deltaHeight = zoomAnimationHeightEquation(actualAnimationCounter, 0, zoomAnimationEndHeight - zoomAnimationStartHeight, animationDuration);
|
||||
|
||||
setWidthHeight(zoomAnimationStartWidth + deltaWidth, zoomAnimationStartHeight + deltaHeight);
|
||||
|
||||
int16_t deltaX;
|
||||
int16_t deltaY;
|
||||
|
||||
if (currentState == ANIMATE_ZOOM_AND_MOVE)
|
||||
{
|
||||
deltaX = moveAnimationXEquation(actualAnimationCounter, 0, (moveAnimationEndX - zoomAnimationStartX) + zoomAnimationDeltaX, animationDuration);
|
||||
deltaY = moveAnimationYEquation(actualAnimationCounter, 0, (moveAnimationEndY - zoomAnimationStartY) + zoomAnimationDeltaY, animationDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaX = zoomAnimationWidthEquation(actualAnimationCounter, 0, zoomAnimationDeltaX, animationDuration);
|
||||
deltaY = zoomAnimationHeightEquation(actualAnimationCounter, 0, zoomAnimationDeltaY, animationDuration);
|
||||
}
|
||||
moveTo(zoomAnimationStartX + deltaX, zoomAnimationStartY + deltaY);
|
||||
|
||||
if (animationCounter >= zoomAnimationDelay + animationDuration)
|
||||
{
|
||||
cancelZoomAnimation();
|
||||
|
||||
if (animationEndedAction && animationEndedAction->isValid())
|
||||
{
|
||||
animationEndedAction->execute(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setBitmaps(const Bitmap& smallBitmap, const Bitmap& largeBitmap)
|
||||
{
|
||||
smallBmp = smallBitmap;
|
||||
largeBmp = largeBitmap;
|
||||
|
||||
scalableImage.setBitmap(largeBmp);
|
||||
|
||||
ZoomAnimationImage::setWidthHeight(largeBmp);
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setWidth(int16_t width)
|
||||
{
|
||||
invalidate();
|
||||
Container::setWidth(width);
|
||||
updateRenderingMethod();
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setHeight(int16_t height)
|
||||
{
|
||||
invalidate();
|
||||
Container::setHeight(height);
|
||||
updateRenderingMethod();
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setScalingMode(ScalableImage::ScalingAlgorithm mode)
|
||||
{
|
||||
scalableImage.setScalingAlgorithm(mode);
|
||||
}
|
||||
|
||||
ScalableImage::ScalingAlgorithm ZoomAnimationImage::getScalingMode()
|
||||
{
|
||||
return scalableImage.getScalingAlgorithm();
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setAlpha(const uint8_t newAlpha)
|
||||
{
|
||||
image.setAlpha(newAlpha);
|
||||
scalableImage.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
uint8_t ZoomAnimationImage::getAlpha() const
|
||||
{
|
||||
return image.getAlpha();
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setAnimationDelay(uint16_t delay)
|
||||
{
|
||||
zoomAnimationDelay = delay;
|
||||
}
|
||||
|
||||
uint16_t ZoomAnimationImage::getAnimationDelay() const
|
||||
{
|
||||
return zoomAnimationDelay;
|
||||
}
|
||||
|
||||
bool ZoomAnimationImage::isZoomAnimationRunning() const
|
||||
{
|
||||
return currentState != NO_ANIMATION;
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::updateRenderingMethod()
|
||||
{
|
||||
if ((smallBmp.getWidth() == getWidth()) && (smallBmp.getHeight() == getHeight()))
|
||||
{
|
||||
image.setBitmap(smallBmp); // Updates width and height
|
||||
image.setVisible(true);
|
||||
scalableImage.setVisible(false);
|
||||
}
|
||||
else if ((largeBmp.getWidth() == getWidth()) && (largeBmp.getHeight() == getHeight()))
|
||||
{
|
||||
image.setBitmap(largeBmp); // Updates width and height
|
||||
image.setVisible(true);
|
||||
scalableImage.setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.setVisible(false);
|
||||
scalableImage.setVisible(true);
|
||||
scalableImage.setWidthHeight(*this);
|
||||
}
|
||||
Container::invalidate();
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::setCurrentState(States state)
|
||||
{
|
||||
currentState = state;
|
||||
animationCounter = 0;
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::startTimerAndSetParameters(int16_t endWidth, int16_t endHeight, uint16_t duration, ZoomMode zoomMode, EasingEquation widthProgressionEquation, EasingEquation heightProgressionEquation)
|
||||
{
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
|
||||
currentZoomMode = zoomMode;
|
||||
|
||||
zoomAnimationStartX = getX();
|
||||
zoomAnimationStartY = getY();
|
||||
zoomAnimationStartWidth = getWidth();
|
||||
zoomAnimationStartHeight = getHeight();
|
||||
zoomAnimationEndWidth = endWidth;
|
||||
zoomAnimationEndHeight = endHeight;
|
||||
animationDuration = duration;
|
||||
|
||||
zoomAnimationWidthEquation = widthProgressionEquation;
|
||||
zoomAnimationHeightEquation = heightProgressionEquation;
|
||||
|
||||
updateZoomAnimationDeltaXY();
|
||||
|
||||
if (zoomAnimationDelay == 0 && animationDuration == 0)
|
||||
{
|
||||
handleTickEvent(); // Finish the zoom and move operation immediately
|
||||
}
|
||||
}
|
||||
|
||||
void ZoomAnimationImage::updateZoomAnimationDeltaXY()
|
||||
{
|
||||
zoomAnimationDeltaX = zoomAnimationStartWidth - zoomAnimationEndWidth;
|
||||
zoomAnimationDeltaY = zoomAnimationStartHeight - zoomAnimationEndHeight;
|
||||
|
||||
switch (currentZoomMode)
|
||||
{
|
||||
case FIXED_CENTER:
|
||||
zoomAnimationDeltaX /= 2;
|
||||
zoomAnimationDeltaY /= 2;
|
||||
break;
|
||||
case FIXED_LEFT:
|
||||
zoomAnimationDeltaX = 0;
|
||||
zoomAnimationDeltaY /= 2;
|
||||
break;
|
||||
case FIXED_RIGHT:
|
||||
zoomAnimationDeltaY /= 2;
|
||||
break;
|
||||
case FIXED_TOP:
|
||||
zoomAnimationDeltaX /= 2;
|
||||
zoomAnimationDeltaY = 0;
|
||||
break;
|
||||
case FIXED_BOTTOM:
|
||||
zoomAnimationDeltaX /= 2;
|
||||
break;
|
||||
case FIXED_LEFT_AND_TOP:
|
||||
zoomAnimationDeltaX = 0;
|
||||
zoomAnimationDeltaY = 0;
|
||||
break;
|
||||
case FIXED_RIGHT_AND_TOP:
|
||||
zoomAnimationDeltaY = 0;
|
||||
break;
|
||||
case FIXED_LEFT_AND_BOTTOM:
|
||||
zoomAnimationDeltaX = 0;
|
||||
break;
|
||||
case FIXED_RIGHT_AND_BOTTOM:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/clock/AbstractClock.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AbstractClock::AbstractClock()
|
||||
: Container(),
|
||||
currentHour(0),
|
||||
currentMinute(0),
|
||||
currentSecond(0)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractClock::setTime24Hour(uint8_t hour, uint8_t minute, uint8_t second)
|
||||
{
|
||||
currentHour = hour % 24;
|
||||
currentMinute = minute % 60;
|
||||
currentSecond = second % 60;
|
||||
|
||||
updateClock();
|
||||
}
|
||||
|
||||
void AbstractClock::setTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am)
|
||||
{
|
||||
setTime24Hour((hour % 12) + (am ? 0 : 12), minute, second);
|
||||
}
|
||||
|
||||
uint8_t AbstractClock::getCurrentHour() const
|
||||
{
|
||||
return currentHour;
|
||||
}
|
||||
|
||||
uint8_t AbstractClock::getCurrentHour24() const
|
||||
{
|
||||
return currentHour;
|
||||
}
|
||||
|
||||
uint8_t AbstractClock::getCurrentHour12() const
|
||||
{
|
||||
return ((currentHour + 11) % 12) + 1;
|
||||
}
|
||||
|
||||
bool AbstractClock::getCurrentAM() const
|
||||
{
|
||||
return currentHour < 12;
|
||||
}
|
||||
|
||||
uint8_t AbstractClock::getCurrentMinute() const
|
||||
{
|
||||
return currentMinute;
|
||||
}
|
||||
|
||||
uint8_t AbstractClock::getCurrentSecond() const
|
||||
{
|
||||
return currentSecond;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/clock/AnalogClock.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AnalogClock::AnalogClock()
|
||||
: AbstractClock(),
|
||||
background(),
|
||||
hourHand(),
|
||||
minuteHand(),
|
||||
secondHand(),
|
||||
animationEquation(EasingEquations::linearEaseNone),
|
||||
animationDuration(0),
|
||||
clockRotationCenterX(0),
|
||||
clockRotationCenterY(0),
|
||||
lastHour(0),
|
||||
lastMinute(0),
|
||||
lastSecond(0),
|
||||
hourHandMinuteCorrectionActive(false),
|
||||
minuteHandSecondCorrectionActive(false)
|
||||
{
|
||||
AnalogClock::add(background);
|
||||
|
||||
hourHand.updateZAngle(0.f);
|
||||
minuteHand.updateZAngle(0.f);
|
||||
secondHand.updateZAngle(0.f);
|
||||
|
||||
hourHand.setVisible(false);
|
||||
minuteHand.setVisible(false);
|
||||
secondHand.setVisible(false);
|
||||
}
|
||||
|
||||
void AnalogClock::setBackground(const BitmapId backgroundBitmapId)
|
||||
{
|
||||
setBackground(backgroundBitmapId, Bitmap(backgroundBitmapId).getWidth() / 2, Bitmap(backgroundBitmapId).getHeight() / 2);
|
||||
}
|
||||
|
||||
void AnalogClock::setBackground(const BitmapId backgroundBitmapId, const int16_t rotationCenterX, const int16_t rotationCenterY)
|
||||
{
|
||||
background.setBitmap(Bitmap(backgroundBitmapId));
|
||||
setWidthHeight(background);
|
||||
|
||||
setRotationCenter(rotationCenterX, rotationCenterY);
|
||||
}
|
||||
|
||||
void AnalogClock::setRotationCenter(int16_t rotationCenterX, int16_t rotationCenterY)
|
||||
{
|
||||
clockRotationCenterX = rotationCenterX;
|
||||
clockRotationCenterY = rotationCenterY;
|
||||
}
|
||||
|
||||
void AnalogClock::setupHourHand(const BitmapId hourHandBitmapId, int16_t rotationCenterX, int16_t rotationCenterY)
|
||||
{
|
||||
setupHand(hourHand, hourHandBitmapId, rotationCenterX, rotationCenterY);
|
||||
}
|
||||
|
||||
void AnalogClock::setupMinuteHand(const BitmapId minuteHandBitmapId, int16_t rotationCenterX, int16_t rotationCenterY)
|
||||
{
|
||||
setupHand(minuteHand, minuteHandBitmapId, rotationCenterX, rotationCenterY);
|
||||
}
|
||||
|
||||
void AnalogClock::setupSecondHand(const BitmapId secondHandBitmapId, int16_t rotationCenterX, int16_t rotationCenterY)
|
||||
{
|
||||
setupHand(secondHand, secondHandBitmapId, rotationCenterX, rotationCenterY);
|
||||
}
|
||||
|
||||
void AnalogClock::setupHand(TextureMapper& hand, const BitmapId bitmapId, int16_t rotationCenterX, int16_t rotationCenterY)
|
||||
{
|
||||
remove(hand);
|
||||
|
||||
hand.setBitmap(Bitmap(bitmapId));
|
||||
hand.setWidthHeight(*this);
|
||||
hand.setXY(0, 0);
|
||||
hand.setBitmapPosition(clockRotationCenterX - rotationCenterX, clockRotationCenterY - rotationCenterY);
|
||||
hand.setCameraDistance(300.0f);
|
||||
hand.setOrigo((float)clockRotationCenterX, (float)clockRotationCenterY, hand.getCameraDistance());
|
||||
hand.setCamera(hand.getOrigoX(), hand.getOrigoY());
|
||||
hand.setRenderingAlgorithm(TextureMapper::BILINEAR_INTERPOLATION);
|
||||
|
||||
add(hand);
|
||||
hand.setVisible(true);
|
||||
}
|
||||
|
||||
void AnalogClock::initializeTime24Hour(uint8_t hour, uint8_t minute, uint8_t second)
|
||||
{
|
||||
lastHour = 255;
|
||||
lastMinute = 255;
|
||||
lastSecond = 255;
|
||||
|
||||
// Disable animation and set time
|
||||
const uint16_t tempAnimationDuration = animationDuration;
|
||||
animationDuration = 1;
|
||||
setTime24Hour(hour, minute, second);
|
||||
|
||||
animationDuration = tempAnimationDuration;
|
||||
}
|
||||
|
||||
void AnalogClock::initializeTime12Hour(uint8_t hour, uint8_t minute, uint8_t second, bool am)
|
||||
{
|
||||
initializeTime24Hour((hour % 12) + (am ? 0 : 12), minute, second);
|
||||
}
|
||||
|
||||
void AnalogClock::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
background.setAlpha(newAlpha);
|
||||
hourHand.setAlpha(newAlpha);
|
||||
minuteHand.setAlpha(newAlpha);
|
||||
secondHand.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
uint8_t AnalogClock::getAlpha() const
|
||||
{
|
||||
return background.getAlpha();
|
||||
}
|
||||
|
||||
void AnalogClock::updateClock()
|
||||
{
|
||||
float newHandAngle;
|
||||
float oldHandAngle;
|
||||
int16_t diff;
|
||||
|
||||
// Move hour hand
|
||||
if (hourHand.isVisible() && ((currentHour % 12 != lastHour % 12) || (hourHandMinuteCorrectionActive && (currentMinute != lastMinute))))
|
||||
{
|
||||
newHandAngle = convertHandValueToAngle(12, currentHour % 12, hourHandMinuteCorrectionActive ? currentMinute : 0);
|
||||
|
||||
// check if sign of absolute angles difference matches sign of normalized difference of angles
|
||||
oldHandAngle = hourHand.getZAngle();
|
||||
diff = (((int16_t)currentHour - (int16_t)lastHour + 24 + 6) % 12) - 6;
|
||||
if (diff > 0 && newHandAngle < oldHandAngle)
|
||||
{
|
||||
while (newHandAngle < oldHandAngle) // correct old angle to force clockwise rotation
|
||||
{
|
||||
oldHandAngle -= (2 * PI);
|
||||
}
|
||||
hourHand.updateZAngle(oldHandAngle);
|
||||
}
|
||||
else if (diff < 0 && newHandAngle > oldHandAngle)
|
||||
{
|
||||
while (newHandAngle > oldHandAngle) // correct old angle to force counterclockwise rotation
|
||||
{
|
||||
oldHandAngle += (2 * PI);
|
||||
}
|
||||
hourHand.updateZAngle(oldHandAngle);
|
||||
}
|
||||
|
||||
if (animationEnabled() && !hourHand.isTextureMapperAnimationRunning())
|
||||
{
|
||||
hourHand.setupAnimation(AnimationTextureMapper::Z_ROTATION, newHandAngle, animationDuration, 0, animationEquation);
|
||||
hourHand.startAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animationEnabled())
|
||||
{
|
||||
hourHand.cancelAnimationTextureMapperAnimation();
|
||||
}
|
||||
hourHand.updateZAngle(newHandAngle);
|
||||
}
|
||||
}
|
||||
|
||||
// Move minute hand
|
||||
if (minuteHand.isVisible() && ((currentMinute != lastMinute) || (minuteHandSecondCorrectionActive && (currentSecond != lastSecond))))
|
||||
{
|
||||
newHandAngle = convertHandValueToAngle(60, currentMinute, minuteHandSecondCorrectionActive ? currentSecond : 0);
|
||||
|
||||
// check if sign of absolute angles difference matches sign of normalized difference of angles
|
||||
oldHandAngle = minuteHand.getZAngle();
|
||||
diff = (((int16_t)currentMinute - (int16_t)lastMinute + 60 + 30) % 60) - 30;
|
||||
if (diff > 0 && newHandAngle < oldHandAngle)
|
||||
{
|
||||
while (newHandAngle < oldHandAngle) // correct old angle to force clockwise rotation
|
||||
{
|
||||
oldHandAngle -= (2 * PI);
|
||||
}
|
||||
minuteHand.updateZAngle(oldHandAngle);
|
||||
}
|
||||
else if (diff < 0 && newHandAngle > oldHandAngle)
|
||||
{
|
||||
while (newHandAngle > oldHandAngle) // correct old angle to force counterclockwise rotation
|
||||
{
|
||||
oldHandAngle += (2 * PI);
|
||||
}
|
||||
minuteHand.updateZAngle(oldHandAngle);
|
||||
}
|
||||
|
||||
if (animationEnabled() && !minuteHand.isTextureMapperAnimationRunning())
|
||||
{
|
||||
minuteHand.setupAnimation(AnimationTextureMapper::Z_ROTATION, newHandAngle, animationDuration, 0, animationEquation);
|
||||
minuteHand.startAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animationEnabled())
|
||||
{
|
||||
minuteHand.cancelAnimationTextureMapperAnimation();
|
||||
}
|
||||
minuteHand.updateZAngle(newHandAngle);
|
||||
}
|
||||
}
|
||||
|
||||
// Move second hand
|
||||
if (secondHand.isVisible() && (currentSecond != lastSecond))
|
||||
{
|
||||
newHandAngle = convertHandValueToAngle(60, currentSecond);
|
||||
|
||||
// check if sign of absolute angles difference matches sign of normalized difference of angles
|
||||
oldHandAngle = secondHand.getZAngle();
|
||||
diff = (((int16_t)currentSecond - (int16_t)lastSecond + 60 + 30) % 60) - 30;
|
||||
if (diff > 0 && newHandAngle < oldHandAngle)
|
||||
{
|
||||
while (newHandAngle < oldHandAngle) // correct old angle to force clockwise rotation
|
||||
{
|
||||
oldHandAngle -= (2 * PI);
|
||||
}
|
||||
secondHand.updateZAngle(oldHandAngle);
|
||||
}
|
||||
else if (diff < 0 && newHandAngle > oldHandAngle)
|
||||
{
|
||||
while (newHandAngle > oldHandAngle) // correct old angle to force counterclockwise rotation
|
||||
{
|
||||
oldHandAngle += (2 * PI);
|
||||
}
|
||||
secondHand.updateZAngle(oldHandAngle);
|
||||
}
|
||||
|
||||
if (animationEnabled() && !secondHand.isTextureMapperAnimationRunning())
|
||||
{
|
||||
secondHand.setupAnimation(AnimationTextureMapper::Z_ROTATION, newHandAngle, animationDuration, 0, animationEquation);
|
||||
secondHand.startAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animationEnabled())
|
||||
{
|
||||
secondHand.cancelAnimationTextureMapperAnimation();
|
||||
}
|
||||
secondHand.updateZAngle(newHandAngle);
|
||||
}
|
||||
}
|
||||
|
||||
lastHour = currentHour;
|
||||
lastMinute = currentMinute;
|
||||
lastSecond = currentSecond;
|
||||
}
|
||||
|
||||
float AnalogClock::convertHandValueToAngle(uint8_t steps, uint8_t handValue, uint8_t secondHandValue /*= 0*/) const
|
||||
{
|
||||
return ((handValue / (float)steps) + (secondHandValue / (steps * 60.f))) * 2 * PI;
|
||||
}
|
||||
|
||||
void AnalogClock::setHourHandMinuteCorrection(bool active)
|
||||
{
|
||||
hourHandMinuteCorrectionActive = active;
|
||||
setTime24Hour(getCurrentHour(), getCurrentMinute(), getCurrentSecond());
|
||||
}
|
||||
|
||||
bool AnalogClock::getHourHandMinuteCorrection() const
|
||||
{
|
||||
return hourHandMinuteCorrectionActive;
|
||||
}
|
||||
|
||||
void AnalogClock::setMinuteHandSecondCorrection(bool active)
|
||||
{
|
||||
minuteHandSecondCorrectionActive = active;
|
||||
setTime24Hour(getCurrentHour(), getCurrentMinute(), getCurrentSecond());
|
||||
}
|
||||
|
||||
bool AnalogClock::getMinuteHandSecondCorrection() const
|
||||
{
|
||||
return minuteHandSecondCorrectionActive;
|
||||
}
|
||||
|
||||
bool AnalogClock::animationEnabled() const
|
||||
{
|
||||
return animationDuration > 1;
|
||||
}
|
||||
|
||||
void AnalogClock::setAnimation(uint16_t duration, EasingEquation animationProgressionEquation)
|
||||
{
|
||||
animationDuration = duration;
|
||||
animationEquation = animationProgressionEquation;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Unicode.hpp>
|
||||
#include <touchgfx/containers/Container.hpp>
|
||||
#include <touchgfx/containers/clock/DigitalClock.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
DigitalClock::DigitalClock()
|
||||
: AbstractClock(),
|
||||
displayMode(DISPLAY_24_HOUR),
|
||||
useLeadingZeroForHourIndicator(false),
|
||||
text()
|
||||
{
|
||||
buffer[0] = '\0';
|
||||
text.setXY(0, 0);
|
||||
text.setWildcard(buffer);
|
||||
AbstractClock::add(text);
|
||||
}
|
||||
|
||||
void DigitalClock::setWidth(int16_t width)
|
||||
{
|
||||
AbstractClock::setWidth(width);
|
||||
text.setWidth(width);
|
||||
}
|
||||
|
||||
void DigitalClock::setHeight(int16_t height)
|
||||
{
|
||||
AbstractClock::setHeight(height);
|
||||
text.setHeight(height);
|
||||
}
|
||||
|
||||
void DigitalClock::setBaselineY(int16_t baselineY)
|
||||
{
|
||||
if (text.getTypedText().hasValidId())
|
||||
{
|
||||
moveTo(getX(), baselineY - text.getTypedText().getFont()->getBaseline());
|
||||
}
|
||||
}
|
||||
|
||||
void DigitalClock::displayLeadingZeroForHourIndicator(bool displayLeadingZero)
|
||||
{
|
||||
useLeadingZeroForHourIndicator = displayLeadingZero;
|
||||
}
|
||||
|
||||
void DigitalClock::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
text.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
uint8_t DigitalClock::getAlpha() const
|
||||
{
|
||||
return text.getAlpha();
|
||||
}
|
||||
|
||||
void DigitalClock::setTypedText(TypedText typedText)
|
||||
{
|
||||
// Do invalidateContent before and after in case the size of the text changes
|
||||
text.invalidateContent();
|
||||
text.setTypedText(typedText);
|
||||
text.invalidateContent();
|
||||
}
|
||||
|
||||
void DigitalClock::setColor(colortype color)
|
||||
{
|
||||
// Do invlidateContent only once since the size does not change
|
||||
text.setColor(color);
|
||||
text.invalidateContent();
|
||||
}
|
||||
|
||||
colortype DigitalClock::getColor() const
|
||||
{
|
||||
return text.getColor();
|
||||
}
|
||||
|
||||
void DigitalClock::updateClock()
|
||||
{
|
||||
text.invalidateContent();
|
||||
if (displayMode == DISPLAY_12_HOUR_NO_SECONDS)
|
||||
{
|
||||
const char* format = useLeadingZeroForHourIndicator ? "%02d:%02d %cM" : "%d:%02d %cM";
|
||||
Unicode::snprintf(buffer, BUFFER_SIZE, format, getCurrentHour12(), getCurrentMinute(), getCurrentAM() ? 'A' : 'P');
|
||||
}
|
||||
else if (displayMode == DISPLAY_24_HOUR_NO_SECONDS)
|
||||
{
|
||||
const char* format = useLeadingZeroForHourIndicator ? "%02d:%02d" : "%d:%02d";
|
||||
Unicode::snprintf(buffer, BUFFER_SIZE, format, getCurrentHour24(), getCurrentMinute());
|
||||
}
|
||||
else if (displayMode == DISPLAY_12_HOUR)
|
||||
{
|
||||
const char* format = useLeadingZeroForHourIndicator ? "%02d:%02d:%02d %cM" : "%d:%02d:%02d %cM";
|
||||
Unicode::snprintf(buffer, BUFFER_SIZE, format, getCurrentHour12(), getCurrentMinute(), getCurrentSecond(), getCurrentAM() ? 'A' : 'P');
|
||||
}
|
||||
else if (displayMode == DISPLAY_24_HOUR)
|
||||
{
|
||||
const char* format = useLeadingZeroForHourIndicator ? "%02d:%02d:%02d" : "%d:%02d:%02d";
|
||||
Unicode::snprintf(buffer, BUFFER_SIZE, format, getCurrentHour24(), getCurrentMinute(), getCurrentSecond());
|
||||
}
|
||||
text.invalidateContent();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/progress_indicators/AbstractDirectionProgress.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AbstractDirectionProgress::AbstractDirectionProgress()
|
||||
: AbstractProgressIndicator(), progressDirection(RIGHT)
|
||||
{
|
||||
AbstractDirectionProgress::setDirection(RIGHT);
|
||||
}
|
||||
|
||||
void AbstractDirectionProgress::setDirection(DirectionType direction)
|
||||
{
|
||||
if (direction != progressDirection)
|
||||
{
|
||||
progressDirection = direction;
|
||||
progressIndicatorContainer.invalidate();
|
||||
setValue(getValue());
|
||||
}
|
||||
}
|
||||
|
||||
AbstractDirectionProgress::DirectionType AbstractDirectionProgress::getDirection() const
|
||||
{
|
||||
return progressDirection;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/progress_indicators/AbstractProgressIndicator.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AbstractProgressIndicator::AbstractProgressIndicator()
|
||||
: Container(), background(), progressIndicatorContainer(), rangeMin(0), rangeMax(100), currentValue(0), rangeSteps(100), rangeStepsMin(0),
|
||||
equation(&EasingEquations::linearEaseNone), animationRunning(false), animationStartValue(0), animationEndValue(0), animationDuration(0), animationStep(0),
|
||||
valueSetCallback(0), valueUpdatedCallback(0)
|
||||
{
|
||||
background.setXY(0, 0);
|
||||
AbstractProgressIndicator::add(background);
|
||||
AbstractProgressIndicator::add(progressIndicatorContainer);
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setBackground(const Bitmap& bitmapBackground)
|
||||
{
|
||||
background.setBitmap(bitmapBackground);
|
||||
setWidthHeight(background);
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setProgressIndicatorPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
progressIndicatorContainer.setPosition(x, y, width, height);
|
||||
|
||||
if (getWidth() < x + width)
|
||||
{
|
||||
AbstractProgressIndicator::setWidth(x + width);
|
||||
}
|
||||
|
||||
if (getHeight() < y + height)
|
||||
{
|
||||
AbstractProgressIndicator::setHeight(y + height);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t AbstractProgressIndicator::getProgressIndicatorX() const
|
||||
{
|
||||
return progressIndicatorContainer.getX();
|
||||
}
|
||||
|
||||
int16_t AbstractProgressIndicator::getProgressIndicatorY() const
|
||||
{
|
||||
return progressIndicatorContainer.getY();
|
||||
}
|
||||
|
||||
int16_t AbstractProgressIndicator::getProgressIndicatorWidth() const
|
||||
{
|
||||
return progressIndicatorContainer.getWidth();
|
||||
}
|
||||
|
||||
int16_t AbstractProgressIndicator::getProgressIndicatorHeight() const
|
||||
{
|
||||
return progressIndicatorContainer.getHeight();
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setRange(int min, int max, uint16_t steps /*= 0*/, uint16_t minStep /*= 0*/)
|
||||
{
|
||||
assert(min < max);
|
||||
rangeMin = min;
|
||||
rangeMax = max;
|
||||
setValue(currentValue);
|
||||
if (steps == 0)
|
||||
{
|
||||
rangeSteps = max - min;
|
||||
}
|
||||
else
|
||||
{
|
||||
rangeSteps = steps;
|
||||
}
|
||||
rangeStepsMin = minStep;
|
||||
assert(rangeStepsMin < rangeSteps);
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::getRange(int& min, int& max, uint16_t& steps, uint16_t& minStep) const
|
||||
{
|
||||
min = rangeMin;
|
||||
max = rangeMax;
|
||||
steps = rangeSteps;
|
||||
minStep = rangeStepsMin;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::getRange(int& min, int& max, uint16_t& steps) const
|
||||
{
|
||||
min = rangeMin;
|
||||
max = rangeMax;
|
||||
steps = rangeSteps;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::getRange(int& min, int& max) const
|
||||
{
|
||||
min = rangeMin;
|
||||
max = rangeMax;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setValue(int value)
|
||||
{
|
||||
value = MAX(value, rangeMin);
|
||||
value = MIN(value, rangeMax);
|
||||
if (value != currentValue)
|
||||
{
|
||||
currentValue = value;
|
||||
if (valueSetCallback && valueSetCallback->isValid())
|
||||
{
|
||||
valueSetCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setEasingEquation(EasingEquation easingEquation)
|
||||
{
|
||||
equation = easingEquation;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::updateValue(int value, uint16_t duration)
|
||||
{
|
||||
value = MAX(value, rangeMin);
|
||||
value = MIN(value, rangeMax);
|
||||
if (duration == 0)
|
||||
{
|
||||
setValue(value);
|
||||
if (valueUpdatedCallback && valueUpdatedCallback->isValid())
|
||||
{
|
||||
valueUpdatedCallback->execute(*this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (animationDuration > 0)
|
||||
{
|
||||
// Old animation is running, stop it first
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
animationRunning = false;
|
||||
}
|
||||
animationStartValue = getValue();
|
||||
animationEndValue = value;
|
||||
animationDuration = duration;
|
||||
animationStep = 0;
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
animationRunning = true;
|
||||
}
|
||||
|
||||
int AbstractProgressIndicator::getValue() const
|
||||
{
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
uint16_t AbstractProgressIndicator::getProgress(uint16_t range /*= 100*/) const
|
||||
{
|
||||
if (range == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int32_t remainder; // Not used here
|
||||
// Find out at what step the current value is.
|
||||
const int32_t step = rangeStepsMin + muldiv(currentValue - rangeMin, rangeSteps - rangeStepsMin, rangeMax - rangeMin, remainder);
|
||||
// Scale the step up to [0..range]
|
||||
const int32_t prog = muldiv(step, range, rangeSteps, remainder);
|
||||
return (uint16_t)prog;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setValueSetAction(GenericCallback<const AbstractProgressIndicator&>& callback)
|
||||
{
|
||||
valueSetCallback = &callback;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::handleTickEvent()
|
||||
{
|
||||
if (!animationRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
animationStep++;
|
||||
const int16_t delta = (int16_t)equation(animationStep, 0, animationEndValue - animationStartValue, animationDuration);
|
||||
setValue(animationStartValue + delta);
|
||||
if (animationStep >= animationDuration)
|
||||
{
|
||||
animationDuration = 0;
|
||||
animationStep = 0;
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
animationRunning = false;
|
||||
if (valueUpdatedCallback && valueUpdatedCallback->isValid())
|
||||
{
|
||||
valueUpdatedCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setValueUpdatedAction(GenericCallback<const AbstractProgressIndicator&>& callback)
|
||||
{
|
||||
valueUpdatedCallback = &callback;
|
||||
}
|
||||
|
||||
void AbstractProgressIndicator::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
background.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
uint8_t AbstractProgressIndicator::getAlpha() const
|
||||
{
|
||||
return background.getAlpha();
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/progress_indicators/BoxProgress.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
BoxProgress::BoxProgress()
|
||||
: AbstractDirectionProgress(), box()
|
||||
{
|
||||
progressIndicatorContainer.add(box);
|
||||
}
|
||||
|
||||
void BoxProgress::setProgressIndicatorPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
box.setPosition(0, 0, width, height);
|
||||
|
||||
AbstractDirectionProgress::setProgressIndicatorPosition(x, y, width, height);
|
||||
}
|
||||
|
||||
void BoxProgress::setColor(colortype color)
|
||||
{
|
||||
box.setColor(color);
|
||||
}
|
||||
|
||||
colortype BoxProgress::getColor() const
|
||||
{
|
||||
return box.getColor();
|
||||
}
|
||||
|
||||
void BoxProgress::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
AbstractDirectionProgress::setAlpha(newAlpha);
|
||||
box.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
void BoxProgress::setValue(int value)
|
||||
{
|
||||
AbstractDirectionProgress::setValue(value);
|
||||
int16_t progress = 0;
|
||||
switch (progressDirection)
|
||||
{
|
||||
case RIGHT:
|
||||
case LEFT:
|
||||
progress = AbstractDirectionProgress::getProgress(progressIndicatorContainer.getWidth());
|
||||
break;
|
||||
case DOWN:
|
||||
case UP:
|
||||
progress = AbstractDirectionProgress::getProgress(progressIndicatorContainer.getHeight());
|
||||
break;
|
||||
}
|
||||
switch (progressDirection)
|
||||
{
|
||||
case RIGHT:
|
||||
{
|
||||
const int16_t oldWidth = box.getWidth();
|
||||
box.setPosition(0, 0, progress, progressIndicatorContainer.getHeight());
|
||||
const int16_t newWidth = box.getWidth();
|
||||
Rect r(MIN(oldWidth, newWidth), 0, abs(oldWidth - newWidth), box.getHeight());
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
case LEFT:
|
||||
{
|
||||
const int16_t oldX = box.getX();
|
||||
box.setPosition(getWidth() - progress, 0, progress, progressIndicatorContainer.getHeight());
|
||||
const int16_t newX = box.getX();
|
||||
Rect r(MIN(oldX, newX), 0, abs(oldX - newX), box.getHeight());
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
case DOWN:
|
||||
{
|
||||
const int16_t oldHeight = box.getHeight();
|
||||
box.setPosition(0, 0, progressIndicatorContainer.getWidth(), progress);
|
||||
const int16_t newHeight = box.getHeight();
|
||||
Rect r(0, MIN(oldHeight, newHeight), box.getWidth(), abs(oldHeight - newHeight));
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
case UP:
|
||||
{
|
||||
const int16_t oldY = box.getY();
|
||||
box.setPosition(0, progressIndicatorContainer.getHeight() - progress, progressIndicatorContainer.getWidth(), progress);
|
||||
const int16_t newY = box.getY();
|
||||
Rect r(0, MIN(oldY, newY), box.getWidth(), abs(oldY - newY));
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/progress_indicators/CircleProgress.hpp>
|
||||
#include <touchgfx/widgets/canvas/CWRUtil.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
CircleProgress::CircleProgress()
|
||||
: AbstractProgressIndicator(), circle(), circleEndAngle(360)
|
||||
{
|
||||
progressIndicatorContainer.add(circle);
|
||||
circle.setPosition(0, 0, getWidth(), getHeight());
|
||||
CircleProgress::setStartEndAngle(0, 360);
|
||||
}
|
||||
|
||||
void CircleProgress::setProgressIndicatorPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
circle.setPosition(0, 0, width, height);
|
||||
|
||||
AbstractProgressIndicator::setProgressIndicatorPosition(x, y, width, height);
|
||||
}
|
||||
|
||||
void CircleProgress::setPainter(AbstractPainter& painter)
|
||||
{
|
||||
circle.setPainter(painter);
|
||||
}
|
||||
|
||||
void CircleProgress::setCenter(int x, int y)
|
||||
{
|
||||
circle.setCenter(x, y);
|
||||
}
|
||||
|
||||
void CircleProgress::getCenter(int& x, int& y) const
|
||||
{
|
||||
circle.getCenter(x, y);
|
||||
}
|
||||
|
||||
void CircleProgress::setRadius(int r)
|
||||
{
|
||||
circle.setRadius(r);
|
||||
}
|
||||
|
||||
int CircleProgress::getRadius() const
|
||||
{
|
||||
int radius;
|
||||
circle.getRadius(radius);
|
||||
return radius;
|
||||
}
|
||||
|
||||
void CircleProgress::setLineWidth(int width)
|
||||
{
|
||||
circle.setLineWidth(width);
|
||||
}
|
||||
|
||||
int CircleProgress::getLineWidth() const
|
||||
{
|
||||
int width;
|
||||
circle.getLineWidth(width);
|
||||
return width;
|
||||
}
|
||||
|
||||
void CircleProgress::setCapPrecision(int precision)
|
||||
{
|
||||
circle.setCapPrecision(precision);
|
||||
}
|
||||
|
||||
void CircleProgress::setStartEndAngle(int startAngle, int endAngle)
|
||||
{
|
||||
assert(startAngle != endAngle);
|
||||
circle.setArc(startAngle, endAngle);
|
||||
circleEndAngle = endAngle;
|
||||
CircleProgress::setValue(CircleProgress::getValue());
|
||||
}
|
||||
|
||||
int CircleProgress::getStartAngle() const
|
||||
{
|
||||
return circle.getArcStart();
|
||||
}
|
||||
|
||||
int CircleProgress::getEndAngle() const
|
||||
{
|
||||
return circleEndAngle;
|
||||
}
|
||||
|
||||
void CircleProgress::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
AbstractProgressIndicator::setAlpha(newAlpha);
|
||||
circle.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
void CircleProgress::setValue(int value)
|
||||
{
|
||||
AbstractProgressIndicator::setValue(value);
|
||||
CWRUtil::Q5 startAngle;
|
||||
CWRUtil::Q5 const endAngle = CWRUtil::toQ5(circleEndAngle);
|
||||
circle.getArcStart<CWRUtil::Q5>(startAngle);
|
||||
const uint16_t rangeAngleSteps = endAngle < startAngle ? (int)(startAngle - endAngle) : (int)(endAngle - startAngle);
|
||||
CWRUtil::Q5 const progress = CWRUtil::Q5(AbstractProgressIndicator::getProgress(rangeAngleSteps));
|
||||
if (endAngle < startAngle)
|
||||
{
|
||||
circle.updateArcEnd<CWRUtil::Q5>(startAngle - progress);
|
||||
}
|
||||
else
|
||||
{
|
||||
circle.updateArcEnd<CWRUtil::Q5>(startAngle + progress);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/progress_indicators/ImageProgress.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ImageProgress::ImageProgress()
|
||||
: AbstractDirectionProgress(), container(), image(), fixedPosition(true)
|
||||
{
|
||||
container.add(image);
|
||||
progressIndicatorContainer.add(container);
|
||||
}
|
||||
|
||||
void ImageProgress::setProgressIndicatorPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
container.setPosition(0, 0, width, height);
|
||||
|
||||
AbstractProgressIndicator::setProgressIndicatorPosition(x, y, width, height);
|
||||
}
|
||||
|
||||
void ImageProgress::setAnchorAtZero(bool anchorAtZero)
|
||||
{
|
||||
fixedPosition = anchorAtZero;
|
||||
setValue(getValue());
|
||||
}
|
||||
|
||||
bool ImageProgress::getAnchorAtZero() const
|
||||
{
|
||||
return fixedPosition;
|
||||
}
|
||||
|
||||
void ImageProgress::setBitmap(BitmapId bitmapId)
|
||||
{
|
||||
image.setBitmap(Bitmap(bitmapId));
|
||||
}
|
||||
|
||||
BitmapId ImageProgress::getBitmap() const
|
||||
{
|
||||
return image.getBitmapId();
|
||||
}
|
||||
|
||||
void ImageProgress::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
AbstractDirectionProgress::setAlpha(newAlpha);
|
||||
image.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
void ImageProgress::setValue(int value)
|
||||
{
|
||||
AbstractDirectionProgress::setValue(value);
|
||||
const uint16_t maxProgress = (progressDirection == RIGHT || progressDirection == LEFT) ? progressIndicatorContainer.getWidth() : progressIndicatorContainer.getHeight();
|
||||
const int16_t progress = AbstractDirectionProgress::getProgress(maxProgress);
|
||||
if (fixedPosition)
|
||||
{
|
||||
switch (progressDirection)
|
||||
{
|
||||
case RIGHT:
|
||||
{
|
||||
const int16_t oldWidth = container.getWidth();
|
||||
container.setPosition(0, 0, progress, progressIndicatorContainer.getHeight());
|
||||
image.setPosition(0, 0, progress, progressIndicatorContainer.getHeight());
|
||||
const int16_t newWidth = container.getWidth();
|
||||
Rect r(MIN(oldWidth, newWidth), 0, abs(oldWidth - newWidth), container.getHeight());
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
case LEFT:
|
||||
{
|
||||
const int16_t oldX = container.getX();
|
||||
container.setPosition(getWidth() - progress, 0, progress, progressIndicatorContainer.getHeight());
|
||||
image.setPosition(-container.getX(), 0, progressIndicatorContainer.getWidth(), progressIndicatorContainer.getHeight());
|
||||
const int16_t newX = container.getX();
|
||||
Rect r(MIN(oldX, newX), 0, abs(oldX - newX), container.getHeight());
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
case DOWN:
|
||||
{
|
||||
const int16_t oldHeight = container.getHeight();
|
||||
container.setPosition(0, 0, progressIndicatorContainer.getWidth(), progress);
|
||||
image.setPosition(0, 0, progressIndicatorContainer.getWidth(), progress);
|
||||
const int16_t newHeight = container.getHeight();
|
||||
Rect r(0, MIN(oldHeight, newHeight), container.getWidth(), abs(oldHeight - newHeight));
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
case UP:
|
||||
{
|
||||
const int16_t oldY = container.getY();
|
||||
container.setPosition(0, progressIndicatorContainer.getHeight() - progress, progressIndicatorContainer.getWidth(), progress);
|
||||
image.setPosition(0, -container.getY(), progressIndicatorContainer.getWidth(), progressIndicatorContainer.getHeight());
|
||||
const int16_t newY = container.getY();
|
||||
Rect r(0, MIN(oldY, newY), container.getWidth(), abs(oldY - newY));
|
||||
progressIndicatorContainer.invalidateRect(r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
container.invalidate();
|
||||
switch (progressDirection)
|
||||
{
|
||||
case RIGHT:
|
||||
container.setPosition(0, 0, progress, getHeight());
|
||||
image.setPosition(progress - progressIndicatorContainer.getWidth(), 0, progressIndicatorContainer.getWidth(), getHeight());
|
||||
break;
|
||||
case LEFT:
|
||||
container.setPosition(progressIndicatorContainer.getWidth() - progress, 0, progress, progressIndicatorContainer.getHeight());
|
||||
image.setPosition(0, 0, progress, progressIndicatorContainer.getHeight());
|
||||
break;
|
||||
case DOWN:
|
||||
container.setPosition(0, 0, progressIndicatorContainer.getWidth(), progress);
|
||||
image.setPosition(0, progress - progressIndicatorContainer.getHeight(), progressIndicatorContainer.getWidth(), progressIndicatorContainer.getHeight());
|
||||
break;
|
||||
case UP:
|
||||
container.setPosition(0, progressIndicatorContainer.getHeight() - progress, progressIndicatorContainer.getWidth(), progress);
|
||||
image.setPosition(0, 0, progressIndicatorContainer.getWidth(), progress);
|
||||
break;
|
||||
}
|
||||
container.invalidate();
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/progress_indicators/LineProgress.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
LineProgress::LineProgress()
|
||||
: AbstractProgressIndicator(), line(), startX(0), startY(0), endX(0), endY(0)
|
||||
{
|
||||
progressIndicatorContainer.add(line);
|
||||
line.setPosition(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void LineProgress::setProgressIndicatorPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
line.setPosition(0, 0, width, height);
|
||||
|
||||
AbstractProgressIndicator::setProgressIndicatorPosition(x, y, width, height);
|
||||
}
|
||||
|
||||
void LineProgress::setPainter(AbstractPainter& painter)
|
||||
{
|
||||
line.setPainter(painter);
|
||||
}
|
||||
|
||||
void LineProgress::setStart(int x, int y)
|
||||
{
|
||||
startX = CWRUtil::toQ5<int>(x);
|
||||
startY = CWRUtil::toQ5<int>(y);
|
||||
line.setStart(x, y);
|
||||
}
|
||||
|
||||
void LineProgress::getStart(int& x, int& y) const
|
||||
{
|
||||
x = startX.to<int>();
|
||||
y = startY.to<int>();
|
||||
}
|
||||
|
||||
void LineProgress::setEnd(int x, int y)
|
||||
{
|
||||
endX = CWRUtil::toQ5<int>(x);
|
||||
endY = CWRUtil::toQ5<int>(y);
|
||||
}
|
||||
|
||||
void LineProgress::getEnd(int& x, int& y) const
|
||||
{
|
||||
x = endX.to<int>();
|
||||
y = endY.to<int>();
|
||||
}
|
||||
|
||||
void LineProgress::setLineWidth(int width)
|
||||
{
|
||||
line.setLineWidth(width);
|
||||
}
|
||||
|
||||
int LineProgress::getLineWidth() const
|
||||
{
|
||||
int width;
|
||||
line.getLineWidth(width);
|
||||
return width;
|
||||
}
|
||||
|
||||
void LineProgress::setLineEndingStyle(Line::LINE_ENDING_STYLE lineEndingStyle)
|
||||
{
|
||||
line.setLineEndingStyle(lineEndingStyle);
|
||||
}
|
||||
|
||||
Line::LINE_ENDING_STYLE LineProgress::getLineEndingStyle() const
|
||||
{
|
||||
return line.getLineEndingStyle();
|
||||
}
|
||||
|
||||
void LineProgress::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
AbstractProgressIndicator::setAlpha(newAlpha);
|
||||
line.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
void LineProgress::setValue(int value)
|
||||
{
|
||||
if (rangeSteps > 0)
|
||||
{
|
||||
AbstractProgressIndicator::setValue(value);
|
||||
const int progress = (int)AbstractProgressIndicator::getProgress(rangeSteps);
|
||||
CWRUtil::Q5 const r(rangeSteps);
|
||||
CWRUtil::Q5 const p(progress);
|
||||
CWRUtil::Q5 const x = startX + ((endX - startX) / r) * p;
|
||||
CWRUtil::Q5 const y = startY + ((endY - startY) / r) * p;
|
||||
line.updateEnd(x, y);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/progress_indicators/TextProgress.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
TextProgress::TextProgress()
|
||||
: AbstractProgressIndicator(),
|
||||
textArea(),
|
||||
decimals(0)
|
||||
{
|
||||
textBuffer[0] = 0;
|
||||
progressIndicatorContainer.add(textArea);
|
||||
}
|
||||
|
||||
void TextProgress::setProgressIndicatorPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
textArea.setPosition(0, 0, width, height);
|
||||
|
||||
AbstractProgressIndicator::setProgressIndicatorPosition(x, y, width, height);
|
||||
}
|
||||
|
||||
void TextProgress::setTypedText(const TypedText& t)
|
||||
{
|
||||
textArea.setTypedText(t);
|
||||
}
|
||||
|
||||
const TypedText& TextProgress::getTypedText() const
|
||||
{
|
||||
return textArea.getTypedText();
|
||||
}
|
||||
|
||||
void TextProgress::setColor(colortype color)
|
||||
{
|
||||
textArea.setColor(color);
|
||||
}
|
||||
|
||||
colortype TextProgress::getColor() const
|
||||
{
|
||||
return textArea.getColor();
|
||||
}
|
||||
|
||||
void TextProgress::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
AbstractProgressIndicator::setAlpha(newAlpha);
|
||||
textArea.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
void TextProgress::setValue(int value)
|
||||
{
|
||||
textArea.invalidateContent();
|
||||
AbstractProgressIndicator::setValue(value);
|
||||
const int range[3] = { 1, 10, 100 };
|
||||
const uint16_t progress = AbstractProgressIndicator::getProgress(100 * range[decimals]);
|
||||
if (decimals > 0)
|
||||
{
|
||||
Unicode::snprintf(textBuffer, 8, "%d.%0*d", progress / range[decimals], decimals, progress % range[decimals]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Unicode::snprintf(textBuffer, 8, "%d", progress);
|
||||
}
|
||||
textArea.setWildcard(textBuffer);
|
||||
textArea.invalidateContent();
|
||||
}
|
||||
|
||||
void TextProgress::setNumberOfDecimals(uint16_t numberOfDecimals)
|
||||
{
|
||||
decimals = MIN(2, numberOfDecimals);
|
||||
setValue(getValue());
|
||||
}
|
||||
|
||||
uint16_t TextProgress::getNumberOfDecimals() const
|
||||
{
|
||||
return decimals;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/scrollers/DrawableList.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
DrawableList::DrawableList()
|
||||
: Container(),
|
||||
isHorizontal(false),
|
||||
isCircular(false),
|
||||
offset(0),
|
||||
itemSize(0),
|
||||
itemMargin(0),
|
||||
numItems(0),
|
||||
numDrawables(0),
|
||||
firstItem(0),
|
||||
firstDrawable(0),
|
||||
drawablesInitialized(false),
|
||||
firstDrawableIndex(0),
|
||||
drawableItems(0),
|
||||
updateDrawable(0)
|
||||
{
|
||||
}
|
||||
|
||||
void DrawableList::setWidth(int16_t width)
|
||||
{
|
||||
Container::setWidth(width);
|
||||
refreshDrawables();
|
||||
}
|
||||
|
||||
void DrawableList::setHeight(int16_t height)
|
||||
{
|
||||
Container::setHeight(height);
|
||||
refreshDrawables();
|
||||
}
|
||||
|
||||
void DrawableList::setHorizontal(bool horizontal)
|
||||
{
|
||||
if ((horizontal && !isHorizontal) || (!horizontal && isHorizontal))
|
||||
{
|
||||
isHorizontal = horizontal;
|
||||
refreshDrawables();
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawableList::getHorizontal() const
|
||||
{
|
||||
return isHorizontal;
|
||||
}
|
||||
|
||||
void DrawableList::setCircular(bool circular)
|
||||
{
|
||||
if ((circular && !isCircular) || (!circular && isCircular))
|
||||
{
|
||||
isCircular = circular;
|
||||
refreshDrawables();
|
||||
}
|
||||
}
|
||||
|
||||
bool DrawableList::getCircular() const
|
||||
{
|
||||
return isCircular;
|
||||
}
|
||||
|
||||
void DrawableList::setDrawableSize(int16_t drawableSize, int16_t drawableMargin)
|
||||
{
|
||||
itemSize = drawableSize + 2 * drawableMargin;
|
||||
itemMargin = drawableMargin;
|
||||
}
|
||||
|
||||
void DrawableList::setDrawables(DrawableListItemsInterface& drawableListItems,
|
||||
int16_t drawableItemIndexOffset,
|
||||
GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>& updateDrawableCallback)
|
||||
{
|
||||
drawableItems = &drawableListItems;
|
||||
firstDrawableIndex = drawableItemIndexOffset;
|
||||
updateDrawable = &updateDrawableCallback;
|
||||
|
||||
refreshDrawables();
|
||||
}
|
||||
|
||||
int16_t DrawableList::getNumberOfDrawables() const
|
||||
{
|
||||
return numDrawables;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getItemSize() const
|
||||
{
|
||||
return itemSize;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getDrawableSize() const
|
||||
{
|
||||
return itemSize - 2 * itemMargin;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getDrawableMargin() const
|
||||
{
|
||||
return itemMargin;
|
||||
}
|
||||
|
||||
void DrawableList::setNumberOfItems(int16_t numberOfItems)
|
||||
{
|
||||
numItems = numberOfItems;
|
||||
refreshDrawables();
|
||||
}
|
||||
|
||||
int16_t DrawableList::getNumberOfItems() const
|
||||
{
|
||||
return numItems;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getRequiredNumberOfDrawables() const
|
||||
{
|
||||
if (drawableItems == 0 || itemSize <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate number of required drawables. Worst case is one pixel visible of drawable at top and rest stacked tightly
|
||||
int16_t requiredDrawables = 1 + (((isHorizontal ? getWidth() : getHeight()) - 1) + (itemSize - 1)) / itemSize;
|
||||
if (!isCircular)
|
||||
{
|
||||
// We never require more drawables than the number of elements on non-circular list.
|
||||
if (requiredDrawables > numItems)
|
||||
{
|
||||
requiredDrawables = numItems;
|
||||
}
|
||||
}
|
||||
|
||||
const int16_t numberOfDrawables = drawableItems->getNumberOfDrawables();
|
||||
return MIN((numberOfDrawables - firstDrawableIndex), requiredDrawables);
|
||||
}
|
||||
|
||||
void DrawableList::setOffset(int32_t ofs)
|
||||
{
|
||||
offset = ofs;
|
||||
|
||||
if (numDrawables == 0 || numItems == 0 || itemSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!updateDrawable || !updateDrawable->isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// ofs is the offset of item[0]
|
||||
// 0 => item[0] is perfectly selected, -itemSize => item[1] is perfectly selected, itemSize => item[N-1] is perfectly selected etc.
|
||||
int16_t newFirstItem = 0;
|
||||
if (ofs > 0)
|
||||
{
|
||||
const int numberOfItems = ofs / itemSize + 1;
|
||||
newFirstItem -= numberOfItems;
|
||||
ofs -= numberOfItems * itemSize;
|
||||
}
|
||||
if (ofs <= -itemSize)
|
||||
{
|
||||
const int numberOfItems = ofs / itemSize;
|
||||
newFirstItem -= numberOfItems;
|
||||
ofs -= numberOfItems * itemSize;
|
||||
}
|
||||
if (isCircular)
|
||||
{
|
||||
// Make sure that firstIndex is "in range"
|
||||
newFirstItem %= numItems;
|
||||
if (newFirstItem < 0)
|
||||
{
|
||||
newFirstItem += numItems;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newFirstItem < 0)
|
||||
{
|
||||
ofs -= newFirstItem * itemSize;
|
||||
newFirstItem = 0;
|
||||
}
|
||||
else if (newFirstItem + numDrawables > numItems)
|
||||
{
|
||||
const int x = numItems - (newFirstItem + numDrawables);
|
||||
ofs += x * itemSize;
|
||||
newFirstItem += x;
|
||||
}
|
||||
}
|
||||
|
||||
int drawableDelta = 0;
|
||||
if (drawablesInitialized && firstItem != newFirstItem)
|
||||
{
|
||||
drawableDelta = numDrawables;
|
||||
for (int i = 1; i < numDrawables; i++)
|
||||
{
|
||||
int fi = (firstItem + i);
|
||||
int nfi = (newFirstItem + i);
|
||||
if (isCircular)
|
||||
{
|
||||
fi %= numItems;
|
||||
nfi %= numItems;
|
||||
}
|
||||
if (fi == newFirstItem)
|
||||
{
|
||||
drawableDelta = -i;
|
||||
break;
|
||||
}
|
||||
if (nfi == firstItem)
|
||||
{
|
||||
drawableDelta = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
firstDrawable = ((firstDrawable - drawableDelta) + numDrawables) % numDrawables;
|
||||
firstItem = newFirstItem;
|
||||
|
||||
for (int i = 0; i < numDrawables; i++)
|
||||
{
|
||||
const int drawableIndex = (firstDrawable + i) % numDrawables;
|
||||
Drawable* drawable = drawableItems->getDrawable(drawableIndex + firstDrawableIndex);
|
||||
if (isHorizontal)
|
||||
{
|
||||
drawable->moveTo(ofs + i * itemSize + itemMargin, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawable->moveTo(0, ofs + i * itemSize + itemMargin);
|
||||
}
|
||||
|
||||
int itemIndex = i + firstItem;
|
||||
if (isCircular)
|
||||
{
|
||||
itemIndex %= numItems;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (itemIndex < 0 || itemIndex >= numItems)
|
||||
{
|
||||
itemIndex = -1;
|
||||
}
|
||||
}
|
||||
if (itemIndex < 0)
|
||||
{
|
||||
drawable->setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawable->setVisible(true);
|
||||
// Only fill if first time or outside old range
|
||||
if (!drawablesInitialized || (i < drawableDelta || i >= numDrawables + drawableDelta))
|
||||
{
|
||||
if (updateDrawable->isValid())
|
||||
{
|
||||
updateDrawable->execute(drawableItems, drawableIndex + firstDrawableIndex, itemIndex);
|
||||
drawable->invalidateContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drawablesInitialized = true;
|
||||
}
|
||||
|
||||
int32_t DrawableList::getOffset() const
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getItemIndex(int16_t drawableIndex) const
|
||||
{
|
||||
if (drawableIndex < 0 || drawableIndex >= numDrawables || numDrawables == 0 || numItems == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int16_t itemNumber = ((drawableIndex + numDrawables - firstDrawable) % numDrawables) + firstItem;
|
||||
if (isCircular)
|
||||
{
|
||||
itemNumber %= numItems;
|
||||
}
|
||||
if (itemNumber < 0 || itemNumber >= numItems)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return itemNumber;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getDrawableIndices(int16_t itemIndex, int16_t* drawableIndexArray, int16_t arraySize) const
|
||||
{
|
||||
int16_t numFound = 0;
|
||||
int16_t drawableIndex = -1;
|
||||
while ((drawableIndex = getDrawableIndex(itemIndex, drawableIndex)) >= 0)
|
||||
{
|
||||
if (numFound < arraySize)
|
||||
{
|
||||
drawableIndexArray[numFound] = drawableIndex;
|
||||
numFound++;
|
||||
}
|
||||
}
|
||||
return numFound;
|
||||
}
|
||||
|
||||
int16_t DrawableList::getDrawableIndex(int16_t itemIndex, int16_t prevDrawableIndex /*= -1*/) const
|
||||
{
|
||||
if (prevDrawableIndex < -1 || prevDrawableIndex >= numDrawables || numDrawables == 0 || numItems == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (prevDrawableIndex >= 0)
|
||||
{
|
||||
prevDrawableIndex = ((prevDrawableIndex - firstDrawable) + numDrawables) % numDrawables;
|
||||
}
|
||||
for (int16_t i = prevDrawableIndex + 1; i < numDrawables; i++)
|
||||
{
|
||||
int16_t currentItemIndex = firstItem + i;
|
||||
if (isCircular)
|
||||
{
|
||||
currentItemIndex %= numItems;
|
||||
}
|
||||
if (itemIndex == currentItemIndex)
|
||||
{
|
||||
const int16_t drawableIndex = (firstDrawable + i) % numDrawables;
|
||||
return drawableIndex;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void DrawableList::refreshDrawables()
|
||||
{
|
||||
if (drawableItems == 0)
|
||||
{
|
||||
numDrawables = 0;
|
||||
return;
|
||||
}
|
||||
numDrawables = getRequiredNumberOfDrawables();
|
||||
// Remove everything
|
||||
Container::removeAll();
|
||||
// Add the itemDrawables
|
||||
const int16_t w = isHorizontal ? itemSize - 2 * itemMargin : getWidth();
|
||||
const int16_t h = isHorizontal ? getHeight() : itemSize - 2 * itemMargin;
|
||||
for (int drawableIndex = 0; drawableIndex < numDrawables; drawableIndex++)
|
||||
{
|
||||
Drawable* drawable = drawableItems->getDrawable(drawableIndex + firstDrawableIndex);
|
||||
// Resize the drawables, X/Y ignored for now.
|
||||
drawable->setPosition(0, 0, w, h);
|
||||
// Add each drawable for later positioning
|
||||
if (drawable->getParent() != 0)
|
||||
{
|
||||
// Remove drawable from the current parent
|
||||
static_cast<Container*>(drawable->getParent())->remove(*drawable);
|
||||
}
|
||||
Container::add(*drawable);
|
||||
}
|
||||
|
||||
drawablesInitialized = false;
|
||||
firstItem = 0;
|
||||
firstDrawable = 0;
|
||||
setOffset(offset);
|
||||
}
|
||||
|
||||
void DrawableList::itemChanged(int16_t itemIndex)
|
||||
{
|
||||
if (updateDrawable && updateDrawable->isValid())
|
||||
{
|
||||
int16_t drawableIndex = -1;
|
||||
while ((drawableIndex = getDrawableIndex(itemIndex, drawableIndex)) != -1)
|
||||
{
|
||||
updateDrawable->execute(drawableItems, drawableIndex + firstDrawableIndex, itemIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/scrollers/ScrollBase.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ScrollBase::ScrollBase()
|
||||
: Container(),
|
||||
list(),
|
||||
numberOfDrawables(0),
|
||||
distanceBeforeAlignedItem(0),
|
||||
itemSize(0),
|
||||
swipeAcceleration(10),
|
||||
dragAcceleration(10),
|
||||
maxSwipeItems(0),
|
||||
easingEquation(&EasingEquations::backEaseOut),
|
||||
defaultAnimationSteps(30),
|
||||
overshootPercentage(75),
|
||||
itemSelectedCallback(0),
|
||||
itemLockedInCallback(0),
|
||||
animationEndedCallback(0),
|
||||
itemPressedCallback(0),
|
||||
currentAnimationState(NO_ANIMATION),
|
||||
gestureStep(0),
|
||||
gestureStepsTotal(0),
|
||||
gestureStart(0),
|
||||
gestureEnd(0),
|
||||
xClick(0),
|
||||
yClick(0),
|
||||
initialSwipeOffset(0),
|
||||
draggableX(false),
|
||||
draggableY(false),
|
||||
isPressed(false),
|
||||
isScrolling(false)
|
||||
{
|
||||
Container::add(list);
|
||||
list.setXY(0, 0);
|
||||
list.setHorizontal(false);
|
||||
list.setCircular(false);
|
||||
setTouchable(true);
|
||||
}
|
||||
|
||||
void ScrollBase::setWidth(int16_t width)
|
||||
{
|
||||
Container::setWidth(width);
|
||||
list.setWidth(width);
|
||||
}
|
||||
|
||||
void ScrollBase::setHeight(int16_t height)
|
||||
{
|
||||
Container::setHeight(height);
|
||||
list.setHeight(height);
|
||||
}
|
||||
|
||||
void ScrollBase::setHorizontal(bool horizontal)
|
||||
{
|
||||
allowVerticalDrag(horizontal);
|
||||
allowHorizontalDrag(!horizontal);
|
||||
list.setHorizontal(horizontal);
|
||||
}
|
||||
|
||||
bool ScrollBase::getHorizontal() const
|
||||
{
|
||||
return list.getHorizontal();
|
||||
}
|
||||
|
||||
void ScrollBase::setCircular(bool circular)
|
||||
{
|
||||
list.setCircular(circular);
|
||||
}
|
||||
|
||||
bool ScrollBase::getCircular() const
|
||||
{
|
||||
return list.getCircular();
|
||||
}
|
||||
|
||||
void ScrollBase::setDrawableSize(int16_t drawableSize, int16_t drawableMargin)
|
||||
{
|
||||
itemSize = drawableSize + drawableMargin * 2;
|
||||
list.setDrawableSize(drawableSize, drawableMargin);
|
||||
}
|
||||
|
||||
int16_t ScrollBase::getDrawableSize() const
|
||||
{
|
||||
return list.getDrawableSize();
|
||||
}
|
||||
|
||||
int16_t ScrollBase::getDrawableMargin() const
|
||||
{
|
||||
return list.getDrawableMargin();
|
||||
}
|
||||
|
||||
void ScrollBase::setNumberOfItems(int16_t numberOfItems)
|
||||
{
|
||||
if (numberOfItems != getNumberOfItems())
|
||||
{
|
||||
list.setNumberOfItems(numberOfItems);
|
||||
if (!getCircular())
|
||||
{
|
||||
animateToPosition(keepOffsetInsideLimits(getOffset(), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int16_t ScrollBase::getNumberOfItems() const
|
||||
{
|
||||
return list.getNumberOfItems();
|
||||
}
|
||||
|
||||
void ScrollBase::setEasingEquation(EasingEquation equation)
|
||||
{
|
||||
easingEquation = equation;
|
||||
}
|
||||
|
||||
void ScrollBase::setAnimationSteps(int16_t steps)
|
||||
{
|
||||
defaultAnimationSteps = steps;
|
||||
}
|
||||
|
||||
uint16_t ScrollBase::getAnimationSteps() const
|
||||
{
|
||||
return defaultAnimationSteps;
|
||||
}
|
||||
|
||||
void ScrollBase::setSwipeAcceleration(uint16_t acceleration)
|
||||
{
|
||||
swipeAcceleration = acceleration;
|
||||
}
|
||||
|
||||
uint16_t ScrollBase::getSwipeAcceleration() const
|
||||
{
|
||||
return swipeAcceleration;
|
||||
}
|
||||
|
||||
void ScrollBase::setMaxSwipeItems(uint16_t maxItems)
|
||||
{
|
||||
maxSwipeItems = maxItems;
|
||||
}
|
||||
|
||||
uint16_t ScrollBase::getMaxSwipeItems() const
|
||||
{
|
||||
return maxSwipeItems;
|
||||
}
|
||||
|
||||
void ScrollBase::setDragAcceleration(uint16_t acceleration)
|
||||
{
|
||||
dragAcceleration = acceleration;
|
||||
}
|
||||
|
||||
uint16_t ScrollBase::getDragAcceleration() const
|
||||
{
|
||||
return dragAcceleration;
|
||||
}
|
||||
|
||||
void ScrollBase::allowHorizontalDrag(bool enable)
|
||||
{
|
||||
draggableX = enable;
|
||||
}
|
||||
|
||||
void ScrollBase::allowVerticalDrag(bool enable)
|
||||
{
|
||||
draggableY = enable;
|
||||
}
|
||||
|
||||
void ScrollBase::animateToItem(int16_t itemIndex, int16_t animationSteps /*= -1*/)
|
||||
{
|
||||
const int32_t position = getPositionForItem(itemIndex);
|
||||
animateToPosition(position, animationSteps);
|
||||
}
|
||||
|
||||
void ScrollBase::setItemSelectedCallback(GenericCallback<int16_t>& callback)
|
||||
{
|
||||
itemSelectedCallback = &callback;
|
||||
}
|
||||
|
||||
void ScrollBase::setAnimationEndedCallback(GenericCallback<>& callback)
|
||||
{
|
||||
animationEndedCallback = &callback;
|
||||
}
|
||||
|
||||
void ScrollBase::setItemPressedCallback(GenericCallback<int16_t>& callback)
|
||||
{
|
||||
itemPressedCallback = &callback;
|
||||
}
|
||||
|
||||
bool ScrollBase::isAnimating() const
|
||||
{
|
||||
return currentAnimationState != NO_ANIMATION;
|
||||
}
|
||||
|
||||
void ScrollBase::stopAnimation()
|
||||
{
|
||||
if (currentAnimationState == ANIMATING_GESTURE)
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
setOffset(gestureEnd);
|
||||
}
|
||||
currentAnimationState = NO_ANIMATION;
|
||||
}
|
||||
|
||||
void ScrollBase::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
isScrolling = true;
|
||||
stopAnimation();
|
||||
currentAnimationState = ANIMATING_DRAG;
|
||||
int32_t newOffset = getOffset() + (getHorizontal() ? event.getDeltaX() : event.getDeltaY()) * dragAcceleration / 10;
|
||||
newOffset = keepOffsetInsideLimits(newOffset, muldiv(itemSize, overshootPercentage, 100));
|
||||
setOffset(newOffset);
|
||||
}
|
||||
|
||||
void ScrollBase::handleGestureEvent(const GestureEvent& event)
|
||||
{
|
||||
if (event.getType() == (getHorizontal() ? GestureEvent::SWIPE_HORIZONTAL : GestureEvent::SWIPE_VERTICAL))
|
||||
{
|
||||
isScrolling = true;
|
||||
const int16_t velocity = abs(event.getVelocity());
|
||||
const int16_t direction = event.getVelocity() < 0 ? -1 : 1;
|
||||
int16_t steps = MAX(1, velocity - 4) * 7;
|
||||
int32_t newOffset = getOffset() + direction * steps * swipeAcceleration / 10;
|
||||
if (maxSwipeItems > 0)
|
||||
{
|
||||
const int32_t maxDistance = maxSwipeItems * itemSize;
|
||||
newOffset = MIN(newOffset, initialSwipeOffset + maxDistance);
|
||||
newOffset = MAX(newOffset, initialSwipeOffset - maxDistance);
|
||||
}
|
||||
newOffset = keepOffsetInsideLimits(newOffset, 0);
|
||||
steps = MIN(steps, defaultAnimationSteps);
|
||||
animateToPosition(newOffset, steps);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBase::handleTickEvent()
|
||||
{
|
||||
if (currentAnimationState == ANIMATING_GESTURE)
|
||||
{
|
||||
gestureStep++;
|
||||
const int newPosition = gestureStart + easingEquation(gestureStep, 0, gestureEnd - gestureStart, gestureStepsTotal);
|
||||
setOffset(newPosition);
|
||||
if (gestureStep > gestureStepsTotal)
|
||||
{
|
||||
currentAnimationState = NO_ANIMATION;
|
||||
gestureStep = 0;
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
setOffset(getNormalizedOffset(gestureEnd));
|
||||
// Also adjust initialSwipeOffset in case it is being used.
|
||||
initialSwipeOffset += getOffset() - gestureEnd;
|
||||
|
||||
// Item has settled, call back
|
||||
if (animationEndedCallback && animationEndedCallback->isValid())
|
||||
{
|
||||
animationEndedCallback->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBase::itemChanged(int itemIndex)
|
||||
{
|
||||
list.itemChanged(itemIndex);
|
||||
}
|
||||
|
||||
void ScrollBase::setOffset(int32_t offset)
|
||||
{
|
||||
list.setOffset(offset + distanceBeforeAlignedItem);
|
||||
}
|
||||
|
||||
int32_t ScrollBase::getOffset() const
|
||||
{
|
||||
return list.getOffset() - distanceBeforeAlignedItem;
|
||||
}
|
||||
|
||||
int ScrollBase::getNormalizedOffset(int offset) const
|
||||
{
|
||||
const int16_t numItems = getNumberOfItems();
|
||||
if (numItems == 0 || itemSize == 0)
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
const int32_t listSize = numItems * itemSize;
|
||||
offset %= listSize;
|
||||
return offset > 0 ? offset - listSize : offset;
|
||||
}
|
||||
|
||||
int32_t ScrollBase::getNearestAlignedOffset(int32_t offset) const
|
||||
{
|
||||
if (itemSize == 0)
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
if (getCircular())
|
||||
{
|
||||
if (offset < 0)
|
||||
{
|
||||
return (((offset - (itemSize / 2)) / itemSize) * itemSize);
|
||||
}
|
||||
return ((offset + (itemSize / 2)) / itemSize) * itemSize;
|
||||
}
|
||||
offset = keepOffsetInsideLimits(offset, 0);
|
||||
return ((offset - (itemSize / 2)) / itemSize) * itemSize;
|
||||
}
|
||||
|
||||
void ScrollBase::animateToPosition(int32_t position, int16_t steps)
|
||||
{
|
||||
const int32_t currentPosition = getOffset();
|
||||
position = getNearestAlignedOffset(position);
|
||||
if (steps < 0)
|
||||
{
|
||||
steps = defaultAnimationSteps;
|
||||
}
|
||||
const int32_t distance = abs(position - currentPosition);
|
||||
steps = MIN(steps, distance);
|
||||
if (steps < 1)
|
||||
{
|
||||
setOffset(position);
|
||||
currentAnimationState = NO_ANIMATION;
|
||||
}
|
||||
else
|
||||
{
|
||||
gestureStart = currentPosition;
|
||||
gestureEnd = position;
|
||||
gestureStep = 0;
|
||||
gestureStepsTotal = steps;
|
||||
if (currentAnimationState != ANIMATING_GESTURE)
|
||||
{
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
currentAnimationState = ANIMATING_GESTURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollBase::getIsScrolling() const
|
||||
{
|
||||
return isScrolling;
|
||||
}
|
||||
|
||||
bool ScrollBase::getIsPressed() const
|
||||
{
|
||||
return isPressed;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/scrollers/ScrollList.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ScrollList::ScrollList()
|
||||
: ScrollBase(),
|
||||
paddingAfterLastItem(0),
|
||||
snapping(false),
|
||||
windowSize(1)
|
||||
{
|
||||
}
|
||||
|
||||
void ScrollList::setWidth(int16_t width)
|
||||
{
|
||||
ScrollBase::setWidth(width);
|
||||
if (getHorizontal())
|
||||
{
|
||||
setWindowSize(windowSize);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollList::setHeight(int16_t height)
|
||||
{
|
||||
ScrollBase::setHeight(height);
|
||||
if (!getHorizontal())
|
||||
{
|
||||
setWindowSize(windowSize);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollList::setDrawableSize(int16_t drawableSize, int16_t drawableMargin)
|
||||
{
|
||||
ScrollBase::setDrawableSize(drawableSize, drawableMargin);
|
||||
setWindowSize(windowSize);
|
||||
}
|
||||
|
||||
void ScrollList::setDrawables(DrawableListItemsInterface& drawableListItems, GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>& updateDrawableCallback)
|
||||
{
|
||||
stopAnimation();
|
||||
numberOfDrawables = drawableListItems.getNumberOfDrawables();
|
||||
list.setDrawables(drawableListItems, 0, updateDrawableCallback);
|
||||
setOffset(0);
|
||||
}
|
||||
|
||||
void ScrollList::setWindowSize(int16_t items)
|
||||
{
|
||||
if (itemSize > 0)
|
||||
{
|
||||
const int16_t widgetSize = getHorizontal() ? getWidth() : getHeight();
|
||||
const int16_t activeWidgetSize = widgetSize - (distanceBeforeAlignedItem + paddingAfterLastItem);
|
||||
const int16_t numberOfVisibleItems = (activeWidgetSize + itemSize / 2) / itemSize; // Round up
|
||||
items = MIN(items, numberOfVisibleItems); // No more than numberOfVisibleItems
|
||||
}
|
||||
windowSize = MAX(1, items); // No less than 1
|
||||
animateToPosition(keepOffsetInsideLimits(getOffset(), 0));
|
||||
}
|
||||
|
||||
void ScrollList::setPadding(int16_t paddingBefore, int16_t paddingAfter)
|
||||
{
|
||||
const int32_t currentOffset = getOffset();
|
||||
distanceBeforeAlignedItem = paddingBefore;
|
||||
paddingAfterLastItem = paddingAfter;
|
||||
setOffset(currentOffset);
|
||||
list.refreshDrawables();
|
||||
}
|
||||
|
||||
int16_t ScrollList::getPaddingBefore() const
|
||||
{
|
||||
return distanceBeforeAlignedItem;
|
||||
}
|
||||
|
||||
int16_t ScrollList::getPaddingAfter() const
|
||||
{
|
||||
return paddingAfterLastItem;
|
||||
}
|
||||
|
||||
void ScrollList::setSnapping(bool snap)
|
||||
{
|
||||
snapping = snap;
|
||||
if (snapping)
|
||||
{
|
||||
setOffset(getNearestAlignedOffset(getOffset()));
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollList::getSnapping() const
|
||||
{
|
||||
return snapping;
|
||||
}
|
||||
|
||||
int32_t ScrollList::getPositionForItem(int16_t itemIndex)
|
||||
{
|
||||
const int32_t currentOffset = getNormalizedOffset(getOffset());
|
||||
if (itemIndex < 0 || itemIndex >= list.getNumberOfItems() || itemSize == 0)
|
||||
{
|
||||
return currentOffset;
|
||||
}
|
||||
const int32_t itemOffset = -itemIndex * itemSize;
|
||||
// Get the visible size
|
||||
const int16_t widgetSize = getHorizontal() ? getWidth() : getHeight();
|
||||
const int16_t activeWidgetSize = widgetSize - (distanceBeforeAlignedItem + paddingAfterLastItem);
|
||||
if (list.getCircular())
|
||||
{
|
||||
int32_t offset = currentOffset;
|
||||
// Important this is a do-while of visibleSize < itemSize in which case we need to check at least one time
|
||||
do
|
||||
{
|
||||
const int16_t i = (-getNormalizedOffset(offset)) / itemSize; // Item index of first
|
||||
if (itemIndex == i)
|
||||
{
|
||||
return currentOffset;
|
||||
}
|
||||
offset -= itemSize;
|
||||
} while (offset >= currentOffset - (activeWidgetSize - itemSize));
|
||||
const int32_t allItemsSize = list.getNumberOfItems() * itemSize;
|
||||
// Either scroll left from the first item or right from the last item. Find out which is closest
|
||||
int32_t leftScrollDistance = itemOffset - currentOffset;
|
||||
const int32_t leftScrollDistance2 = (itemOffset + allItemsSize) - currentOffset;
|
||||
const int32_t rightItemOffset = getNormalizedOffset(currentOffset - (activeWidgetSize - itemSize));
|
||||
int32_t rightScrollDistance = rightItemOffset - itemOffset;
|
||||
const int32_t rightScrollDistance2 = rightItemOffset - (itemOffset - allItemsSize);
|
||||
if (abs(leftScrollDistance2) < abs(leftScrollDistance))
|
||||
{
|
||||
leftScrollDistance = leftScrollDistance2;
|
||||
}
|
||||
if (abs(rightScrollDistance2) < abs(rightScrollDistance))
|
||||
{
|
||||
rightScrollDistance = rightScrollDistance2;
|
||||
}
|
||||
if (abs(rightScrollDistance) < abs(leftScrollDistance))
|
||||
{
|
||||
return currentOffset - rightScrollDistance;
|
||||
}
|
||||
return currentOffset + leftScrollDistance;
|
||||
}
|
||||
|
||||
if (itemOffset > currentOffset) // First item on screen is higher than the itemIndex. Scroll itemIndex to top position
|
||||
{
|
||||
return itemOffset;
|
||||
}
|
||||
const int16_t numberOfVisibleItems = activeWidgetSize / itemSize;
|
||||
int32_t itemOffsetAtEnd = itemOffset;
|
||||
if (numberOfVisibleItems > 0)
|
||||
{
|
||||
if (snapping)
|
||||
{
|
||||
itemOffsetAtEnd = itemOffset + (numberOfVisibleItems - 1) * itemSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemOffsetAtEnd = itemOffset + activeWidgetSize - itemSize;
|
||||
}
|
||||
}
|
||||
if (itemOffsetAtEnd < currentOffset)
|
||||
{
|
||||
return itemOffsetAtEnd;
|
||||
}
|
||||
return currentOffset;
|
||||
}
|
||||
|
||||
void ScrollList::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
ScrollBase::handleClickEvent(event);
|
||||
if (event.getType() == ClickEvent::PRESSED)
|
||||
{
|
||||
isPressed = true;
|
||||
xClick = event.getX();
|
||||
yClick = event.getY();
|
||||
initialSwipeOffset = getOffset();
|
||||
|
||||
setOffset(getNearestAlignedOffset(initialSwipeOffset));
|
||||
if (itemPressedCallback && itemPressedCallback->isValid())
|
||||
{
|
||||
const int16_t click = (getHorizontal() ? xClick : yClick);
|
||||
int32_t offset = click - getOffset();
|
||||
const int32_t listSize = getNumberOfItems() * itemSize;
|
||||
if (getCircular())
|
||||
{
|
||||
offset += listSize;
|
||||
offset %= listSize;
|
||||
}
|
||||
if (offset >= 0 && offset < listSize)
|
||||
{
|
||||
const int16_t item = offset / itemSize;
|
||||
itemPressedCallback->execute(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (event.getType() == ClickEvent::RELEASED)
|
||||
{
|
||||
if (currentAnimationState == NO_ANIMATION)
|
||||
{
|
||||
// For a tiny drag, start by re-aligning (no animation(!))
|
||||
setOffset(getNearestAlignedOffset(getOffset()));
|
||||
if (itemSelectedCallback && itemSelectedCallback->isValid())
|
||||
{
|
||||
const int16_t click = (getHorizontal() ? xClick : yClick);
|
||||
int32_t offset = click - getOffset();
|
||||
const int32_t listSize = getNumberOfItems() * itemSize;
|
||||
if (getCircular())
|
||||
{
|
||||
offset += listSize;
|
||||
offset %= listSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= distanceBeforeAlignedItem;
|
||||
}
|
||||
if (offset >= 0 && offset < listSize)
|
||||
{
|
||||
const int16_t item = offset / itemSize;
|
||||
itemSelectedCallback->execute(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (currentAnimationState == ANIMATING_DRAG)
|
||||
{
|
||||
// click + drag + release. Find best Y to scroll to
|
||||
animateToPosition(getNearestAlignedOffset(getOffset()));
|
||||
}
|
||||
isPressed = false;
|
||||
}
|
||||
isScrolling = false;
|
||||
}
|
||||
|
||||
int32_t ScrollList::getNearestAlignedOffset(int32_t offset) const
|
||||
{
|
||||
if (snapping)
|
||||
{
|
||||
// ScrollBase implementation will snap
|
||||
return ScrollBase::getNearestAlignedOffset(offset);
|
||||
}
|
||||
|
||||
return keepOffsetInsideLimits(offset, 0);
|
||||
}
|
||||
|
||||
int32_t ScrollList::keepOffsetInsideLimits(int32_t newOffset, int16_t overShoot) const
|
||||
{
|
||||
if (!getCircular())
|
||||
{
|
||||
newOffset = MIN(newOffset, overShoot);
|
||||
const int maxOffToTheStart = windowSize < getNumberOfItems() ? getNumberOfItems() - windowSize : 0;
|
||||
newOffset = MAX(newOffset, -(itemSize * maxOffToTheStart) - overShoot);
|
||||
}
|
||||
return newOffset;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/scrollers/ScrollWheel.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void ScrollWheel::setDrawables(DrawableListItemsInterface& drawableListItems, GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>& updateDrawableCallback)
|
||||
{
|
||||
stopAnimation();
|
||||
numberOfDrawables = drawableListItems.getNumberOfDrawables();
|
||||
|
||||
list.setDrawables(drawableListItems, 0, updateDrawableCallback);
|
||||
|
||||
setOffset(0);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/containers/scrollers/ScrollWheelBase.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ScrollWheelBase::ScrollWheelBase()
|
||||
: ScrollBase(),
|
||||
animateToCallback(0)
|
||||
{
|
||||
ScrollWheelBase::setHorizontal(false);
|
||||
setTouchable(true);
|
||||
}
|
||||
|
||||
void ScrollWheelBase::setSelectedItemOffset(int16_t offset)
|
||||
{
|
||||
const int32_t currentOffset = getOffset();
|
||||
distanceBeforeAlignedItem = offset;
|
||||
setOffset(currentOffset);
|
||||
}
|
||||
|
||||
int16_t ScrollWheelBase::getSelectedItemOffset() const
|
||||
{
|
||||
return distanceBeforeAlignedItem;
|
||||
}
|
||||
|
||||
int32_t ScrollWheelBase::getPositionForItem(int16_t itemIndex)
|
||||
{
|
||||
int32_t newOffset = -itemIndex * itemSize;
|
||||
if (getCircular())
|
||||
{
|
||||
// Check if it is closer to scroll backwards
|
||||
const int32_t otherOffset = newOffset + getNumberOfItems() * itemSize;
|
||||
const int32_t offset = getOffset();
|
||||
if (abs(otherOffset - offset) < abs(newOffset - offset))
|
||||
{
|
||||
newOffset = otherOffset;
|
||||
}
|
||||
}
|
||||
return newOffset;
|
||||
}
|
||||
|
||||
void ScrollWheelBase::animateToPosition(int32_t position, int16_t steps)
|
||||
{
|
||||
if (itemSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (animateToCallback && animateToCallback->isValid() && itemSize > 0)
|
||||
{
|
||||
position = getNearestAlignedOffset(position);
|
||||
const int16_t itemIndex = (-position) / itemSize;
|
||||
animateToCallback->execute(itemIndex);
|
||||
}
|
||||
ScrollBase::animateToPosition(position, steps);
|
||||
}
|
||||
|
||||
int ScrollWheelBase::getSelectedItem() const
|
||||
{
|
||||
if (itemSize == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (currentAnimationState == ANIMATING_GESTURE)
|
||||
{
|
||||
// Scroll in progress, get the destination value
|
||||
return (-getNormalizedOffset(gestureEnd)) / itemSize;
|
||||
}
|
||||
return (-getNormalizedOffset(getOffset())) / itemSize;
|
||||
}
|
||||
|
||||
int32_t ScrollWheelBase::keepOffsetInsideLimits(int32_t newOffset, int16_t overShoot) const
|
||||
{
|
||||
if (!getCircular())
|
||||
{
|
||||
newOffset = MIN(newOffset, overShoot);
|
||||
const int16_t numberOfItems = getNumberOfItems();
|
||||
newOffset = MAX(newOffset, -(itemSize * (numberOfItems - 1)) - overShoot);
|
||||
}
|
||||
return newOffset;
|
||||
}
|
||||
|
||||
void ScrollWheelBase::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
if (itemSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int32_t offset = getOffset();
|
||||
if (event.getType() == ClickEvent::PRESSED)
|
||||
{
|
||||
isPressed = true;
|
||||
xClick = event.getX();
|
||||
yClick = event.getY();
|
||||
initialSwipeOffset = offset;
|
||||
|
||||
if (itemPressedCallback && itemPressedCallback->isValid())
|
||||
{
|
||||
itemPressedCallback->execute(getSelectedItem());
|
||||
}
|
||||
}
|
||||
else if (event.getType() == ClickEvent::RELEASED)
|
||||
{
|
||||
if (currentAnimationState == NO_ANIMATION)
|
||||
{
|
||||
const int16_t click = getHorizontal() ? xClick : yClick;
|
||||
// Click => move to clicked position
|
||||
if (click < distanceBeforeAlignedItem)
|
||||
{
|
||||
animateToPosition(offset + ((distanceBeforeAlignedItem - click) / itemSize + 1) * itemSize);
|
||||
}
|
||||
else if (click > distanceBeforeAlignedItem + itemSize)
|
||||
{
|
||||
animateToPosition(offset - ((click - distanceBeforeAlignedItem) / itemSize) * itemSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
animateToPosition(offset);
|
||||
}
|
||||
}
|
||||
else if (currentAnimationState == ANIMATING_DRAG)
|
||||
{
|
||||
// click + drag + release. Find best Y to scroll to
|
||||
animateToPosition(offset);
|
||||
}
|
||||
|
||||
if (itemSelectedCallback && itemSelectedCallback->isValid())
|
||||
{
|
||||
itemSelectedCallback->execute(getSelectedItem());
|
||||
}
|
||||
isPressed = false;
|
||||
}
|
||||
isScrolling = false;
|
||||
}
|
||||
|
||||
void ScrollWheelBase::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
isScrolling = true;
|
||||
currentAnimationState = ANIMATING_DRAG;
|
||||
int newOffset = getOffset() + (getHorizontal() ? event.getDeltaX() : event.getDeltaY()) * dragAcceleration / 10;
|
||||
newOffset = keepOffsetInsideLimits(newOffset, overshootPercentage);
|
||||
setOffset(newOffset);
|
||||
}
|
||||
|
||||
void ScrollWheelBase::handleGestureEvent(const GestureEvent& event)
|
||||
{
|
||||
if (event.getType() == (getHorizontal() ? GestureEvent::SWIPE_HORIZONTAL : GestureEvent::SWIPE_VERTICAL))
|
||||
{
|
||||
isScrolling = true;
|
||||
int32_t newOffset = getOffset() + event.getVelocity() * swipeAcceleration / 10;
|
||||
if (maxSwipeItems > 0)
|
||||
{
|
||||
const int32_t maxDistance = maxSwipeItems * itemSize;
|
||||
newOffset = MIN(newOffset, initialSwipeOffset + maxDistance);
|
||||
newOffset = MAX(newOffset, initialSwipeOffset - maxDistance);
|
||||
}
|
||||
animateToPosition(newOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollWheelBase::setAnimateToCallback(GenericCallback<int16_t>& callback)
|
||||
{
|
||||
animateToCallback = &callback;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/containers/scrollers/ScrollWheelWithSelectionStyle.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ScrollWheelWithSelectionStyle::ScrollWheelWithSelectionStyle()
|
||||
: ScrollWheelBase(),
|
||||
drawablesInFirstList(0),
|
||||
listCenter(),
|
||||
listAfter(),
|
||||
extraSizeBeforeSelectedItem(0),
|
||||
extraSizeAfterSelectedItem(0),
|
||||
marginBeforeSelectedItem(0),
|
||||
marginAfterSelectedItem(0),
|
||||
drawables(0),
|
||||
centerDrawables(0),
|
||||
originalUpdateDrawableCallback(0),
|
||||
originalUpdateCenterDrawableCallback(0)
|
||||
{
|
||||
ScrollWheelBase::add(listAfter);
|
||||
ScrollWheelBase::add(listCenter); // Put center list at top of the first/last list.
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setWidth(int16_t width)
|
||||
{
|
||||
ScrollWheelBase::setWidth(width);
|
||||
if (getHorizontal())
|
||||
{
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setHeight(int16_t height)
|
||||
{
|
||||
ScrollWheelBase::setHeight(height);
|
||||
if (!getHorizontal())
|
||||
{
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setHorizontal(bool horizontal)
|
||||
{
|
||||
ScrollWheelBase::setHorizontal(horizontal);
|
||||
listCenter.setHorizontal(horizontal);
|
||||
listAfter.setHorizontal(horizontal);
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setCircular(bool circular)
|
||||
{
|
||||
ScrollWheelBase::setCircular(circular);
|
||||
listCenter.setCircular(circular);
|
||||
listAfter.setCircular(circular);
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setNumberOfItems(int16_t numberOfItems)
|
||||
{
|
||||
if (numberOfItems != getNumberOfItems())
|
||||
{
|
||||
ScrollWheelBase::setNumberOfItems(numberOfItems);
|
||||
listCenter.setNumberOfItems(numberOfItems);
|
||||
listAfter.setNumberOfItems(numberOfItems);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setSelectedItemOffset(int16_t offset)
|
||||
{
|
||||
ScrollWheelBase::setSelectedItemOffset(offset);
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setSelectedItemExtraSize(int16_t extraSizeBefore, int16_t extraSizeAfter)
|
||||
{
|
||||
extraSizeBeforeSelectedItem = extraSizeBefore;
|
||||
extraSizeAfterSelectedItem = extraSizeAfter;
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
|
||||
int16_t ScrollWheelWithSelectionStyle::getSelectedItemExtraSizeBefore() const
|
||||
{
|
||||
return extraSizeBeforeSelectedItem;
|
||||
}
|
||||
|
||||
int16_t ScrollWheelWithSelectionStyle::getSelectedItemExtraSizeAfter() const
|
||||
{
|
||||
return extraSizeAfterSelectedItem;
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setSelectedItemMargin(int16_t marginBefore, int16_t marginAfter)
|
||||
{
|
||||
marginBeforeSelectedItem = marginBefore;
|
||||
marginAfterSelectedItem = marginAfter;
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
|
||||
int16_t ScrollWheelWithSelectionStyle::getSelectedItemMarginBefore() const
|
||||
{
|
||||
return marginBeforeSelectedItem;
|
||||
}
|
||||
|
||||
int16_t ScrollWheelWithSelectionStyle::getSelectedItemMarginAfter() const
|
||||
{
|
||||
return marginAfterSelectedItem;
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setSelectedItemPosition(int16_t offset, int16_t extraSizeBefore, int16_t extraSizeAfter, int16_t marginBefore, int16_t marginAfter)
|
||||
{
|
||||
setSelectedItemOffset(offset);
|
||||
setSelectedItemExtraSize(extraSizeBefore, extraSizeAfter);
|
||||
setSelectedItemMargin(marginBefore, marginAfter);
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setDrawableSize(int16_t drawableSize, int16_t drawableMargin)
|
||||
{
|
||||
ScrollWheelBase::setDrawableSize(drawableSize, drawableMargin);
|
||||
listCenter.setDrawableSize(drawableSize, drawableMargin);
|
||||
listAfter.setDrawableSize(drawableSize, drawableMargin);
|
||||
|
||||
// Resize the three lists
|
||||
setSelectedItemOffset(distanceBeforeAlignedItem);
|
||||
|
||||
// Changing the drawable size alters number of required drawables, so refresh this
|
||||
refreshDrawableListsLayout();
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setDrawables(DrawableListItemsInterface& drawableListItems, GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>& updateDrawableCallback,
|
||||
DrawableListItemsInterface& centerDrawableListItems, GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>& updateCenterDrawableCallback)
|
||||
{
|
||||
drawables = &drawableListItems;
|
||||
centerDrawables = ¢erDrawableListItems;
|
||||
|
||||
currentAnimationState = NO_ANIMATION;
|
||||
|
||||
originalUpdateDrawableCallback = &updateDrawableCallback;
|
||||
originalUpdateCenterDrawableCallback = &updateCenterDrawableCallback;
|
||||
|
||||
refreshDrawableListsLayout();
|
||||
|
||||
setOffset(0);
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::setOffset(int32_t offset)
|
||||
{
|
||||
ScrollWheelBase::setOffset(offset);
|
||||
listCenter.setOffset((distanceBeforeAlignedItem - (distanceBeforeAlignedItem - extraSizeBeforeSelectedItem)) + offset);
|
||||
listAfter.setOffset((distanceBeforeAlignedItem - (distanceBeforeAlignedItem + itemSize + extraSizeAfterSelectedItem + marginAfterSelectedItem)) + offset);
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::itemChanged(int itemIndex)
|
||||
{
|
||||
ScrollWheelBase::itemChanged(itemIndex);
|
||||
listCenter.itemChanged(itemIndex);
|
||||
listAfter.itemChanged(itemIndex);
|
||||
}
|
||||
|
||||
void ScrollWheelWithSelectionStyle::refreshDrawableListsLayout()
|
||||
{
|
||||
if (drawables != 0 && centerDrawables != 0)
|
||||
{
|
||||
const int32_t currentOffset = getOffset();
|
||||
|
||||
const int16_t listCenterPos = distanceBeforeAlignedItem - extraSizeBeforeSelectedItem;
|
||||
const int16_t listAfterPos = distanceBeforeAlignedItem + itemSize + (extraSizeAfterSelectedItem + marginAfterSelectedItem);
|
||||
const int16_t listBeforeSize = listCenterPos - marginBeforeSelectedItem;
|
||||
const int16_t listCenterSize = itemSize + extraSizeBeforeSelectedItem + extraSizeAfterSelectedItem;
|
||||
|
||||
if (getHorizontal())
|
||||
{
|
||||
const int16_t listAfterSize = getWidth() - listAfterPos;
|
||||
list.setPosition(0, 0, listBeforeSize, getHeight());
|
||||
listCenter.setPosition(listCenterPos, 0, listCenterSize, getHeight());
|
||||
listAfter.setPosition(listAfterPos, 0, listAfterSize, getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
const int16_t listAfterSize = getHeight() - listAfterPos;
|
||||
list.setPosition(0, 0, getWidth(), listBeforeSize);
|
||||
listCenter.setPosition(0, listCenterPos, getWidth(), listCenterSize);
|
||||
listAfter.setPosition(0, listAfterPos, getWidth(), listAfterSize);
|
||||
}
|
||||
|
||||
list.setDrawables(*drawables, 0, *originalUpdateDrawableCallback);
|
||||
drawablesInFirstList = list.getNumberOfDrawables();
|
||||
listCenter.setDrawables(*centerDrawables, 0, *originalUpdateCenterDrawableCallback);
|
||||
listAfter.setDrawables(*drawables, drawablesInFirstList, *originalUpdateDrawableCallback);
|
||||
|
||||
setOffset(keepOffsetInsideLimits(currentOffset, 0));
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,32 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/AbstractButton.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void AbstractButton::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
const bool wasPressed = pressed;
|
||||
pressed = (event.getType() == ClickEvent::PRESSED);
|
||||
if ((pressed && !wasPressed) || (!pressed && wasPressed))
|
||||
{
|
||||
// Pressed state changed, so invalidate
|
||||
invalidate();
|
||||
}
|
||||
if (wasPressed && (event.getType() == ClickEvent::RELEASED))
|
||||
{
|
||||
// This is a click. Fire callback.
|
||||
executeAction();
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,149 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/widgets/AnimatedImage.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void AnimatedImage::handleTickEvent()
|
||||
{
|
||||
if (!running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
++ticksSinceUpdate;
|
||||
if (ticksSinceUpdate != updateTicksInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ticksSinceUpdate = 0;
|
||||
BitmapId currentId = getBitmap();
|
||||
|
||||
if (((currentId == endId) && !reverse) || ((currentId == startId) && reverse))
|
||||
{
|
||||
if (!loopAnimation)
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
running = false;
|
||||
}
|
||||
|
||||
if (animationDoneAction && animationDoneAction->isValid())
|
||||
{
|
||||
animationDoneAction->execute(*this);
|
||||
}
|
||||
|
||||
if (running && loopAnimation)
|
||||
{
|
||||
if (reverse)
|
||||
{
|
||||
Image::setBitmap(Bitmap(endId));
|
||||
}
|
||||
else
|
||||
{
|
||||
Image::setBitmap(Bitmap(startId));
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (reverse)
|
||||
{
|
||||
--currentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
++currentId;
|
||||
}
|
||||
Image::setBitmap(Bitmap(currentId));
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedImage::startAnimation(const bool rev, const bool reset /*= false*/, const bool loop /*= false*/)
|
||||
{
|
||||
if ((startId != BITMAP_INVALID) && (endId != BITMAP_INVALID))
|
||||
{
|
||||
reverse = rev;
|
||||
loopAnimation = loop;
|
||||
if (reverse && reset)
|
||||
{
|
||||
Image::setBitmap(Bitmap(endId));
|
||||
invalidate();
|
||||
}
|
||||
else if (!reverse && reset)
|
||||
{
|
||||
Image::setBitmap(Bitmap(startId));
|
||||
invalidate();
|
||||
}
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedImage::stopAnimation()
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
running = false;
|
||||
}
|
||||
if (reverse)
|
||||
{
|
||||
Image::setBitmap(Bitmap(endId));
|
||||
}
|
||||
else
|
||||
{
|
||||
Image::setBitmap(Bitmap(startId));
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void AnimatedImage::pauseAnimation()
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimatedImage::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
startId = bmp.getId();
|
||||
Image::setBitmap(bmp);
|
||||
}
|
||||
|
||||
void AnimatedImage::setBitmapEnd(const Bitmap& bmp)
|
||||
{
|
||||
endId = bmp.getId();
|
||||
}
|
||||
|
||||
void AnimatedImage::setBitmaps(BitmapId start, BitmapId end)
|
||||
{
|
||||
setBitmap(start);
|
||||
setBitmapEnd(end);
|
||||
}
|
||||
|
||||
void AnimatedImage::setUpdateTicksInterval(uint8_t updateInterval)
|
||||
{
|
||||
updateTicksInterval = updateInterval;
|
||||
ticksSinceUpdate = 0;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/widgets/AnimationTextureMapper.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AnimationTextureMapper::AnimationTextureMapper()
|
||||
: TextureMapper(),
|
||||
textureMapperAnimationStepCallback(0),
|
||||
textureMapperAnimationEndedCallback(0),
|
||||
animationCounter(0),
|
||||
animationRunning(false)
|
||||
{
|
||||
for (int i = 0; i < NUMBER_OF_ANIMATION_PARAMETERS; i++)
|
||||
{
|
||||
animations[i].animationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTextureMapper::setTextureMapperAnimationStepAction(GenericCallback<const AnimationTextureMapper&>& callback)
|
||||
{
|
||||
textureMapperAnimationStepCallback = &callback;
|
||||
}
|
||||
|
||||
void AnimationTextureMapper::setTextureMapperAnimationEndedAction(GenericCallback<const AnimationTextureMapper&>& callback)
|
||||
{
|
||||
textureMapperAnimationEndedCallback = &callback;
|
||||
}
|
||||
|
||||
bool AnimationTextureMapper::isTextureMapperAnimationRunning() const
|
||||
{
|
||||
return animationRunning;
|
||||
}
|
||||
|
||||
void AnimationTextureMapper::setupAnimation(AnimationParameter parameter, float endValue, uint16_t duration, uint16_t delay, EasingEquation progressionEquation /*= &EasingEquations::linearEaseNone*/)
|
||||
{
|
||||
animations[parameter].animationActive = true;
|
||||
animations[parameter].animationEnd = endValue;
|
||||
animations[parameter].animationDuration = duration;
|
||||
animations[parameter].animationDelay = delay;
|
||||
animations[parameter].animationProgressionEquation = progressionEquation;
|
||||
}
|
||||
|
||||
void AnimationTextureMapper::startAnimation()
|
||||
{
|
||||
if (!animationRunning)
|
||||
{
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
}
|
||||
|
||||
animationCounter = 0;
|
||||
|
||||
animations[X_ROTATION].animationStart = xAngle;
|
||||
animations[Y_ROTATION].animationStart = yAngle;
|
||||
animations[Z_ROTATION].animationStart = zAngle;
|
||||
animations[SCALE].animationStart = scale;
|
||||
|
||||
animationRunning = true;
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_ANIMATION_PARAMETERS; i++)
|
||||
{
|
||||
if (animations[i].animationActive && animations[i].animationDelay + animations[i].animationDuration > 0)
|
||||
{
|
||||
return; // Animation needs to run, return
|
||||
}
|
||||
}
|
||||
// No active animations or all active animations have zero steps, execute now!
|
||||
handleTickEvent();
|
||||
}
|
||||
|
||||
void AnimationTextureMapper::cancelAnimationTextureMapperAnimation()
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
animationRunning = false;
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_ANIMATION_PARAMETERS; i++)
|
||||
{
|
||||
animations[i].animationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t AnimationTextureMapper::getAnimationStep()
|
||||
{
|
||||
return animationCounter;
|
||||
}
|
||||
|
||||
void AnimationTextureMapper::handleTickEvent()
|
||||
{
|
||||
if (!animationRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool newAngleAssigned = false;
|
||||
bool newScaleAssigned = false;
|
||||
bool activeAnimationExists = false;
|
||||
|
||||
animationCounter++;
|
||||
|
||||
float newXAngle = xAngle;
|
||||
float newYAngle = yAngle;
|
||||
float newZAngle = zAngle;
|
||||
float newScale = scale;
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_ANIMATION_PARAMETERS; i++)
|
||||
{
|
||||
if (!(animations[i].animationActive))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (animationCounter >= animations[i].animationDelay)
|
||||
{
|
||||
// Adjust the used animationCounter for the startup delay
|
||||
const uint32_t actualAnimationCounter = animationCounter - animations[i].animationDelay;
|
||||
|
||||
int directionModifier;
|
||||
int16_t distance;
|
||||
|
||||
if (animations[i].animationEnd > animations[i].animationStart)
|
||||
{
|
||||
directionModifier = 1;
|
||||
distance = (int16_t)((animations[i].animationEnd - animations[i].animationStart) * 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
directionModifier = -1;
|
||||
distance = (int16_t)((animations[i].animationStart - animations[i].animationEnd) * 1000);
|
||||
}
|
||||
|
||||
const float delta = (float)directionModifier * (animations[i].animationProgressionEquation(actualAnimationCounter, 0, distance, animations[i].animationDuration) / 1000.f);
|
||||
|
||||
switch ((AnimationParameter)i)
|
||||
{
|
||||
case X_ROTATION:
|
||||
newXAngle = animations[X_ROTATION].animationStart + delta;
|
||||
newAngleAssigned = true;
|
||||
break;
|
||||
case Y_ROTATION:
|
||||
newYAngle = animations[Y_ROTATION].animationStart + delta;
|
||||
newAngleAssigned = true;
|
||||
break;
|
||||
case Z_ROTATION:
|
||||
newZAngle = animations[Z_ROTATION].animationStart + delta;
|
||||
newAngleAssigned = true;
|
||||
break;
|
||||
case SCALE:
|
||||
newScale = animations[SCALE].animationStart + delta;
|
||||
newScaleAssigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (animationCounter >= (uint32_t)(animations[i].animationDelay + animations[i].animationDuration))
|
||||
{
|
||||
animations[i].animationActive = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
activeAnimationExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (newAngleAssigned || newScaleAssigned)
|
||||
{
|
||||
if (newAngleAssigned)
|
||||
{
|
||||
updateAngles(newXAngle, newYAngle, newZAngle);
|
||||
}
|
||||
if (newScaleAssigned)
|
||||
{
|
||||
updateScale(newScale);
|
||||
}
|
||||
|
||||
if (textureMapperAnimationStepCallback && textureMapperAnimationStepCallback->isValid())
|
||||
{
|
||||
textureMapperAnimationStepCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
if (!activeAnimationExists)
|
||||
{
|
||||
// End of animation
|
||||
cancelAnimationTextureMapperAnimation();
|
||||
|
||||
if (textureMapperAnimationEndedCallback && textureMapperAnimationEndedCallback->isValid())
|
||||
{
|
||||
textureMapperAnimationEndedCallback->execute(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,36 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/Box.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Rect Box::getSolidRect() const
|
||||
{
|
||||
Rect solidRect;
|
||||
if (alpha == 255)
|
||||
{
|
||||
solidRect.width = rect.width;
|
||||
solidRect.height = rect.height;
|
||||
}
|
||||
return solidRect;
|
||||
}
|
||||
|
||||
void Box::draw(const Rect& area) const
|
||||
{
|
||||
Rect dirty = area;
|
||||
translateRectToAbsolute(dirty);
|
||||
HAL::lcd().fillRect(dirty, color, alpha);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,55 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/BoxWithBorder.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void BoxWithBorder::draw(const Rect& area) const
|
||||
{
|
||||
const Rect centerRect = Rect(borderSize, borderSize, getWidth() - (2 * borderSize), getHeight() - (2 * borderSize));
|
||||
if (centerRect.isEmpty())
|
||||
{
|
||||
Rect dirty = area;
|
||||
translateRectToAbsolute(dirty);
|
||||
HAL::lcd().fillRect(dirty, borderColor, alpha);
|
||||
return;
|
||||
}
|
||||
|
||||
const Rect dirty = area & centerRect;
|
||||
Box::draw(dirty);
|
||||
|
||||
if (borderSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const Rect borders[4] = {
|
||||
Rect(0, 0, getWidth(), borderSize), // Upper
|
||||
Rect(0, getHeight() - borderSize, getWidth(), borderSize), // lower
|
||||
Rect(0, borderSize, borderSize, getHeight() - (2 * borderSize)), // left
|
||||
Rect(getWidth() - borderSize, borderSize, borderSize, getHeight() - (2 * borderSize)) // right
|
||||
};
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Rect borderDirty = borders[i] & area;
|
||||
if (!borderDirty.isEmpty())
|
||||
{
|
||||
translateRectToAbsolute(borderDirty);
|
||||
HAL::lcd().fillRect(borderDirty, borderColor, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,51 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/Button.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void Button::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const Bitmap bmp(pressed ? down : up);
|
||||
Rect dirty(0, 0, bmp.getWidth(), bmp.getHeight());
|
||||
dirty &= invalidatedArea;
|
||||
if ((bmp.getId() != BITMAP_INVALID) && !dirty.isEmpty())
|
||||
{
|
||||
Rect r;
|
||||
translateRectToAbsolute(r);
|
||||
HAL::lcd().drawPartialBitmap(bmp, r.x, r.y, dirty, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setBitmaps(const Bitmap& bitmapReleased, const Bitmap& bitmapPressed)
|
||||
{
|
||||
up = bitmapReleased;
|
||||
down = bitmapPressed;
|
||||
// Adjust width and height of this widget to match bitmap. It is assumed
|
||||
// that the two bitmaps have same dimensions.
|
||||
Button::setWidthHeight(down);
|
||||
}
|
||||
|
||||
Rect Button::getSolidRect() const
|
||||
{
|
||||
if (alpha < 255)
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
|
||||
return (pressed ? down.getSolidRect() : up.getSolidRect());
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,57 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/ButtonWithIcon.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ButtonWithIcon::ButtonWithIcon()
|
||||
: Button(),
|
||||
iconReleased(),
|
||||
iconPressed(),
|
||||
iconX(0),
|
||||
iconY(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ButtonWithIcon::setBitmaps(const Bitmap& newBackgroundReleased, const Bitmap& newBackgroundPressed,
|
||||
const Bitmap& newIconReleased, const Bitmap& newIconPressed)
|
||||
{
|
||||
Button::setBitmaps(newBackgroundReleased, newBackgroundPressed);
|
||||
|
||||
iconReleased = newIconReleased;
|
||||
iconPressed = newIconPressed;
|
||||
|
||||
iconX = (getWidth() / 2) - (newIconPressed.getWidth() / 2);
|
||||
iconY = (getHeight() / 2) - (newIconPressed.getHeight() / 2);
|
||||
}
|
||||
|
||||
void ButtonWithIcon::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
Button::draw(invalidatedArea);
|
||||
|
||||
const Bitmap bmp(pressed ? iconPressed : iconReleased);
|
||||
const Rect iconRect(iconX, iconY, bmp.getWidth(), bmp.getHeight());
|
||||
Rect dirty = invalidatedArea & iconRect;
|
||||
if ((bmp.getId() != BITMAP_INVALID) && !dirty.isEmpty())
|
||||
{
|
||||
Rect r;
|
||||
translateRectToAbsolute(r);
|
||||
dirty.x -= iconX;
|
||||
dirty.y -= iconY;
|
||||
HAL::lcd().drawPartialBitmap(bmp, r.x + iconX, r.y + iconY, dirty, alpha);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,67 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Font.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/ButtonWithLabel.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ButtonWithLabel::ButtonWithLabel()
|
||||
: Button(), typedText(), color(0), colorPressed(0), rotation(TEXT_ROTATE_0), textHeightIncludingSpacing(0)
|
||||
{
|
||||
}
|
||||
|
||||
void ButtonWithLabel::draw(const Rect& area) const
|
||||
{
|
||||
Button::draw(area);
|
||||
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
const Font* fontToDraw = typedText.getFont(); // Never return 0
|
||||
const int16_t height = textHeightIncludingSpacing;
|
||||
int16_t offset;
|
||||
Rect labelRect;
|
||||
switch (rotation)
|
||||
{
|
||||
case TEXT_ROTATE_0:
|
||||
offset = (this->getHeight() - height) / 2; // Prefer round down
|
||||
labelRect = Rect(0, offset, this->getWidth(), height);
|
||||
break;
|
||||
case TEXT_ROTATE_180:
|
||||
offset = ((this->getHeight() - height) + 1) / 2; // Prefer round up
|
||||
labelRect = Rect(0, offset, this->getWidth(), height);
|
||||
break;
|
||||
case TEXT_ROTATE_90:
|
||||
offset = ((this->getWidth() - height) + 1) / 2; // Prefer round up
|
||||
labelRect = Rect(offset, 0, height, this->getHeight());
|
||||
break;
|
||||
case TEXT_ROTATE_270:
|
||||
offset = (this->getWidth() - height) / 2; // Prefer round down
|
||||
labelRect = Rect(offset, 0, height, this->getHeight());
|
||||
break;
|
||||
}
|
||||
Rect dirty = labelRect & area;
|
||||
|
||||
if (!dirty.isEmpty())
|
||||
{
|
||||
dirty.x -= labelRect.x;
|
||||
dirty.y -= labelRect.y;
|
||||
translateRectToAbsolute(labelRect);
|
||||
const LCD::StringVisuals visuals(fontToDraw, pressed ? colorPressed : color, alpha, typedText.getAlignment(), 0, rotation, typedText.getTextDirection(), 0, WIDE_TEXT_NONE);
|
||||
HAL::lcd().drawString(labelRect, dirty, visuals, typedText.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,177 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Bitmap.hpp>
|
||||
#include <touchgfx/widgets/Gauge.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Gauge::Gauge()
|
||||
: AbstractProgressIndicator(),
|
||||
needle(),
|
||||
algorithmMoving(TextureMapper::BILINEAR_INTERPOLATION),
|
||||
algorithmSteady(TextureMapper::BILINEAR_INTERPOLATION),
|
||||
needleStartAngle(0),
|
||||
needleEndAngle(0),
|
||||
gaugeCenterX(0),
|
||||
gaugeCenterY(0),
|
||||
needleCenterX(0),
|
||||
needleCenterY(0),
|
||||
arc()
|
||||
{
|
||||
Gauge::remove(progressIndicatorContainer);
|
||||
Gauge::add(arc);
|
||||
Gauge::add(needle);
|
||||
arc.setVisible(false);
|
||||
}
|
||||
|
||||
void Gauge::setWidth(int16_t width)
|
||||
{
|
||||
AbstractProgressIndicator::setWidth(width);
|
||||
needle.setWidth(width);
|
||||
arc.setWidth(width);
|
||||
}
|
||||
|
||||
void Gauge::setHeight(int16_t height)
|
||||
{
|
||||
AbstractProgressIndicator::setHeight(height);
|
||||
needle.setHeight(height);
|
||||
arc.setHeight(height);
|
||||
}
|
||||
|
||||
void Gauge::setBackgroundOffset(int16_t offsetX, int16_t offsetY)
|
||||
{
|
||||
background.setXY(offsetX, offsetY);
|
||||
}
|
||||
|
||||
void Gauge::setCenter(int x, int y)
|
||||
{
|
||||
gaugeCenterX = x;
|
||||
gaugeCenterY = y;
|
||||
setupNeedleTextureMapper();
|
||||
arc.setPixelCenter(x - arc.getX(), y - arc.getY());
|
||||
}
|
||||
|
||||
void Gauge::setArcPosition(int16_t x, int16_t y, int16_t width, int16_t height)
|
||||
{
|
||||
arc.setPosition(x, y, width, height);
|
||||
arc.setPixelCenter(gaugeCenterX - x, gaugeCenterY - y);
|
||||
}
|
||||
|
||||
void Gauge::setNeedle(const BitmapId bitmapId, int16_t rotationCenterX, int16_t rotationCenterY)
|
||||
{
|
||||
needle.setBitmap(Bitmap(bitmapId));
|
||||
needleCenterX = rotationCenterX;
|
||||
needleCenterY = rotationCenterY;
|
||||
setupNeedleTextureMapper();
|
||||
}
|
||||
|
||||
void Gauge::setMovingNeedleRenderingAlgorithm(TextureMapper::RenderingAlgorithm algorithm)
|
||||
{
|
||||
algorithmMoving = algorithm;
|
||||
}
|
||||
|
||||
void Gauge::setSteadyNeedleRenderingAlgorithm(TextureMapper::RenderingAlgorithm algorithm)
|
||||
{
|
||||
algorithmSteady = algorithm;
|
||||
}
|
||||
|
||||
void Gauge::setStartEndAngle(int startAngle, int endAngle)
|
||||
{
|
||||
assert(startAngle != endAngle);
|
||||
needleStartAngle = startAngle;
|
||||
needleEndAngle = endAngle;
|
||||
arc.setArc(startAngle, endAngle);
|
||||
Gauge::setValue(Gauge::getValue());
|
||||
}
|
||||
|
||||
int Gauge::getStartAngle() const
|
||||
{
|
||||
return needleStartAngle;
|
||||
}
|
||||
|
||||
int Gauge::getEndAngle() const
|
||||
{
|
||||
return needleEndAngle;
|
||||
}
|
||||
|
||||
void Gauge::setArcVisible(bool show /*= true*/)
|
||||
{
|
||||
arc.setVisible(show);
|
||||
}
|
||||
|
||||
void Gauge::putArcOnTop(bool onTop /*= true*/)
|
||||
{
|
||||
if (onTop)
|
||||
{
|
||||
remove(arc);
|
||||
add(arc);
|
||||
}
|
||||
else
|
||||
{
|
||||
remove(needle);
|
||||
add(needle);
|
||||
}
|
||||
}
|
||||
|
||||
Circle& Gauge::getArc()
|
||||
{
|
||||
return arc; //lint !e1536
|
||||
}
|
||||
|
||||
void Gauge::setValue(int value)
|
||||
{
|
||||
AbstractProgressIndicator::setValue(value);
|
||||
if (animationStep >= animationDuration)
|
||||
{
|
||||
needle.setRenderingAlgorithm(algorithmSteady);
|
||||
}
|
||||
else
|
||||
{
|
||||
needle.setRenderingAlgorithm(algorithmMoving);
|
||||
}
|
||||
const uint16_t progress = AbstractProgressIndicator::getProgress(abs(needleEndAngle - needleStartAngle));
|
||||
if (needleEndAngle < needleStartAngle)
|
||||
{
|
||||
needle.updateZAngle(((float)(needleStartAngle - progress) / 180.0f) * PI);
|
||||
arc.updateArcEnd(needleStartAngle - progress);
|
||||
}
|
||||
else
|
||||
{
|
||||
needle.updateZAngle(((float)(needleStartAngle + progress) / 180.0f) * PI);
|
||||
arc.updateArcEnd(needleStartAngle + progress);
|
||||
}
|
||||
}
|
||||
|
||||
void Gauge::setAlpha(uint8_t newAlpha)
|
||||
{
|
||||
AbstractProgressIndicator::setAlpha(newAlpha);
|
||||
needle.setAlpha(newAlpha);
|
||||
arc.setAlpha(newAlpha);
|
||||
}
|
||||
|
||||
void Gauge::setupNeedleTextureMapper()
|
||||
{
|
||||
needle.setWidthHeight(*this);
|
||||
needle.setXY(0, 0);
|
||||
needle.setBitmapPosition(gaugeCenterX - needleCenterX, gaugeCenterY - needleCenterY);
|
||||
needle.setCameraDistance(300.0f);
|
||||
needle.setOrigo((float)gaugeCenterX, (float)gaugeCenterY, needle.getCameraDistance());
|
||||
needle.setCamera(needle.getOrigoX(), needle.getOrigoY());
|
||||
needle.setRenderingAlgorithm(TextureMapper::BILINEAR_INTERPOLATION);
|
||||
}
|
||||
|
||||
void Gauge::setProgressIndicatorPosition(int16_t /*x*/, int16_t /*y*/, int16_t /*width*/, int16_t /*height*/)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,51 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/Image.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void Image::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
bitmap = bmp;
|
||||
// When setting bitmap, adjust size of this widget to match.
|
||||
setWidthHeight(bmp);
|
||||
}
|
||||
|
||||
void Image::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
Rect meAbs;
|
||||
translateRectToAbsolute(meAbs); // To find our x and y coords in absolute.
|
||||
|
||||
// Calculate intersection between bitmap rect and invalidated area.
|
||||
const Rect dirtyBitmapArea = bitmap.getRect() & invalidatedArea;
|
||||
|
||||
if (!dirtyBitmapArea.isEmpty())
|
||||
{
|
||||
HAL::lcd().drawPartialBitmap(bitmap, meAbs.x, meAbs.y, dirtyBitmapArea, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
Rect Image::getSolidRect() const
|
||||
{
|
||||
// If alpha is less than solid, we have an empty solid rect.
|
||||
if (alpha < 255)
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
// Return solid rect from bitmap (precalculated).
|
||||
return bitmap.getSolidRect();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,292 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Bitmap.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Font.hpp>
|
||||
#include <touchgfx/FontManager.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/Keyboard.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Keyboard::Keyboard()
|
||||
: Container(), keyListener(0), buffer(0), bufferSize(0), bufferPosition(0), image(), enteredText(), layout(0), keyMappingList(0), highlightImage(), cancelIsEmitted(false)
|
||||
{
|
||||
setTouchable(true);
|
||||
|
||||
image.setXY(0, 0);
|
||||
Keyboard::add(image);
|
||||
|
||||
highlightImage.setVisible(false);
|
||||
Keyboard::add(highlightImage);
|
||||
|
||||
enteredText.setColor(Color::getColorFromRGB(0, 0, 0));
|
||||
Keyboard::add(enteredText);
|
||||
}
|
||||
|
||||
void Keyboard::setBuffer(Unicode::UnicodeChar* newBuffer, uint16_t newBufferSize)
|
||||
{
|
||||
buffer = newBuffer;
|
||||
bufferSize = newBufferSize;
|
||||
|
||||
enteredText.setWildcard(buffer);
|
||||
|
||||
// Place cursor at end of string if we already have something
|
||||
// in the edit buffer.
|
||||
bufferPosition = Unicode::strlen(buffer);
|
||||
}
|
||||
|
||||
void Keyboard::setLayout(const Layout* newLayout)
|
||||
{
|
||||
layout = newLayout;
|
||||
if (newLayout != 0)
|
||||
{
|
||||
image.setBitmap(Bitmap(newLayout->bitmap));
|
||||
|
||||
enteredText.setTypedText(newLayout->textAreaFont);
|
||||
enteredText.setColor(newLayout->textAreaFontColor);
|
||||
enteredText.setPosition(newLayout->textAreaPosition.x, newLayout->textAreaPosition.y,
|
||||
newLayout->textAreaPosition.width, newLayout->textAreaPosition.height);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void Keyboard::setTextIndentation()
|
||||
{
|
||||
if (layout != 0)
|
||||
{
|
||||
const uint8_t indentation = layout->textAreaFont.getFont()->getMaxPixelsLeft();
|
||||
enteredText.setPosition(layout->textAreaPosition.x - indentation, layout->textAreaPosition.y,
|
||||
layout->textAreaPosition.width + indentation * 2, layout->textAreaPosition.height);
|
||||
enteredText.setIndentation(indentation);
|
||||
}
|
||||
}
|
||||
|
||||
Keyboard::Key Keyboard::getKeyForCoordinates(int16_t x, int16_t y) const
|
||||
{
|
||||
Key key;
|
||||
key.keyId = 0; // No key
|
||||
if (layout != 0)
|
||||
{
|
||||
for (uint8_t i = 0; i < layout->numberOfKeys; i++)
|
||||
{
|
||||
if (layout->keyArray[i].keyArea.intersect(x, y))
|
||||
{
|
||||
key = layout->keyArray[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
Keyboard::CallbackArea Keyboard::getCallbackAreaForCoordinates(int16_t x, int16_t y) const
|
||||
{
|
||||
CallbackArea area;
|
||||
area.callback = reinterpret_cast<GenericCallback<>*>(0);
|
||||
if (layout != 0)
|
||||
{
|
||||
for (uint8_t i = 0; i < layout->numberOfCallbackAreas; i++)
|
||||
{
|
||||
if (layout->callbackAreaArray[i].keyArea.intersect(x, y))
|
||||
{
|
||||
area = layout->callbackAreaArray[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
void Keyboard::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
assert(layout && "No layout configured for Keyboard");
|
||||
if (layout != 0)
|
||||
{
|
||||
Font* font = FontManager::getFont(layout->keyFont);
|
||||
assert(font && "Keyboard::draw: Unable to find font, is font db initialized?");
|
||||
if (font != 0)
|
||||
{
|
||||
// Setup visuals for h-center of "string"
|
||||
LCD::StringVisuals visuals;
|
||||
visuals.font = font;
|
||||
visuals.alignment = CENTER;
|
||||
visuals.color = layout->keyFontColor;
|
||||
// String with room for a single character
|
||||
Unicode::UnicodeChar character[2] = { 0, 0 }; // The last is important as string terminator.
|
||||
|
||||
const uint16_t fontHeight = font->getHeight();
|
||||
|
||||
for (uint8_t i = 0; i < layout->numberOfKeys; i++)
|
||||
{
|
||||
const Key& key = layout->keyArray[i];
|
||||
if (key.keyArea.intersect(invalidatedArea))
|
||||
{
|
||||
const uint8_t keyId = key.keyId;
|
||||
const Unicode::UnicodeChar c = getCharForKey(keyId);
|
||||
if (c != 0)
|
||||
{
|
||||
// Get a copy of the keyArea and v-center the area for the character
|
||||
Rect keyArea = key.keyArea;
|
||||
const uint16_t offset = (keyArea.height - fontHeight) / 2;
|
||||
keyArea.y += offset;
|
||||
keyArea.height -= offset;
|
||||
// Calculate the invalidated area relative to the key
|
||||
Rect invalidatedAreaRelative = key.keyArea & invalidatedArea;
|
||||
invalidatedAreaRelative.x -= keyArea.x;
|
||||
invalidatedAreaRelative.y -= keyArea.y;
|
||||
// Set up string with one character
|
||||
character[0] = c;
|
||||
translateRectToAbsolute(keyArea);
|
||||
HAL::lcd().drawString(keyArea, invalidatedAreaRelative, visuals, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Keyboard::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
const ClickEvent::ClickEventType type = event.getType();
|
||||
if (type == ClickEvent::RELEASED && cancelIsEmitted)
|
||||
{
|
||||
cancelIsEmitted = false;
|
||||
return;
|
||||
}
|
||||
const int16_t x = event.getX();
|
||||
const int16_t y = event.getY();
|
||||
Rect toDraw;
|
||||
|
||||
Keyboard::CallbackArea callbackArea = getCallbackAreaForCoordinates(x, y);
|
||||
if (callbackArea.callback != 0)
|
||||
{
|
||||
if (type == ClickEvent::PRESSED)
|
||||
{
|
||||
highlightImage.setXY(callbackArea.keyArea.x, callbackArea.keyArea.y);
|
||||
highlightImage.setBitmap(Bitmap(callbackArea.highlightBitmapId));
|
||||
highlightImage.setVisible(true);
|
||||
toDraw = highlightImage.getRect();
|
||||
invalidateRect(toDraw);
|
||||
}
|
||||
|
||||
if ((type == ClickEvent::RELEASED) && callbackArea.callback->isValid())
|
||||
{
|
||||
callbackArea.callback->execute();
|
||||
if (keyListener)
|
||||
{
|
||||
keyListener->execute(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const Keyboard::Key key = getKeyForCoordinates(x, y);
|
||||
|
||||
if (type == ClickEvent::PRESSED)
|
||||
{
|
||||
if (key.keyId != 0)
|
||||
{
|
||||
highlightImage.setXY(key.keyArea.x, key.keyArea.y);
|
||||
highlightImage.setBitmap(Bitmap(key.highlightBitmapId));
|
||||
highlightImage.setVisible(true);
|
||||
toDraw = highlightImage.getRect();
|
||||
invalidateRect(toDraw);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == ClickEvent::RELEASED)
|
||||
{
|
||||
if (key.keyId != 0 && buffer)
|
||||
{
|
||||
const Unicode::UnicodeChar c = getCharForKey(key.keyId);
|
||||
if (c != 0 && bufferPosition < (bufferSize - 1))
|
||||
{
|
||||
enteredText.invalidateContent();
|
||||
buffer[bufferPosition++] = c;
|
||||
buffer[bufferPosition] = 0;
|
||||
enteredText.invalidateContent();
|
||||
if (keyListener)
|
||||
{
|
||||
keyListener->execute(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == ClickEvent::RELEASED || type == ClickEvent::CANCEL)
|
||||
{
|
||||
toDraw = highlightImage.getRect();
|
||||
highlightImage.setVisible(false);
|
||||
invalidateRect(toDraw);
|
||||
|
||||
if (type == ClickEvent::CANCEL)
|
||||
{
|
||||
cancelIsEmitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Keyboard::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
if (highlightImage.isVisible() && (!highlightImage.getRect().intersect(static_cast<int16_t>(event.getNewX()), static_cast<int16_t>(event.getNewY()))) && (!cancelIsEmitted))
|
||||
{
|
||||
// Send a CANCEL click event, if user has dragged out of currently pressed/highlighted key.
|
||||
const ClickEvent cancelEvent(ClickEvent::CANCEL, static_cast<int16_t>(event.getOldX()), static_cast<int16_t>(event.getOldY()));
|
||||
handleClickEvent(cancelEvent);
|
||||
}
|
||||
}
|
||||
|
||||
Unicode::UnicodeChar Keyboard::getCharForKey(uint8_t keyId) const
|
||||
{
|
||||
Unicode::UnicodeChar ch = 0;
|
||||
if (keyMappingList != 0)
|
||||
{
|
||||
for (uint8_t i = 0; i < keyMappingList->numberOfKeys; i++)
|
||||
{
|
||||
if (keyMappingList->keyMappingArray[i].keyId == keyId)
|
||||
{
|
||||
ch = keyMappingList->keyMappingArray[i].keyValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
void Keyboard::setupDrawChain(const Rect& invalidatedArea, Drawable** nextPreviousElement)
|
||||
{
|
||||
// Keyboard is a Container, and they do not normally appear in the draw chain (they just draw children).
|
||||
// But this particular container actually has a draw() function implementation, so we must change default
|
||||
// behavior.
|
||||
// First, add children
|
||||
Container::setupDrawChain(invalidatedArea, nextPreviousElement);
|
||||
// Then add yourself
|
||||
Drawable::setupDrawChain(invalidatedArea, nextPreviousElement);
|
||||
}
|
||||
|
||||
void Keyboard::setBufferPosition(uint16_t newPos)
|
||||
{
|
||||
bufferPosition = newPos;
|
||||
enteredText.invalidate();
|
||||
}
|
||||
|
||||
void Keyboard::setKeymappingList(const KeyMappingList* newKeyMappingList)
|
||||
{
|
||||
keyMappingList = newKeyMappingList;
|
||||
invalidate();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,65 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/PixelDataWidget.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PixelDataWidget::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (!buffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to lcd coordinates.
|
||||
const Rect absolute = getAbsoluteRect();
|
||||
// Copy to LCD
|
||||
HAL::lcd().blitCopy(buffer, format, absolute, invalidatedArea, alpha, false);
|
||||
}
|
||||
|
||||
Rect PixelDataWidget::getSolidRect() const
|
||||
{
|
||||
if (buffer && alpha == 255)
|
||||
{
|
||||
// There are at least some solid pixels
|
||||
switch (format)
|
||||
{
|
||||
case Bitmap::BW:
|
||||
case Bitmap::BW_RLE:
|
||||
case Bitmap::GRAY2:
|
||||
case Bitmap::GRAY4:
|
||||
case Bitmap::RGB565:
|
||||
case Bitmap::RGB888:
|
||||
case Bitmap::COMPRESSED_RGB565:
|
||||
case Bitmap::COMPRESSED_RGB888:
|
||||
// All solid pixels
|
||||
return Rect(0, 0, getWidth(), getHeight());
|
||||
case Bitmap::ARGB8888:
|
||||
case Bitmap::COMPRESSED_ARGB8888:
|
||||
case Bitmap::ARGB2222:
|
||||
case Bitmap::ABGR2222:
|
||||
case Bitmap::RGBA2222:
|
||||
case Bitmap::BGRA2222:
|
||||
case Bitmap::L8:
|
||||
case Bitmap::A4:
|
||||
case Bitmap::CUSTOM:
|
||||
// No knowledge about solid pixels
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Empty rectangle
|
||||
return Rect();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,311 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <string.h>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/QRCode.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
QRCode::QRCode()
|
||||
: qrCodeData(0),
|
||||
qrTempBuffer(0),
|
||||
qrCodeVersion(1),
|
||||
sizeOfQRCodeSymbol(0),
|
||||
scale(1),
|
||||
eccLevel(ECC_LOW),
|
||||
alpha(0xFF)
|
||||
{
|
||||
setColors(0x000000, 0xFFFFFF);
|
||||
}
|
||||
|
||||
bool QRCode::convertStringToQRCode(const char* text)
|
||||
{
|
||||
if (qrTempBuffer && qrCodeData)
|
||||
{
|
||||
// Clear old QR size field.
|
||||
qrCodeData[0] = 0;
|
||||
bool ok = qrcodegen_encodeText(text, qrTempBuffer, qrCodeData, (qrcodegen_Ecc)eccLevel,
|
||||
qrCodeVersion, qrCodeVersion, qrcodegen_Mask_AUTO, true);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QRCode::convertBinaryDataToQRCode(const uint8_t* data, size_t length)
|
||||
{
|
||||
const size_t bufferLength = qrcodegen_BUFFER_LEN_FOR_VERSION(qrCodeVersion);
|
||||
// Assuming qrTempBuffer has correct length (bufferLength)
|
||||
if (length <= bufferLength)
|
||||
{
|
||||
if (qrTempBuffer && qrCodeData)
|
||||
{
|
||||
// Copy data to scratch buffer
|
||||
memcpy(qrTempBuffer, data, length);
|
||||
// Clear old QR size field.
|
||||
qrCodeData[0] = 0;
|
||||
bool ok = qrcodegen_encodeBinary(qrTempBuffer, length, qrCodeData, (qrcodegen_Ecc)eccLevel,
|
||||
qrCodeVersion, qrCodeVersion, qrcodegen_Mask_AUTO, true);
|
||||
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* QRCode::drawBitRGB565(uint8_t* dst, bool on, int pixels) const
|
||||
{
|
||||
uint16_t* dst16 = (uint16_t*)dst;
|
||||
uint16_t* const dst_end = dst16 + pixels;
|
||||
const uint16_t color = on ? color0_565 : color1_565;
|
||||
while (dst16 < dst_end)
|
||||
{
|
||||
*dst16++ = color;
|
||||
}
|
||||
return (uint8_t*)dst16;
|
||||
}
|
||||
|
||||
uint8_t* QRCode::drawBitRGB565Blend(uint8_t* dst, bool on, int pixels) const
|
||||
{
|
||||
uint16_t* dst16 = (uint16_t*)dst;
|
||||
uint16_t* const dst_end = dst16 + pixels;
|
||||
|
||||
const uint16_t alphaR = on ? alphaR0 : alphaR1;
|
||||
const uint16_t alphaG = on ? alphaG0 : alphaG1;
|
||||
const uint16_t alphaB = on ? alphaB0 : alphaB1;
|
||||
|
||||
while (dst16 < dst_end)
|
||||
{
|
||||
const uint16_t bufpix = *dst16;
|
||||
const uint8_t fbr = Color::getRedFromRGB565(bufpix);
|
||||
const uint8_t fbg = Color::getGreenFromRGB565(bufpix);
|
||||
const uint8_t fbb = Color::getBlueFromRGB565(bufpix);
|
||||
|
||||
*dst16++ = getRGB565ColorFromRGB(div255(alphaR + fbr * ialpha), div255(alphaG + fbg * ialpha), div255(alphaB + fbb * ialpha));
|
||||
}
|
||||
return (uint8_t*)dst16;
|
||||
}
|
||||
|
||||
uint8_t* QRCode::drawBitRGB888(uint8_t* dst, bool on, int pixels) const
|
||||
{
|
||||
uint8_t* const dst_end = dst + pixels * 3;
|
||||
|
||||
const uint8_t r = on ? r0 : r1;
|
||||
const uint8_t g = on ? g0 : g1;
|
||||
const uint8_t b = on ? b0 : b1;
|
||||
|
||||
while (dst < dst_end)
|
||||
{
|
||||
*dst++ = b;
|
||||
*dst++ = g;
|
||||
*dst++ = r;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
uint8_t* QRCode::drawBitRGB888Blend(uint8_t* dst, bool on, int pixels) const
|
||||
{
|
||||
uint8_t* const dst_end = dst + pixels * 3;
|
||||
const uint16_t alphaRed = on ? alphaR0 : alphaR1;
|
||||
const uint16_t alphaGreen = on ? alphaG0 : alphaG1;
|
||||
const uint16_t alphaBlue = on ? alphaB0 : alphaB1;
|
||||
|
||||
while (dst < dst_end)
|
||||
{
|
||||
*dst = div255(alphaBlue + *dst * ialpha);
|
||||
dst++; // Avoid side effects
|
||||
*dst = div255(alphaGreen + *dst * ialpha);
|
||||
dst++; // Avoid side effects
|
||||
*dst = div255(alphaRed + *dst * ialpha);
|
||||
dst++; // Avoid side effects
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
uint8_t* QRCode::drawBitARGB8888(uint8_t* dst, bool on, int pixels) const
|
||||
{
|
||||
uint8_t* const dst_end = dst + pixels * 4;
|
||||
|
||||
const uint8_t r = on ? r0 : r1;
|
||||
const uint8_t g = on ? g0 : g1;
|
||||
const uint8_t b = on ? b0 : b1;
|
||||
|
||||
while (dst < dst_end)
|
||||
{
|
||||
*dst++ = b;
|
||||
*dst++ = g;
|
||||
*dst++ = r;
|
||||
*dst++ = 0xFF;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
uint8_t* QRCode::drawBitARGB8888Blend(uint8_t* dst, bool on, int pixels) const
|
||||
{
|
||||
uint32_t* dst32 = (uint32_t*)dst;
|
||||
uint32_t* dst32_end = dst32 + pixels;
|
||||
const uint16_t alphaRed = on ? alphaR0 : alphaR1;
|
||||
const uint16_t alphaGreen = on ? alphaG0 : alphaG1;
|
||||
const uint16_t alphaBlue = on ? alphaB0 : alphaB1;
|
||||
|
||||
while (dst32 < dst32_end)
|
||||
{
|
||||
const uint32_t rgbBg = *dst32;
|
||||
const uint8_t alphaBg = rgbBg >> 24;
|
||||
if (alphaBg == 0)
|
||||
{
|
||||
// Completely transparent background
|
||||
uint8_t* dst8 = (uint8_t*)dst32;
|
||||
if (on)
|
||||
{
|
||||
*dst8++ = b0;
|
||||
*dst8++ = g0;
|
||||
*dst8++ = r0;
|
||||
*dst8 = alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
*dst8++ = b1;
|
||||
*dst8++ = g1;
|
||||
*dst8++ = r1;
|
||||
*dst8 = alpha;
|
||||
}
|
||||
dst32++;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t alphaMult = div255(alpha * alphaBg);
|
||||
const uint8_t alphaOut = alpha + alphaBg - alphaMult;
|
||||
|
||||
const uint8_t blueOut = (alphaBlue + (rgbBg & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t greenOut = (alphaGreen + ((rgbBg >> 8) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t redOut = (alphaRed + ((rgbBg >> 16) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
|
||||
*dst32++ = (alphaOut << 24) | (redOut << 16) | (greenOut << 8) | blueOut;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint8_t*)dst32;
|
||||
}
|
||||
|
||||
void QRCode::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
// To check if qrCodeData is ready to be drawn
|
||||
int result = qrCodeData[0];
|
||||
if (!((qrcodegen_VERSION_MIN * 4 + 17) <= result) || !(result <= (qrcodegen_VERSION_MAX * 4 + 17)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (alpha == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rect absInvalidated = invalidatedArea;
|
||||
translateRectToAbsolute(absInvalidated);
|
||||
|
||||
// QR bit drawing function
|
||||
uint8_t* (QRCode::*drawfunc)(uint8_t * dst, bool on, int pixels) const = 0;
|
||||
|
||||
const int bpp = HAL::lcd().bitDepth();
|
||||
|
||||
// Select correct drawing function for framebuffer format and alpha
|
||||
switch (bpp)
|
||||
{
|
||||
case 16:
|
||||
// RGB565
|
||||
drawfunc = (alpha < 255) ? &QRCode::drawBitRGB565Blend : &QRCode::drawBitRGB565;
|
||||
break;
|
||||
case 24:
|
||||
// RGB888
|
||||
drawfunc = (alpha < 255) ? &QRCode::drawBitRGB888Blend : &QRCode::drawBitRGB888;
|
||||
break;
|
||||
case 32:
|
||||
// ARGB8888
|
||||
drawfunc = (alpha < 255) ? &QRCode::drawBitARGB8888Blend : &QRCode::drawBitARGB8888;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const int bytespp = bpp / 8;
|
||||
const int line_stride = HAL::lcd().framebufferStride();
|
||||
|
||||
uint8_t* const fb = (uint8_t*)HAL::getInstance()->lockFrameBuffer();
|
||||
|
||||
if (HAL::DISPLAY_ROTATION == rotate90)
|
||||
{
|
||||
Rect transformedAbs = absInvalidated;
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(transformedAbs);
|
||||
// Find address of first pixel in (unrotated) framebuffer = top right corner of inv. area.
|
||||
uint8_t* fb_line = fb + bytespp * (transformedAbs.x + transformedAbs.y * HAL::FRAME_BUFFER_WIDTH);
|
||||
// Start in rightmost column = top row in framebuffer
|
||||
// Drawing downwards in display = increasing address in framebuffer
|
||||
for (int col = 0; col < invalidatedArea.width; col++)
|
||||
{
|
||||
const int qr_x = (invalidatedArea.right() - 1 - col) / scale;
|
||||
const int row_end = invalidatedArea.y + invalidatedArea.height;
|
||||
uint8_t* dest = fb_line;
|
||||
for (int row = invalidatedArea.y; row < row_end;)
|
||||
{
|
||||
const int length = MIN(scale - row % scale, row_end - row);
|
||||
const bool bit = getQRBit(qr_x, row / scale);
|
||||
dest = (this->*drawfunc)(dest, bit, length);
|
||||
row += length;
|
||||
}
|
||||
fb_line += line_stride;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t* fb_line = fb + (absInvalidated.x + absInvalidated.y * HAL::FRAME_BUFFER_WIDTH) * bytespp;
|
||||
for (int y = 0; y < absInvalidated.height; y++)
|
||||
{
|
||||
const int qr_y = (invalidatedArea.y + y) / scale;
|
||||
const int x_end = invalidatedArea.x + invalidatedArea.width;
|
||||
uint8_t* dest = fb_line;
|
||||
for (int x = invalidatedArea.x; x < x_end;)
|
||||
{
|
||||
const int length = MIN(scale - x % scale, x_end - x);
|
||||
const bool bit = getQRBit(x / scale, qr_y);
|
||||
dest = (this->*drawfunc)(dest, bit, length);
|
||||
x += length;
|
||||
}
|
||||
fb_line += line_stride;
|
||||
}
|
||||
}
|
||||
|
||||
HAL::getInstance()->unlockFrameBuffer();
|
||||
}
|
||||
|
||||
Rect QRCode::getSolidRect() const
|
||||
{
|
||||
// Widget does not draw anything if it has no valid qrCodeData, so
|
||||
// report transparent to show background
|
||||
if (alpha == 255 && qrCodeData && qrCodeData[0] != 0)
|
||||
{
|
||||
return Rect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
return Rect();
|
||||
}
|
||||
|
||||
void QRCode::updateWidthAndHeight()
|
||||
{
|
||||
setWidth(sizeOfQRCodeSymbol * scale);
|
||||
setHeight(sizeOfQRCodeSymbol * scale);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,96 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/RadioButton.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void RadioButton::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const Bitmap bitmap = getCurrentlyDisplayedBitmap();
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
Rect meAbs;
|
||||
translateRectToAbsolute(meAbs); // To find our x and y coords in absolute.
|
||||
|
||||
// Calculate intersection between bitmap rect and invalidated area.
|
||||
const Rect dirtyBitmapArea = bitmap.getRect() & invalidatedArea;
|
||||
|
||||
if (!dirtyBitmapArea.isEmpty())
|
||||
{
|
||||
HAL::lcd().drawPartialBitmap(bitmap, meAbs.x, meAbs.y, dirtyBitmapArea, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RadioButton::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
const bool wasPressed = pressed;
|
||||
pressed = (event.getType() == ClickEvent::PRESSED);
|
||||
if ((pressed && !wasPressed) || (!pressed && wasPressed))
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
if (wasPressed && (event.getType() == ClickEvent::RELEASED))
|
||||
{
|
||||
if (deselectionEnabled)
|
||||
{
|
||||
setSelected(!getSelected());
|
||||
}
|
||||
else if (!getSelected())
|
||||
{
|
||||
setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RadioButton::setBitmaps(const Bitmap& bmpUnselected, const Bitmap& bmpUnselectedPressed, const Bitmap& bmpSelected, const Bitmap& bmpSelectedPressed)
|
||||
{
|
||||
bitmapUnselected = bmpUnselected;
|
||||
bitmapUnselectedPressed = bmpUnselectedPressed;
|
||||
bitmapSelected = bmpSelected;
|
||||
bitmapSelectedPressed = bmpSelectedPressed;
|
||||
|
||||
RadioButton::setWidthHeight(bitmapUnselected);
|
||||
}
|
||||
|
||||
Rect RadioButton::getSolidRect() const
|
||||
{
|
||||
if (alpha < 255)
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
|
||||
return getCurrentlyDisplayedBitmap().getSolidRect();
|
||||
}
|
||||
|
||||
void RadioButton::setSelected(bool newSelected)
|
||||
{
|
||||
const bool wasSelected = selected;
|
||||
selected = newSelected;
|
||||
|
||||
if (wasSelected && !newSelected)
|
||||
{
|
||||
executeDeselectedAction();
|
||||
}
|
||||
|
||||
if (!wasSelected && newSelected)
|
||||
{
|
||||
executeAction();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,82 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/widgets/RepeatButton.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
RepeatButton::RepeatButton()
|
||||
: Button(), ticksDelay(30), ticksInterval(15), ticks(0), ticksBeforeContinuous(0)
|
||||
{
|
||||
}
|
||||
|
||||
void RepeatButton::setDelay(int delay)
|
||||
{
|
||||
ticksDelay = delay;
|
||||
}
|
||||
|
||||
int RepeatButton::getDelay()
|
||||
{
|
||||
return ticksDelay;
|
||||
}
|
||||
|
||||
void RepeatButton::setInterval(int interval)
|
||||
{
|
||||
ticksInterval = interval;
|
||||
}
|
||||
|
||||
int RepeatButton::getInterval()
|
||||
{
|
||||
return ticksInterval;
|
||||
}
|
||||
|
||||
void RepeatButton::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
pressed = false; // To prevent AbstractButton from calling action->execute().
|
||||
invalidate(); // Force redraw after forced state change
|
||||
Button::handleClickEvent(event);
|
||||
if (event.getType() == ClickEvent::PRESSED)
|
||||
{
|
||||
executeAction();
|
||||
|
||||
ticks = 0;
|
||||
ticksBeforeContinuous = ticksDelay;
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
void RepeatButton::handleTickEvent()
|
||||
{
|
||||
Button::handleTickEvent();
|
||||
|
||||
if (!pressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ticks == ticksBeforeContinuous)
|
||||
{
|
||||
executeAction();
|
||||
|
||||
ticks = 0;
|
||||
ticksBeforeContinuous = ticksInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks++;
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,264 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <math.h>
|
||||
#include <touchgfx/hal/VectorRenderer.hpp>
|
||||
#include <touchgfx/widgets/SVGImage.hpp>
|
||||
|
||||
// Declarations, defined in SVGDatabase.cpp
|
||||
namespace SVGDatabase
|
||||
{
|
||||
const touchgfx::VGObject* getInstance();
|
||||
uint16_t getInstanceSize();
|
||||
} // namespace SVGDatabase
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void SVGImage::setSVG(uint16_t id)
|
||||
{
|
||||
if (id >= SVGDatabase::getInstanceSize())
|
||||
{
|
||||
svgId = SVG_INVALID;
|
||||
return;
|
||||
}
|
||||
|
||||
svgId = id;
|
||||
reset();
|
||||
|
||||
// If the widget is empty, resize it to the size of the SVG
|
||||
if (rect.isEmpty())
|
||||
{
|
||||
resizeToCurrentSVG();
|
||||
}
|
||||
}
|
||||
|
||||
void SVGImage::resizeToCurrentSVG()
|
||||
{
|
||||
if (svgId == SVG_INVALID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const VGObject* const table = SVGDatabase::getInstance();
|
||||
const VGObject& image = table[svgId];
|
||||
const Matrix3x3 matrix = getTransformationMatrix();
|
||||
// Transform bounding box corners
|
||||
const Matrix3x3::Point p1 = matrix.affineTransform(0.0f, 0.0f);
|
||||
const Matrix3x3::Point p2 = matrix.affineTransform(0.0f, image.imageHeight);
|
||||
const Matrix3x3::Point p3 = matrix.affineTransform(image.imageWidth, 0.0f);
|
||||
const Matrix3x3::Point p4 = matrix.affineTransform(image.imageWidth, image.imageHeight);
|
||||
// Find limits of transformed box
|
||||
const float x_max = MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x));
|
||||
const float y_max = MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y));
|
||||
setWidthHeight(static_cast<int16_t>(ceilf(x_max)), static_cast<int16_t>(ceilf(y_max)));
|
||||
}
|
||||
|
||||
void SVGImage::reset()
|
||||
{
|
||||
scaleX = 1.0f;
|
||||
scaleY = 1.0f;
|
||||
rotation = 0.0f;
|
||||
rotationCenterX = 0.0f;
|
||||
rotationCenterY = 0.0f;
|
||||
imagePositionX = 0.0f;
|
||||
imagePositionY = 0.0f;
|
||||
}
|
||||
|
||||
float SVGImage::getImageWidth() const
|
||||
{
|
||||
if (svgId == SVG_INVALID)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
const VGObject* const table = SVGDatabase::getInstance();
|
||||
const VGObject& image = table[svgId];
|
||||
return image.imageWidth;
|
||||
}
|
||||
|
||||
float SVGImage::getImageHeight() const
|
||||
{
|
||||
if (svgId == SVG_INVALID)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
const VGObject* const table = SVGDatabase::getInstance();
|
||||
const VGObject& image = table[svgId];
|
||||
return image.imageHeight;
|
||||
}
|
||||
|
||||
void SVGImage::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (svgId == SVG_INVALID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const VGObject* const table = SVGDatabase::getInstance();
|
||||
const VGObject& image = table[svgId];
|
||||
|
||||
VectorRenderer* renderer = VectorRenderer::getInstance();
|
||||
assert(renderer && "No VectorRenderer instance found!");
|
||||
|
||||
renderer->setup(getAbsoluteRect(), invalidatedArea);
|
||||
|
||||
const Matrix3x3 matrix = getTransformationMatrix();
|
||||
for (const VGShape* shape = image.shape; shape != 0; shape = shape->next)
|
||||
{
|
||||
Matrix3x3 shapeMatrix;
|
||||
shapeMatrix.setAffineTransformation(shape->transformation);
|
||||
shapeMatrix = matrix.multiply(shapeMatrix);
|
||||
|
||||
// Skip entire shape if bounding box does not cover invalidatedArea
|
||||
// Find bounding box including stroke width
|
||||
const float strokeWidthHalf = shape->strokeWidth / 2.0f;
|
||||
const float strokeMiterLengthHalf = shape->strokeLineJoin == VG_STROKE_LINEJOIN_MITER ? (shape->strokeWidth * shape->strokeMiterLimit) / 2.0f - strokeWidthHalf : 0.0f;
|
||||
const float bb_xMin = shape->boundingbox[0] - strokeWidthHalf - strokeMiterLengthHalf;
|
||||
const float bb_yMin = shape->boundingbox[1] - strokeWidthHalf - strokeMiterLengthHalf;
|
||||
const float bb_xMax = shape->boundingbox[2] + strokeWidthHalf + strokeMiterLengthHalf;
|
||||
const float bb_yMax = shape->boundingbox[3] + strokeWidthHalf + strokeMiterLengthHalf;
|
||||
// Transform bounding box corners
|
||||
const Matrix3x3::Point p1 = shapeMatrix.affineTransform(bb_xMin, bb_yMin);
|
||||
const Matrix3x3::Point p2 = shapeMatrix.affineTransform(bb_xMin, bb_yMax);
|
||||
const Matrix3x3::Point p3 = shapeMatrix.affineTransform(bb_xMax, bb_yMin);
|
||||
const Matrix3x3::Point p4 = shapeMatrix.affineTransform(bb_xMax, bb_yMax);
|
||||
// Find limits of transformed box
|
||||
const int16_t x_min = static_cast<int16_t>(floorf(MIN(MIN(p1.x, p2.x), MIN(p3.x, p4.x))));
|
||||
const int16_t x_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x))));
|
||||
const int16_t y_min = static_cast<int16_t>(floorf(MIN(MIN(p1.y, p2.y), MIN(p3.y, p4.y))));
|
||||
const int16_t y_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y))));
|
||||
const Rect shapeBox(x_min, y_min, (x_max - x_min) + 1, (y_max - y_min) + 1);
|
||||
if (shapeBox.intersect(invalidatedArea))
|
||||
{
|
||||
renderer->setTransformationMatrix(shapeMatrix);
|
||||
|
||||
// First fill
|
||||
if (shape->fillMode != VG_NONE)
|
||||
{
|
||||
// Set drawing mode and color for this shape
|
||||
renderer->setMode((shape->drawingMode == VG_FILL_EVEN_ODD) ? VectorRenderer::FILL_EVEN_ODD : VectorRenderer::FILL_NON_ZERO);
|
||||
renderer->setAlpha(shape->fillAlpha);
|
||||
|
||||
// Set the fill color
|
||||
switch (shape->fillMode)
|
||||
{
|
||||
case VG_NONE:
|
||||
break; // Guarded above
|
||||
case VG_FIXED_COLOR:
|
||||
{
|
||||
const VGFixedColor* color = reinterpret_cast<const VGFixedColor*>(shape->fillPaint);
|
||||
renderer->setColor(color->argb);
|
||||
break;
|
||||
}
|
||||
case VG_LINEAR_GRADIENT:
|
||||
{
|
||||
//Set shape box size on linear (and radial) gradient
|
||||
const float width = shape->boundingbox[2] - shape->boundingbox[0];
|
||||
const float height = shape->boundingbox[3] - shape->boundingbox[1];
|
||||
|
||||
const VGLinearGradient* linear = reinterpret_cast<const VGLinearGradient*>(shape->fillPaint);
|
||||
renderer->setLinearGradient(linear->x0, linear->y0, linear->x1, linear->y1,
|
||||
linear->stops, linear->stopPositions, linear->colors,
|
||||
width, height,
|
||||
linear->isSolid, linear->palette);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw this Shape
|
||||
renderer->drawPath(shape->commands, shape->numCommands, shape->points, shape->numPoints, shape->boundingbox);
|
||||
}
|
||||
|
||||
// Then stroke
|
||||
if (shape->strokeMode != VG_NONE)
|
||||
{
|
||||
// Stroking is nonzero
|
||||
renderer->setMode(VectorRenderer::STROKE);
|
||||
renderer->setStrokeWidth(shape->strokeWidth);
|
||||
renderer->setStrokeLineCap(shape->strokeLineCap);
|
||||
renderer->setStrokeLineJoin(shape->strokeLineJoin);
|
||||
renderer->setStrokeMiterLimit(shape->strokeMiterLimit);
|
||||
renderer->setAlpha(shape->strokeAlpha);
|
||||
|
||||
// Set the fill color
|
||||
switch (shape->strokeMode)
|
||||
{
|
||||
case VG_NONE:
|
||||
break; // Guarded above
|
||||
case VG_FIXED_COLOR:
|
||||
{
|
||||
const VGFixedColor* color = reinterpret_cast<const VGFixedColor*>(shape->strokePaint);
|
||||
renderer->setColor(color->argb);
|
||||
break;
|
||||
}
|
||||
case VG_LINEAR_GRADIENT:
|
||||
{
|
||||
//Set shape box size on linear (and radial) gradient
|
||||
const float width = shape->boundingbox[2] - shape->boundingbox[0] + shape->strokeWidth;
|
||||
const float height = shape->boundingbox[3] - shape->boundingbox[1] + shape->strokeWidth;
|
||||
|
||||
const VGLinearGradient* linear = reinterpret_cast<const VGLinearGradient*>(shape->strokePaint);
|
||||
renderer->setLinearGradient(linear->x0, linear->y0, linear->x1, linear->y1,
|
||||
linear->stops, linear->stopPositions, linear->colors,
|
||||
width, height,
|
||||
linear->isSolid, linear->palette);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw this Shape
|
||||
const float boundingbox[4] = { shape->boundingbox[0] - strokeWidthHalf, shape->boundingbox[1] - strokeWidthHalf,
|
||||
shape->boundingbox[2] + strokeWidthHalf, shape->boundingbox[3] + strokeWidthHalf };
|
||||
renderer->drawPath(shape->commands, shape->numCommands, shape->points, shape->numPoints, boundingbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize rendering
|
||||
renderer->tearDown();
|
||||
}
|
||||
|
||||
void SVGImage::invalidateContent() const
|
||||
{
|
||||
if (!visible || svgId == SVG_INVALID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const VGObject* const table = SVGDatabase::getInstance();
|
||||
const VGObject& image = table[svgId];
|
||||
const Matrix3x3 matrix = getTransformationMatrix();
|
||||
// Transform bounding box corners
|
||||
const Matrix3x3::Point p1 = matrix.affineTransform(image.x, image.y);
|
||||
const Matrix3x3::Point p2 = matrix.affineTransform(image.x, image.y + image.height);
|
||||
const Matrix3x3::Point p3 = matrix.affineTransform(image.x + image.width, image.y);
|
||||
const Matrix3x3::Point p4 = matrix.affineTransform(image.x + image.width, image.y + image.height);
|
||||
// Find limits of transformed box
|
||||
const int16_t x_min = static_cast<int16_t>(floorf(MIN(MIN(p1.x, p2.x), MIN(p3.x, p4.x))));
|
||||
const int16_t x_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.x, p2.x), MAX(p3.x, p4.x))));
|
||||
const int16_t y_min = static_cast<int16_t>(floorf(MIN(MIN(p1.y, p2.y), MIN(p3.y, p4.y))));
|
||||
const int16_t y_max = static_cast<int16_t>(ceilf(MAX(MAX(p1.y, p2.y), MAX(p3.y, p4.y))));
|
||||
Rect r(x_min, y_min, (x_max - x_min) + 1, (y_max - y_min) + 1);
|
||||
invalidateRect(r);
|
||||
}
|
||||
|
||||
touchgfx::Matrix3x3 SVGImage::getTransformationMatrix() const
|
||||
{
|
||||
Matrix3x3 matrix; // Implicit reset to identity matrix
|
||||
if (svgId != SVG_INVALID)
|
||||
{
|
||||
matrix.scale(scaleX, scaleY);
|
||||
matrix.translate(imagePositionX - rotationCenterX, imagePositionY - rotationCenterY);
|
||||
matrix.rotate(rotation);
|
||||
matrix.translate(rotationCenterX, rotationCenterY);
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,79 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/ScalableImage.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
ScalableImage::ScalableImage(const Bitmap& bmp /*= Bitmap() */)
|
||||
: Image(bmp),
|
||||
currentScalingAlgorithm(BILINEAR_INTERPOLATION)
|
||||
{
|
||||
}
|
||||
|
||||
void ScalableImage::setScalingAlgorithm(ScalingAlgorithm algorithm)
|
||||
{
|
||||
currentScalingAlgorithm = algorithm;
|
||||
}
|
||||
|
||||
ScalableImage::ScalingAlgorithm ScalableImage::getScalingAlgorithm()
|
||||
{
|
||||
return currentScalingAlgorithm;
|
||||
}
|
||||
|
||||
RenderingVariant ScalableImage::lookupRenderVariant() const
|
||||
{
|
||||
RenderingVariant renderVariant;
|
||||
if (currentScalingAlgorithm == NEAREST_NEIGHBOR)
|
||||
{
|
||||
renderVariant = lookupNearestNeighborRenderVariant(bitmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderVariant = lookupBilinearRenderVariant(bitmap);
|
||||
}
|
||||
return renderVariant;
|
||||
}
|
||||
|
||||
void ScalableImage::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (!alpha)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Rect meAbs = getAbsoluteRect();
|
||||
HAL::lcd().drawScaledBitmap(bitmap, meAbs, invalidatedArea, lookupRenderVariant(), alpha);
|
||||
}
|
||||
|
||||
Rect ScalableImage::getSolidRect() const
|
||||
{
|
||||
// If original image is completely solid the scaled image will also be
|
||||
if (alpha == 255)
|
||||
{
|
||||
const Rect bmpSolid = bitmap.getSolidRect();
|
||||
if (bmpSolid.width == bitmap.getWidth() && bmpSolid.height == bitmap.getHeight())
|
||||
{
|
||||
const Rect widgetRect(0, 0, getWidth(), getHeight());
|
||||
// NOTE: TextureMapper may not fill solid pixels in the whole widget due to antialiasing,
|
||||
// so the intersect is needed as of now until TextureMapper is fixed.
|
||||
return bmpSolid & widgetRect;
|
||||
}
|
||||
}
|
||||
|
||||
// Return empty rect otherwise
|
||||
return Rect();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,58 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/SnapshotWidget.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
SnapshotWidget::SnapshotWidget()
|
||||
: Widget(), bitmapId(BITMAP_INVALID), alpha(255)
|
||||
{
|
||||
}
|
||||
|
||||
void SnapshotWidget::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (alpha == 0 || bitmapId == BITMAP_INVALID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rect absRect(0, 0, Bitmap(bitmapId).getWidth(), rect.height);
|
||||
translateRectToAbsolute(absRect);
|
||||
HAL::lcd().blitCopy((const uint16_t*)Bitmap(bitmapId).getData(), absRect, invalidatedArea, alpha, false);
|
||||
}
|
||||
|
||||
Rect SnapshotWidget::getSolidRect() const
|
||||
{
|
||||
if (alpha < 255 || bitmapId == BITMAP_INVALID)
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
return Rect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
void SnapshotWidget::makeSnapshot()
|
||||
{
|
||||
makeSnapshot(BITMAP_ANIMATION_STORAGE);
|
||||
}
|
||||
|
||||
void SnapshotWidget::makeSnapshot(const BitmapId bmp)
|
||||
{
|
||||
Rect visRect(0, 0, rect.width, rect.height);
|
||||
getVisibleRect(visRect);
|
||||
const Rect absRect = getAbsoluteRect();
|
||||
bitmapId = (HAL::lcd().copyFrameBufferRegionToMemory(visRect, absRect, bmp)) ? bmp : BITMAP_INVALID;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,323 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/TextArea.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
Alignment TextArea::getAlignment() const
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
return typedText.getAlignment();
|
||||
}
|
||||
return LEFT;
|
||||
}
|
||||
|
||||
int16_t TextArea::getTextHeight() const
|
||||
{
|
||||
return typedText.hasValidId() ? calculateTextHeight(typedText.getText(), getWildcard1(), getWildcard2()) : 0;
|
||||
}
|
||||
|
||||
uint16_t TextArea::getTextWidth() const
|
||||
{
|
||||
return typedText.hasValidId() ? typedText.getFont()->getStringWidth(typedText.getTextDirection(), typedText.getText(), getWildcard1(), getWildcard2()) : 0;
|
||||
}
|
||||
|
||||
void TextArea::draw(const Rect& area) const
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
Rect rectToDraw = area;
|
||||
if (typedText.hasValidId() && boundingArea.isValid(typedText.getText()))
|
||||
{
|
||||
rectToDraw &= boundingArea.getRect();
|
||||
}
|
||||
if (!rectToDraw.isEmpty())
|
||||
{
|
||||
const Font* fontToDraw = typedText.getFont();
|
||||
if (fontToDraw != 0)
|
||||
{
|
||||
const LCD::StringVisuals visuals(fontToDraw, color, alpha, getAlignment(), linespace, rotation, typedText.getTextDirection(), indentation, wideTextAction);
|
||||
HAL::lcd().drawString(getAbsoluteRect(), rectToDraw, visuals, typedText.getText(), getWildcard1(), getWildcard2());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::setTypedText(const TypedText& t)
|
||||
{
|
||||
typedText = t;
|
||||
// If this TextArea does not yet have a width and height,
|
||||
// just assign the smallest possible size to fit current text.
|
||||
if (getWidth() == 0 && getHeight() == 0)
|
||||
{
|
||||
resizeToCurrentText();
|
||||
}
|
||||
boundingArea = calculateBoundingArea();
|
||||
}
|
||||
|
||||
void TextArea::resizeToCurrentText()
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
const uint16_t w = getTextWidth();
|
||||
const uint16_t h = getTextHeight();
|
||||
if (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180)
|
||||
{
|
||||
setWidthHeight(w, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
setWidthHeight(h, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::resizeToCurrentTextWithAlignment()
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
const Alignment alignment = getAlignment();
|
||||
const uint16_t text_width = getTextWidth();
|
||||
const uint16_t text_height = getTextHeight();
|
||||
if (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180)
|
||||
{
|
||||
// (rotate-0 && left-align) or (rotate-180 && right-align) places text to the left
|
||||
if (!((rotation == TEXT_ROTATE_0 && alignment == LEFT) || (rotation == TEXT_ROTATE_180 && alignment == RIGHT)))
|
||||
{
|
||||
const uint16_t old_width = getWidth();
|
||||
const uint16_t old_x = getX();
|
||||
if (alignment == CENTER)
|
||||
{
|
||||
setX(old_x + (old_width - text_width) / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
setX(old_x + (old_width - text_width));
|
||||
}
|
||||
}
|
||||
if (rotation == TEXT_ROTATE_180)
|
||||
{
|
||||
const uint16_t old_height = getHeight();
|
||||
const uint16_t old_y = getY();
|
||||
setY(old_y + (old_height - text_height));
|
||||
}
|
||||
setWidthHeight(text_width, text_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 90+left or 270+right places text at the same y coordinate
|
||||
if (!((rotation == TEXT_ROTATE_90 && alignment == LEFT) || (rotation == TEXT_ROTATE_270 && alignment == RIGHT)))
|
||||
{
|
||||
const uint16_t old_height = getHeight();
|
||||
const uint16_t old_y = getY();
|
||||
if (alignment == CENTER)
|
||||
{
|
||||
setY(old_y + (old_height - text_width) / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
setY(old_y + (old_height - text_width));
|
||||
}
|
||||
}
|
||||
if (rotation == TEXT_ROTATE_90)
|
||||
{
|
||||
const uint16_t old_width = getWidth();
|
||||
const uint16_t old_x = getX();
|
||||
setX(old_x + (old_width - text_height));
|
||||
}
|
||||
setWidthHeight(text_height, text_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::resizeHeightToCurrentText()
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
const uint16_t h = getTextHeight();
|
||||
if (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180)
|
||||
{
|
||||
setHeight(h);
|
||||
}
|
||||
else
|
||||
{
|
||||
setWidth(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::resizeHeightToCurrentTextWithRotation()
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
const uint16_t h = getTextHeight();
|
||||
switch (rotation)
|
||||
{
|
||||
case TEXT_ROTATE_0:
|
||||
setHeight(h);
|
||||
break;
|
||||
case TEXT_ROTATE_90:
|
||||
setX(rect.right() - h);
|
||||
setWidth(h);
|
||||
break;
|
||||
case TEXT_ROTATE_180:
|
||||
setY(rect.bottom() - h);
|
||||
setHeight(h);
|
||||
break;
|
||||
case TEXT_ROTATE_270:
|
||||
setWidth(h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int16_t TextArea::calculateTextHeight(const Unicode::UnicodeChar* format, ...) const
|
||||
{
|
||||
if (!typedText.hasValidId())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
va_list pArg;
|
||||
va_start(pArg, format);
|
||||
|
||||
const Font* fontToDraw = typedText.getFont();
|
||||
const int16_t textHeight = fontToDraw->getHeight();
|
||||
|
||||
TextProvider textProvider;
|
||||
textProvider.initialize(format, pArg, fontToDraw->getGSUBTable(), fontToDraw->getContextualFormsTable());
|
||||
|
||||
const int16_t numLines = LCD::getNumLines(textProvider, wideTextAction, typedText.getTextDirection(), typedText.getFont(), getWidth() - indentation);
|
||||
|
||||
va_end(pArg);
|
||||
return textHeight + linespace > 0 ? numLines * textHeight + (numLines - 1) * linespace : (numLines > 0 ? textHeight : 0);
|
||||
}
|
||||
|
||||
void TextArea::invalidateContent() const
|
||||
{
|
||||
if (alpha == 0 || !typedText.hasValidId() || rect.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (boundingArea.isValid(typedText.getText()))
|
||||
{
|
||||
Rect boundingRect = boundingArea.getRect();
|
||||
invalidateRect(boundingRect);
|
||||
return;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
TextArea::BoundingArea TextArea::calculateBoundingArea() const
|
||||
{
|
||||
if (!typedText.hasValidId())
|
||||
{
|
||||
return TextArea::BoundingArea(); // Return Invalid BoundingArea
|
||||
}
|
||||
|
||||
const Font* fontToDraw = typedText.getFont();
|
||||
const Unicode::UnicodeChar* textToDraw = typedText.getText();
|
||||
const int16_t fontHeight = fontToDraw->getHeight();
|
||||
const int16_t lineHeight = fontHeight + linespace;
|
||||
int16_t width = 0;
|
||||
uint16_t numberOfLines = 0;
|
||||
|
||||
if (wideTextAction == WIDE_TEXT_NONE)
|
||||
{
|
||||
TextProvider textProvider;
|
||||
textProvider.initialize(textToDraw, fontToDraw->getGSUBTable(), fontToDraw->getContextualFormsTable(), getWildcard1(), getWildcard2());
|
||||
|
||||
int16_t widgetRectHeight = (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180) ? getHeight() : getWidth();
|
||||
|
||||
// Iterate through each line, find the longest line width and sum up the total height of the bounding rectangle
|
||||
do
|
||||
{
|
||||
const uint16_t lineWidth = LCD::stringWidth(textProvider, *(fontToDraw), 0x7FFF, typedText.getTextDirection());
|
||||
width = MAX(width, lineWidth);
|
||||
numberOfLines++;
|
||||
widgetRectHeight -= lineHeight;
|
||||
// Keep reading until end of string or next line completely invisible.
|
||||
} while (!textProvider.endOfString() && widgetRectHeight + fontToDraw->getPixelsAboveTop() > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextProvider wideTextProvider;
|
||||
wideTextProvider.initialize(textToDraw, fontToDraw->getGSUBTable(), fontToDraw->getContextualFormsTable(), getWildcard1(), getWildcard2());
|
||||
|
||||
const int16_t widgetRectWidth = (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180) ? getWidth() : getHeight();
|
||||
int16_t widgetRectHeight = (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180) ? getHeight() : getWidth();
|
||||
LCD::WideTextInternalStruct wtis(wideTextProvider, widgetRectWidth - indentation, widgetRectHeight, typedText.getTextDirection(), fontToDraw, linespace, wideTextAction);
|
||||
|
||||
// Iterate through each line, find the longest line width and sum up the total height of the bounding rectangle
|
||||
do
|
||||
{
|
||||
wtis.scanStringLengthForLine();
|
||||
const uint16_t lineWidth = wtis.getLineWidth();
|
||||
width = MAX(width, lineWidth);
|
||||
numberOfLines++;
|
||||
widgetRectHeight -= lineHeight;
|
||||
// Keep reading until end of string, ellipsis inserted or next line completely invisible.
|
||||
} while (wtis.getCurrChar() != 0 && !wtis.ellipsisAtEndOfLine() && widgetRectHeight + fontToDraw->getPixelsAboveTop() > 0);
|
||||
}
|
||||
int16_t height = (numberOfLines * lineHeight) - linespace;
|
||||
height = MAX(height, fontHeight) + fontToDraw->getPixelsBelowBottom();
|
||||
|
||||
Rect boundingRect(0, 0, width, height);
|
||||
|
||||
// Adjust for alignment
|
||||
const int16_t areaWidth = (rotation == TEXT_ROTATE_0 || rotation == TEXT_ROTATE_180) ? getWidth() : getHeight();
|
||||
switch (getAlignment())
|
||||
{
|
||||
default:
|
||||
case LEFT:
|
||||
boundingRect.x = indentation;
|
||||
break;
|
||||
case CENTER:
|
||||
boundingRect.x = ((areaWidth - boundingRect.width) / 2);
|
||||
break;
|
||||
case RIGHT:
|
||||
boundingRect.x = areaWidth - (boundingRect.width + indentation);
|
||||
break;
|
||||
}
|
||||
|
||||
// Adjust for left and right pixels offsets
|
||||
const uint8_t maxPixelsLeft = fontToDraw->getMaxPixelsLeft();
|
||||
const uint8_t maxPixelsRight = fontToDraw->getMaxPixelsRight();
|
||||
boundingRect.x -= maxPixelsLeft;
|
||||
boundingRect.width += (maxPixelsLeft + maxPixelsRight);
|
||||
|
||||
// Adjust for rotation
|
||||
switch (rotation)
|
||||
{
|
||||
case TEXT_ROTATE_0:
|
||||
break;
|
||||
case TEXT_ROTATE_90:
|
||||
boundingRect = Rect(getWidth() - boundingRect.bottom(), boundingRect.x, boundingRect.height, boundingRect.width);
|
||||
break;
|
||||
case TEXT_ROTATE_180:
|
||||
boundingRect = Rect(getWidth() - boundingRect.right(), getHeight() - boundingRect.bottom(), boundingRect.width, boundingRect.height);
|
||||
break;
|
||||
case TEXT_ROTATE_270:
|
||||
boundingRect = Rect(boundingRect.y, getHeight() - boundingRect.right(), boundingRect.height, boundingRect.width);
|
||||
break;
|
||||
}
|
||||
|
||||
return TextArea::BoundingArea(boundingRect, typedText.getText());
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Font.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/TextAreaWithWildcard.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void TextAreaWithWildcardBase::draw(const Rect& area) const
|
||||
{
|
||||
if (typedText.hasValidId())
|
||||
{
|
||||
const Font* fontToDraw = typedText.getFont();
|
||||
if (fontToDraw != 0)
|
||||
{
|
||||
const LCD::StringVisuals visuals(fontToDraw, color, alpha, typedText.getAlignment(), linespace, rotation, typedText.getTextDirection(), indentation, wideTextAction);
|
||||
HAL::lcd().drawString(getAbsoluteRect(), area, visuals, typedText.getText(), getWildcard1(), getWildcard2());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,360 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <math.h>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/Math3D.hpp>
|
||||
#include <touchgfx/TextureMapTypes.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/TextureMapper.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
TextureMapper::TextureMapper(const Bitmap& bmp /*= Bitmap()*/)
|
||||
: Image(bmp),
|
||||
currentRenderingAlgorithm(NEAREST_NEIGHBOR),
|
||||
xBitmapPosition(0.0f),
|
||||
yBitmapPosition(0.0f),
|
||||
xAngle(0.0f),
|
||||
yAngle(0.0f),
|
||||
zAngle(0.0f),
|
||||
scale(1.0f),
|
||||
xOrigo(0.0f),
|
||||
yOrigo(0.0f),
|
||||
zOrigo(1000.0f),
|
||||
xCamera(0.0f),
|
||||
yCamera(0.0f),
|
||||
cameraDistance(1000.0f),
|
||||
imageX0(0.0f),
|
||||
imageY0(0.0f),
|
||||
imageZ0(1.0f),
|
||||
imageX1(0.0f),
|
||||
imageY1(0.0f),
|
||||
imageZ1(1.0f),
|
||||
imageX2(0.0f),
|
||||
imageY2(0.0f),
|
||||
imageZ2(1.0f),
|
||||
imageX3(0.0f),
|
||||
imageY3(0.0f),
|
||||
imageZ3(1.0f),
|
||||
subDivisionSize(12)
|
||||
{
|
||||
}
|
||||
|
||||
void TextureMapper::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
Image::setBitmap(bmp);
|
||||
applyTransformation();
|
||||
}
|
||||
|
||||
void TextureMapper::applyTransformation()
|
||||
{
|
||||
const uint8_t n = 4;
|
||||
|
||||
const int imgWidth = Bitmap(bitmap).getWidth() + 1;
|
||||
const int imgHeight = Bitmap(bitmap).getHeight() + 1;
|
||||
|
||||
const Point4 vertices[n] = {
|
||||
Point4(xBitmapPosition - 1, yBitmapPosition - 1, cameraDistance),
|
||||
Point4((xBitmapPosition - 1) + imgWidth, yBitmapPosition - 1, cameraDistance),
|
||||
Point4((xBitmapPosition - 1) + imgWidth, (yBitmapPosition - 1) + imgHeight, cameraDistance),
|
||||
Point4(xBitmapPosition - 1, (yBitmapPosition - 1) + imgHeight, cameraDistance),
|
||||
};
|
||||
Point4 transformed[n];
|
||||
|
||||
const Vector4 tm_center(xOrigo, yOrigo, zOrigo);
|
||||
|
||||
Matrix4x4 translateToCenter;
|
||||
translateToCenter.concatenateXTranslation(-tm_center.getX()).concatenateYTranslation(-tm_center.getY()).concatenateZTranslation(-tm_center.getZ());
|
||||
|
||||
Matrix4x4 rotateAroundCenter;
|
||||
rotateAroundCenter.concatenateXRotation(xAngle).concatenateYRotation(yAngle).concatenateZRotation(zAngle);
|
||||
|
||||
Matrix4x4 scaleAroundCenter;
|
||||
scaleAroundCenter.concatenateXScale(scale).concatenateYScale(scale).concatenateZScale(scale);
|
||||
|
||||
Matrix4x4 translateFromCenter;
|
||||
translateFromCenter.concatenateXTranslation(tm_center.getX()).concatenateYTranslation(tm_center.getY()).concatenateZTranslation(tm_center.getZ());
|
||||
|
||||
Matrix4x4 transform = translateFromCenter * scaleAroundCenter * rotateAroundCenter * translateToCenter;
|
||||
|
||||
Matrix4x4 translateToCamera;
|
||||
translateToCamera.concatenateXTranslation(-xCamera);
|
||||
translateToCamera.concatenateYTranslation(-yCamera);
|
||||
|
||||
Matrix4x4 perspectiveProject;
|
||||
perspectiveProject.setViewDistance(cameraDistance);
|
||||
|
||||
Matrix4x4 translateFromCamera;
|
||||
translateFromCamera.concatenateXTranslation(xCamera).concatenateYTranslation(yCamera);
|
||||
|
||||
transform = translateFromCamera * perspectiveProject * translateToCamera * transform;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
transformed[i] = transform * vertices[i];
|
||||
}
|
||||
|
||||
imageX0 = ((float)transformed[0].getX() * cameraDistance / (float)transformed[0].getZ());
|
||||
imageY0 = ((float)transformed[0].getY() * cameraDistance / (float)transformed[0].getZ());
|
||||
imageZ0 = ((float)transformed[0].getZ());
|
||||
|
||||
imageX1 = ((float)transformed[1].getX() * cameraDistance / (float)transformed[1].getZ());
|
||||
imageY1 = ((float)transformed[1].getY() * cameraDistance / (float)transformed[1].getZ());
|
||||
imageZ1 = ((float)transformed[1].getZ());
|
||||
|
||||
imageX2 = ((float)transformed[2].getX() * cameraDistance / (float)transformed[2].getZ());
|
||||
imageY2 = ((float)transformed[2].getY() * cameraDistance / (float)transformed[2].getZ());
|
||||
imageZ2 = ((float)transformed[2].getZ());
|
||||
|
||||
imageX3 = ((float)transformed[3].getX() * cameraDistance / (float)transformed[3].getZ());
|
||||
imageY3 = ((float)transformed[3].getY() * cameraDistance / (float)transformed[3].getZ());
|
||||
imageZ3 = ((float)transformed[3].getZ());
|
||||
}
|
||||
|
||||
Rect TextureMapper::getBoundingRect() const
|
||||
{
|
||||
// MIN and MAX are macros so do not insert them into each other
|
||||
float minXf = MIN(imageX0, imageX1);
|
||||
minXf = MIN(minXf, imageX2);
|
||||
minXf = floorf(MIN(minXf, imageX3));
|
||||
const int16_t minX = (int16_t)(MAX(0, minXf));
|
||||
|
||||
float maxXf = MAX(imageX0, imageX1);
|
||||
maxXf = MAX(maxXf, imageX2);
|
||||
maxXf = ceilf(MAX(maxXf, imageX3));
|
||||
int16_t maxX = getWidth();
|
||||
maxX = (int16_t)(MIN(maxX, maxXf));
|
||||
|
||||
float minYf = MIN(imageY0, imageY1);
|
||||
minYf = MIN(minYf, imageY2);
|
||||
minYf = floorf(MIN(minYf, imageY3));
|
||||
const int16_t minY = (int16_t)(MAX(0, minYf));
|
||||
|
||||
float maxYf = MAX(imageY0, imageY1);
|
||||
maxYf = MAX(maxYf, imageY2);
|
||||
maxYf = ceilf(MAX(maxYf, imageY3));
|
||||
int16_t maxY = getHeight();
|
||||
maxY = (int16_t)(MIN(maxY, maxYf));
|
||||
|
||||
return Rect(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
void TextureMapper::setAngles(float newXAngle, float newYAngle, float newZAngle)
|
||||
{
|
||||
xAngle = newXAngle;
|
||||
yAngle = newYAngle;
|
||||
zAngle = newZAngle;
|
||||
|
||||
applyTransformation();
|
||||
}
|
||||
|
||||
void TextureMapper::updateAngles(float newXAngle, float newYAngle, float newZAngle)
|
||||
{
|
||||
invalidateContent();
|
||||
setAngles(newXAngle, newYAngle, newZAngle);
|
||||
invalidateContent();
|
||||
}
|
||||
|
||||
void TextureMapper::setScale(float newScale)
|
||||
{
|
||||
scale = newScale;
|
||||
|
||||
applyTransformation();
|
||||
}
|
||||
|
||||
void TextureMapper::updateScale(float newScale)
|
||||
{
|
||||
invalidateContent();
|
||||
setScale(newScale);
|
||||
invalidateContent();
|
||||
}
|
||||
|
||||
void TextureMapper::invalidateBoundingRect() const
|
||||
{
|
||||
Rect r = getBoundingRect();
|
||||
invalidateRect(r);
|
||||
}
|
||||
|
||||
void TextureMapper::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (!alpha)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint16_t* fb = 0;
|
||||
|
||||
// Setup texture coordinates
|
||||
const float right = (float)(bitmap.getWidth());
|
||||
const float bottom = (float)(bitmap.getHeight());
|
||||
float textureU0 = -1.0f;
|
||||
float textureV0 = -1.0f;
|
||||
float textureU1 = right;
|
||||
float textureV1 = -1.0f;
|
||||
float textureU2 = right;
|
||||
float textureV2 = bottom;
|
||||
float textureU3 = -1.0f;
|
||||
float textureV3 = bottom;
|
||||
if (HAL::DISPLAY_ROTATION == rotate90)
|
||||
{
|
||||
textureU0 = -1.0f;
|
||||
textureV0 = right;
|
||||
textureU1 = -1.0f;
|
||||
textureV1 = -1.0f;
|
||||
textureU2 = bottom;
|
||||
textureV2 = -1.0f;
|
||||
textureU3 = bottom;
|
||||
textureV3 = right;
|
||||
}
|
||||
|
||||
float triangleXs[4];
|
||||
float triangleYs[4];
|
||||
float triangleZs[4];
|
||||
float triangleUs[4];
|
||||
float triangleVs[4];
|
||||
|
||||
// Determine winding order
|
||||
Vector4 zeroToOne(imageX1 - imageX0, imageY1 - imageY0, imageZ1 - imageZ0);
|
||||
const Vector4 zeroToTwo(imageX2 - imageX0, imageY2 - imageY0, imageZ2 - imageZ0);
|
||||
const Vector4 normal = zeroToOne.crossProduct(zeroToTwo);
|
||||
|
||||
if (normal.getZ() > 0)
|
||||
{
|
||||
triangleXs[0] = imageX0;
|
||||
triangleXs[1] = imageX1;
|
||||
triangleXs[2] = imageX2;
|
||||
triangleXs[3] = imageX3;
|
||||
triangleYs[0] = imageY0;
|
||||
triangleYs[1] = imageY1;
|
||||
triangleYs[2] = imageY2;
|
||||
triangleYs[3] = imageY3;
|
||||
triangleZs[0] = imageZ0;
|
||||
triangleZs[1] = imageZ1;
|
||||
triangleZs[2] = imageZ2;
|
||||
triangleZs[3] = imageZ3;
|
||||
|
||||
triangleUs[0] = textureU0;
|
||||
triangleUs[1] = textureU1;
|
||||
triangleUs[2] = textureU2;
|
||||
triangleUs[3] = textureU3;
|
||||
triangleVs[0] = textureV0;
|
||||
triangleVs[1] = textureV1;
|
||||
triangleVs[2] = textureV2;
|
||||
triangleVs[3] = textureV3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// invert due to the triangles winding order (showing backface of the triangle)
|
||||
triangleXs[1] = imageX0;
|
||||
triangleXs[0] = imageX1;
|
||||
triangleXs[2] = imageX3;
|
||||
triangleXs[3] = imageX2;
|
||||
triangleYs[1] = imageY0;
|
||||
triangleYs[0] = imageY1;
|
||||
triangleYs[2] = imageY3;
|
||||
triangleYs[3] = imageY2;
|
||||
triangleZs[1] = imageZ0;
|
||||
triangleZs[0] = imageZ1;
|
||||
triangleZs[2] = imageZ3;
|
||||
triangleZs[3] = imageZ2;
|
||||
|
||||
triangleUs[1] = textureU0;
|
||||
triangleUs[0] = textureU1;
|
||||
triangleUs[2] = textureU3;
|
||||
triangleUs[3] = textureU2;
|
||||
triangleVs[1] = textureV0;
|
||||
triangleVs[0] = textureV1;
|
||||
triangleVs[2] = textureV3;
|
||||
triangleVs[3] = textureV2;
|
||||
}
|
||||
|
||||
drawQuad(invalidatedArea, fb, triangleXs, triangleYs, triangleZs, triangleUs, triangleVs);
|
||||
}
|
||||
|
||||
void TextureMapper::drawQuad(const Rect& invalidatedArea, uint16_t* fb, const float* triangleXs, const float* triangleYs, const float* triangleZs, const float* triangleUs, const float* triangleVs) const
|
||||
{
|
||||
// Area to redraw. Relative to the TextureMapper.
|
||||
Rect dirtyArea = Rect(0, 0, getWidth(), getHeight()) & invalidatedArea;
|
||||
|
||||
// Absolute position of the TextureMapper.
|
||||
Rect dirtyAreaAbsolute = dirtyArea & getBoundingRect();
|
||||
translateRectToAbsolute(dirtyAreaAbsolute);
|
||||
|
||||
Rect absoluteRect = getAbsoluteRect();
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(absoluteRect);
|
||||
|
||||
// Transform rects to match framebuffer coordinates
|
||||
// This is needed if the display is rotated compared to the framebuffer
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(dirtyArea, this->getRect());
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(dirtyAreaAbsolute);
|
||||
|
||||
// Get a pointer to the bitmap data, return if no bitmap found
|
||||
const uint16_t* textmap = (const uint16_t*)bitmap.getData();
|
||||
if (!textmap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float x0 = triangleXs[0];
|
||||
float x1 = triangleXs[1];
|
||||
float x2 = triangleXs[2];
|
||||
float x3 = triangleXs[3];
|
||||
float y0 = triangleYs[0];
|
||||
float y1 = triangleYs[1];
|
||||
float y2 = triangleYs[2];
|
||||
float y3 = triangleYs[3];
|
||||
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(x0, y0, this->getRect());
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(x1, y1, this->getRect());
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(x2, y2, this->getRect());
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(x3, y3, this->getRect());
|
||||
|
||||
const Point3D point0 = { floatToFixed28_4(x0), floatToFixed28_4(y0), triangleZs[0], triangleUs[0], triangleVs[0] };
|
||||
const Point3D point1 = { floatToFixed28_4(x1), floatToFixed28_4(y1), triangleZs[1], triangleUs[1], triangleVs[1] };
|
||||
const Point3D point2 = { floatToFixed28_4(x2), floatToFixed28_4(y2), triangleZs[2], triangleUs[2], triangleVs[2] };
|
||||
const Point3D point3 = { floatToFixed28_4(x3), floatToFixed28_4(y3), triangleZs[3], triangleUs[3], triangleVs[3] };
|
||||
|
||||
const Point3D vertices[4] = { point0, point1, point2, point3 };
|
||||
|
||||
const DrawingSurface dest = { fb, HAL::FRAME_BUFFER_WIDTH };
|
||||
const TextureSurface src = { textmap, bitmap.getExtraData(), bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth() };
|
||||
|
||||
uint16_t subDivs = subDivisionSize;
|
||||
if (point0.Z == point1.Z && point1.Z == point2.Z) //lint !e777
|
||||
{
|
||||
subDivs = 0xFFFF; // Max: One sweep
|
||||
}
|
||||
HAL::lcd().drawTextureMapQuad(dest, vertices, src, absoluteRect, dirtyAreaAbsolute, lookupRenderVariant(), alpha, subDivs);
|
||||
}
|
||||
|
||||
RenderingVariant TextureMapper::lookupRenderVariant() const
|
||||
{
|
||||
RenderingVariant renderVariant;
|
||||
if (currentRenderingAlgorithm == NEAREST_NEIGHBOR)
|
||||
{
|
||||
renderVariant = lookupNearestNeighborRenderVariant(bitmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderVariant = lookupBilinearRenderVariant(bitmap);
|
||||
}
|
||||
return renderVariant;
|
||||
}
|
||||
|
||||
Rect TextureMapper::getSolidRect() const
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,162 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/TiledImage.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void TiledImage::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
Image::setBitmap(bmp);
|
||||
// Make sure the xOffset and yOffset are correct (in range)
|
||||
setOffset(xOffset, yOffset);
|
||||
}
|
||||
|
||||
void TiledImage::setOffset(int16_t x, int16_t y)
|
||||
{
|
||||
setXOffset(x);
|
||||
setYOffset(y);
|
||||
}
|
||||
|
||||
void TiledImage::setXOffset(int16_t x)
|
||||
{
|
||||
xOffset = x;
|
||||
if (bitmap.getWidth() != 0)
|
||||
{
|
||||
xOffset %= bitmap.getWidth();
|
||||
if (xOffset < 0)
|
||||
{
|
||||
xOffset += bitmap.getWidth();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TiledImage::setYOffset(int16_t y)
|
||||
{
|
||||
yOffset = y;
|
||||
if (bitmap.getHeight() != 0)
|
||||
{
|
||||
yOffset %= bitmap.getHeight();
|
||||
if (yOffset < 0)
|
||||
{
|
||||
yOffset += bitmap.getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TiledImage::getOffset(int16_t& x, int16_t& y)
|
||||
{
|
||||
x = getXOffset();
|
||||
y = getYOffset();
|
||||
}
|
||||
|
||||
int16_t TiledImage::getXOffset()
|
||||
{
|
||||
return xOffset;
|
||||
}
|
||||
|
||||
int16_t TiledImage::getYOffset()
|
||||
{
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
void TiledImage::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const int16_t bitmapWidth = bitmap.getWidth();
|
||||
const int16_t bitmapHeight = bitmap.getHeight();
|
||||
|
||||
if (bitmapWidth == 0 || bitmapHeight == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rect meAbs;
|
||||
translateRectToAbsolute(meAbs);
|
||||
|
||||
const int16_t minX = ((invalidatedArea.x + xOffset) / bitmapWidth) * bitmapWidth - xOffset;
|
||||
const int16_t maxX = (((invalidatedArea.right() + xOffset) - 1) / bitmapWidth) * bitmapWidth;
|
||||
const int16_t minY = ((invalidatedArea.y + yOffset) / bitmapHeight) * bitmapHeight - yOffset;
|
||||
const int16_t maxY = (((invalidatedArea.bottom() + yOffset) - 1) / bitmapHeight) * bitmapHeight;
|
||||
for (int16_t x = minX; x <= maxX; x += bitmapWidth)
|
||||
{
|
||||
for (int16_t y = minY; y <= maxY; y += bitmapHeight)
|
||||
{
|
||||
Rect dirty = Rect(x, y, bitmapWidth, bitmapHeight) & invalidatedArea;
|
||||
dirty.x -= x;
|
||||
dirty.y -= y;
|
||||
HAL::lcd().drawPartialBitmap(bitmap, meAbs.x + x, meAbs.y + y, dirty, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect TiledImage::getSolidRect() const
|
||||
{
|
||||
if (alpha < 255)
|
||||
{
|
||||
return Rect();
|
||||
}
|
||||
|
||||
Rect solidRect = bitmap.getSolidRect();
|
||||
if (solidRect.width == bitmap.getWidth())
|
||||
{
|
||||
solidRect.width = getWidth();
|
||||
}
|
||||
else
|
||||
{
|
||||
solidRect.x -= xOffset;
|
||||
Rect solidRect2 = solidRect;
|
||||
solidRect2.x += bitmap.getWidth();
|
||||
if (solidRect.x < 0)
|
||||
{
|
||||
const int16_t right = solidRect.right();
|
||||
solidRect.width = MAX(right, 0);
|
||||
solidRect.x = 0;
|
||||
}
|
||||
if (solidRect2.right() > getWidth())
|
||||
{
|
||||
solidRect2.width = solidRect2.right() - getWidth();
|
||||
}
|
||||
if (solidRect2.width > solidRect.width)
|
||||
{
|
||||
solidRect = solidRect2;
|
||||
}
|
||||
}
|
||||
if (solidRect.height == bitmap.getHeight())
|
||||
{
|
||||
solidRect.height = getHeight();
|
||||
}
|
||||
else
|
||||
{
|
||||
solidRect.y -= yOffset;
|
||||
Rect solidRect2 = solidRect;
|
||||
solidRect2.y += bitmap.getHeight();
|
||||
if (solidRect.y < 0)
|
||||
{
|
||||
const int16_t bottom = solidRect.bottom();
|
||||
solidRect.height = MAX(bottom, 0);
|
||||
solidRect.y = 0;
|
||||
}
|
||||
if (solidRect2.bottom() > getHeight())
|
||||
{
|
||||
solidRect2.height = solidRect2.bottom() - getHeight();
|
||||
}
|
||||
if (solidRect2.height > solidRect.height)
|
||||
{
|
||||
solidRect = solidRect2;
|
||||
}
|
||||
}
|
||||
return solidRect;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,50 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/events/ClickEvent.hpp>
|
||||
#include <touchgfx/widgets/ToggleButton.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void ToggleButton::forceState(bool activeState)
|
||||
{
|
||||
if (activeState)
|
||||
{
|
||||
// up should equal originalPressed
|
||||
if (up.getId() != originalPressed.getId())
|
||||
{
|
||||
down = up;
|
||||
up = originalPressed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// down should equal originalPressed
|
||||
if (down.getId() != originalPressed.getId())
|
||||
{
|
||||
up = down;
|
||||
down = originalPressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ToggleButton::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
if (pressed && (event.getType() == ClickEvent::RELEASED))
|
||||
{
|
||||
const Bitmap tmp = up;
|
||||
up = down;
|
||||
down = tmp;
|
||||
}
|
||||
Button::handleClickEvent(event);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,27 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/TouchArea.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
class ClickEvent;
|
||||
|
||||
void TouchArea::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
AbstractButton::handleClickEvent(event);
|
||||
if (pressedAction && pressedAction->isValid() && pressed)
|
||||
{
|
||||
pressedAction->execute(*this);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,179 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Application.hpp>
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/widgets/VideoWidget.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
VideoWidget::VideoWidget()
|
||||
: Widget(),
|
||||
movieEndedAction(0), buffer(0), format(Bitmap::RGB888),
|
||||
bufferWidth(0), bufferHeight(0), videoWidth(0), videoHeight(0)
|
||||
{
|
||||
handle = VideoController::getInstance().registerVideoWidget(*this);
|
||||
Application::getInstance()->registerTimerWidget(this);
|
||||
}
|
||||
|
||||
VideoWidget::~VideoWidget()
|
||||
{
|
||||
VideoController::getInstance().unregisterVideoWidget(handle);
|
||||
Application::getInstance()->unregisterTimerWidget(this);
|
||||
}
|
||||
|
||||
void VideoWidget::play() const
|
||||
{
|
||||
VideoController::getInstance().setCommand(handle, VideoController::PLAY, 0);
|
||||
}
|
||||
|
||||
void VideoWidget::pause() const
|
||||
{
|
||||
VideoController::getInstance().setCommand(handle, VideoController::PAUSE, 0);
|
||||
}
|
||||
|
||||
void VideoWidget::stop() const
|
||||
{
|
||||
VideoController::getInstance().setCommand(handle, VideoController::STOP, 0);
|
||||
}
|
||||
|
||||
bool VideoWidget::isPlaying() const
|
||||
{
|
||||
return VideoController::getInstance().getIsPlaying(handle);
|
||||
}
|
||||
|
||||
void VideoWidget::seek(uint32_t frameNumber) const
|
||||
{
|
||||
VideoController::getInstance().setCommand(handle, VideoController::SEEK, frameNumber);
|
||||
}
|
||||
|
||||
void VideoWidget::showFrame(uint32_t frameNumber) const
|
||||
{
|
||||
if (isPlaying())
|
||||
{
|
||||
seek(frameNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoController::getInstance().setCommand(handle, VideoController::SHOW, frameNumber);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoWidget::setRepeat(bool repeat) const
|
||||
{
|
||||
VideoController::getInstance().setCommand(handle, VideoController::SET_REPEAT, repeat ? 1 : 0);
|
||||
}
|
||||
|
||||
void VideoWidget::handleTickEvent()
|
||||
{
|
||||
const bool hasMoreFrames = VideoController::getInstance().updateFrame(handle, *this);
|
||||
if (!hasMoreFrames)
|
||||
{
|
||||
// Now showing last frame
|
||||
if (movieEndedAction && movieEndedAction->isValid())
|
||||
{
|
||||
movieEndedAction->execute(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect VideoWidget::getSolidRect() const
|
||||
{
|
||||
// For Dedicated or DoubleBuffered video we have a bufferWidth > 0
|
||||
// But maybe no buffer yet (only after decoding first frame)
|
||||
if (bufferWidth > 0)
|
||||
{
|
||||
// Solid inside video area if we have a buffer, otherwise transparent
|
||||
if (buffer != 0)
|
||||
{
|
||||
return Rect(0, 0, MIN(videoWidth, bufferWidth), MIN(videoHeight, bufferHeight));
|
||||
}
|
||||
return Rect();
|
||||
}
|
||||
|
||||
//Direct to framebuffer, solid inside the video area
|
||||
return Rect(0, 0, videoWidth, videoHeight);
|
||||
}
|
||||
|
||||
void VideoWidget::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
// Only draw the invalidated area inside the video x buffer
|
||||
Rect invVideoRect(0, 0, videoWidth, videoHeight);
|
||||
invVideoRect &= invalidatedArea;
|
||||
|
||||
// Inform controller that we are drawing
|
||||
VideoController::getInstance().draw(handle, invVideoRect, *this);
|
||||
|
||||
// If we have a buffer, draw that
|
||||
if (buffer != 0)
|
||||
{
|
||||
// Convert to lcd coordinates.
|
||||
Rect absolute = getAbsoluteRect();
|
||||
invVideoRect &= Rect(0, 0, bufferWidth, bufferHeight);
|
||||
// Width is buffer stride in rotate0, and Height is buffer stride in rotate90.
|
||||
if (HAL::DISPLAY_ROTATION == rotate90)
|
||||
{
|
||||
// Limit to buffer size
|
||||
absolute.height = bufferWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Limit to buffer size
|
||||
absolute.width = bufferWidth;
|
||||
}
|
||||
// Copy to LCD, always solid
|
||||
HAL::lcd().blitCopy(buffer, format, absolute, invVideoRect, 255, false);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VideoWidget::getCurrentFrameNumber() const
|
||||
{
|
||||
return VideoController::getInstance().getCurrentFrameNumber(handle);
|
||||
}
|
||||
|
||||
void VideoWidget::setFrameRate(uint32_t ui_frames, uint32_t video_frames) const
|
||||
{
|
||||
VideoController::getInstance().setFrameRate(handle, ui_frames, video_frames);
|
||||
}
|
||||
|
||||
void VideoWidget::setVideoData(const uint8_t* movie, const uint32_t length)
|
||||
{
|
||||
VideoController::getInstance().setVideoData(handle, movie, length);
|
||||
|
||||
readVideoInformation();
|
||||
}
|
||||
|
||||
void VideoWidget::setVideoData(VideoDataReader& reader)
|
||||
{
|
||||
VideoController::getInstance().setVideoData(handle, reader);
|
||||
|
||||
readVideoInformation();
|
||||
}
|
||||
|
||||
void VideoWidget::getVideoInformation(VideoInformation* data) const
|
||||
{
|
||||
VideoController::getInstance().getVideoInformation(handle, data);
|
||||
}
|
||||
|
||||
void VideoWidget::readVideoInformation()
|
||||
{
|
||||
// Set frame rate to movie speed (assuming 60 ui frames in a second)
|
||||
VideoInformation videoInformation;
|
||||
VideoController::getInstance().getVideoInformation(handle, &videoInformation);
|
||||
videoWidth = (int16_t)videoInformation.frame_width;
|
||||
videoHeight = (int16_t)videoInformation.frame_height;
|
||||
const uint32_t videoFramesIn1000ms = 1000 / videoInformation.ms_between_frames;
|
||||
setFrameRate(60, videoFramesIn1000ms);
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/widgets/canvas/AbstractShape.hpp>
|
||||
#include <touchgfx/widgets/canvas/Canvas.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AbstractShape::AbstractShape()
|
||||
: CanvasWidget(),
|
||||
fillingRule(Rasterizer::FILL_NON_ZERO),
|
||||
dx(0), dy(0), shapeAngle(0),
|
||||
xScale(CWRUtil::toQ10<int>(1)), yScale(CWRUtil::toQ10<int>(1)),
|
||||
minimalRect()
|
||||
{
|
||||
Drawable::setWidthHeight(0, 0);
|
||||
}
|
||||
|
||||
bool AbstractShape::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
const int numPoints = getNumPoints();
|
||||
if (numPoints == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidatedArea, getAlpha());
|
||||
|
||||
canvas.setFillingRule(fillingRule);
|
||||
|
||||
canvas.moveTo(getCacheX(0), getCacheY(0));
|
||||
for (int i = 1; i < numPoints; i++)
|
||||
{
|
||||
canvas.lineTo(getCacheX(i), getCacheY(i));
|
||||
if (canvas.wasOutlineTooComplex())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return canvas.render();
|
||||
}
|
||||
|
||||
void AbstractShape::updateAbstractShapeCache()
|
||||
{
|
||||
const int numPoints = getNumPoints();
|
||||
|
||||
int xMin = 0;
|
||||
int xMax = 0;
|
||||
int yMin = 0;
|
||||
int yMax = 0;
|
||||
|
||||
const CWRUtil::Q15 cos = CWRUtil::cosine(shapeAngle);
|
||||
const CWRUtil::Q15 sin = CWRUtil::sine(shapeAngle);
|
||||
|
||||
for (int i = 0; i < numPoints; i++)
|
||||
{
|
||||
CWRUtil::Q5 const xCorner = getCornerX(i);
|
||||
CWRUtil::Q5 const yCorner = getCornerY(i);
|
||||
|
||||
CWRUtil::Q5 const xCache = dx + ((CWRUtil::mulQ5(xCorner, xScale) * cos)) - ((CWRUtil::mulQ5(yCorner, yScale) * sin));
|
||||
if (i == 0 || xCache.to<int>() > xMax)
|
||||
{
|
||||
xMax = xCache.to<int>();
|
||||
}
|
||||
if (i == 0 || xCache.to<int>() < xMin)
|
||||
{
|
||||
xMin = xCache.to<int>();
|
||||
}
|
||||
CWRUtil::Q5 const yCache = dy + ((CWRUtil::mulQ5(yCorner, yScale) * cos)) + ((CWRUtil::mulQ5(xCorner, xScale) * sin));
|
||||
if (i == 0 || yCache.to<int>() > yMax)
|
||||
{
|
||||
yMax = yCache.to<int>();
|
||||
}
|
||||
if (i == 0 || yCache.to<int>() < yMin)
|
||||
{
|
||||
yMin = yCache.to<int>();
|
||||
}
|
||||
setCache(i, xCache, yCache);
|
||||
}
|
||||
minimalRect = Rect(xMin, yMin, (xMax - xMin) + 1, (yMax - yMin) + 1);
|
||||
}
|
||||
|
||||
Rect AbstractShape::getMinimalRect() const
|
||||
{
|
||||
return minimalRect;
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+696
@@ -0,0 +1,696 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <math.h>
|
||||
#include <touchgfx/canvas_widget_renderer/CanvasWidgetRenderer.hpp>
|
||||
#include <touchgfx/widgets/canvas/CWRVectorRenderer.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void CWRVectorRenderer::setup(const Rect& canvasAreaAbs, const Rect& invalidatedAreaRel)
|
||||
{
|
||||
canvasAreaAbsolute = canvasAreaAbs;
|
||||
drawArea = invalidatedAreaRel;
|
||||
canvasPainter = 0;
|
||||
|
||||
// Clear transformation matrix
|
||||
matrix.reset();
|
||||
|
||||
// Alpha reset
|
||||
alpha = 255;
|
||||
colorAlpha = 255;
|
||||
|
||||
// Stroke reset
|
||||
strokeWidth = 1.0f;
|
||||
strokeLineJoin = VG_STROKE_LINEJOIN_MITER;
|
||||
strokeLineCap = VG_STROKE_LINECAP_BUTT;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::tearDown()
|
||||
{
|
||||
// Clear drawing area to avoid drawing any paths until next setup
|
||||
drawArea = Rect();
|
||||
// Wait for the painter to finish
|
||||
if (canvasPainter)
|
||||
{
|
||||
canvasPainter->tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::drawPath(const uint8_t* cmds, uint32_t nCmds, const float* points, uint32_t nPoints, const float* /* bbox */)
|
||||
{
|
||||
// Draw the path. Try reduced areas until success
|
||||
Rect area = drawArea;
|
||||
if (area.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (HAL::DISPLAY_ROTATION)
|
||||
{
|
||||
case rotate0:
|
||||
{
|
||||
const int16_t bottom = area.bottom();
|
||||
while (area.y < bottom)
|
||||
{
|
||||
while (!drawPathArea(cmds, nCmds, points, nPoints, area))
|
||||
{
|
||||
if (area.height == 1)
|
||||
{
|
||||
// Failed on a single line
|
||||
break;
|
||||
}
|
||||
area.height = (area.height + 1) >> 1;
|
||||
#ifdef SIMULATOR
|
||||
if (CanvasWidgetRenderer::getWriteMemoryUsageReport())
|
||||
{
|
||||
if (area.height > 1)
|
||||
{
|
||||
touchgfx_printf("CWR will split draw into multiple draws due to limited memory.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
touchgfx_printf("CWR was unable to complete a draw operation due to limited memory.\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
area.y += area.height;
|
||||
if (area.bottom() > bottom)
|
||||
{
|
||||
area.height = bottom - area.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case rotate90:
|
||||
{
|
||||
const int16_t right = area.right();
|
||||
while (area.x < right)
|
||||
{
|
||||
while (!drawPathArea(cmds, nCmds, points, nPoints, area))
|
||||
{
|
||||
if (area.width == 1)
|
||||
{
|
||||
// Failed on a single line
|
||||
break;
|
||||
}
|
||||
area.width = (area.width + 1) >> 1;
|
||||
#ifdef SIMULATOR
|
||||
if (CanvasWidgetRenderer::getWriteMemoryUsageReport())
|
||||
{
|
||||
if (area.width > 1)
|
||||
{
|
||||
touchgfx_printf("CWR will split draw into multiple draws due to limited memory.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
touchgfx_printf("CWR was unable to complete a draw operation due to limited memory.\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
area.x += area.width;
|
||||
if (area.right() > right)
|
||||
{
|
||||
area.width = right - area.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CWRVectorRenderer::drawPathArea(const uint8_t* cmds, uint32_t nCmds, const float* points, uint32_t nPoints, const Rect& area) const
|
||||
{
|
||||
assert(cmds);
|
||||
assert(points);
|
||||
|
||||
if (drawMode != STROKE)
|
||||
{
|
||||
return drawFill(cmds, nCmds, points, nPoints, area);
|
||||
}
|
||||
return drawStroke(cmds, nCmds, points, nPoints, area);
|
||||
}
|
||||
|
||||
bool CWRVectorRenderer::drawFill(const uint8_t* cmds, uint32_t nCmds, const float* points, uint32_t nPoints, const Rect& area) const
|
||||
{
|
||||
(void)nPoints;
|
||||
uint32_t cmdInx = 0;
|
||||
uint32_t pointInx = 0;
|
||||
|
||||
Canvas canvas(canvasPainter, canvasAreaAbsolute, area, 255U);
|
||||
canvas.setFillingRule((drawMode == FILL_EVEN_ODD) ? Rasterizer::FILL_EVEN_ODD : Rasterizer::FILL_NON_ZERO);
|
||||
|
||||
float positionX = 0.0f;
|
||||
float positionY = 0.0f;
|
||||
Matrix3x3::Point transformedPosition = matrix.affineTransform(positionX, positionY);
|
||||
while (cmdInx < nCmds)
|
||||
{
|
||||
switch (static_cast<VectorPrimitives>(cmds[cmdInx]))
|
||||
{
|
||||
case VECTOR_PRIM_CLOSE:
|
||||
assert(pointInx <= nPoints);
|
||||
canvas.close();
|
||||
break;
|
||||
case VECTOR_PRIM_MOVE:
|
||||
assert(pointInx + 2 <= nPoints);
|
||||
positionX = points[pointInx];
|
||||
positionY = points[pointInx + 1];
|
||||
transformedPosition = matrix.affineTransform(positionX, positionY);
|
||||
canvas.moveTo(transformedPosition.x, transformedPosition.y);
|
||||
pointInx += 2;
|
||||
break;
|
||||
case VECTOR_PRIM_LINE:
|
||||
assert(pointInx + 1 <= nPoints);
|
||||
positionX = points[pointInx];
|
||||
positionY = points[pointInx + 1];
|
||||
transformedPosition = matrix.affineTransform(positionX, positionY);
|
||||
canvas.lineTo(transformedPosition.x, transformedPosition.y);
|
||||
pointInx += 2;
|
||||
break;
|
||||
case VECTOR_PRIM_HLINE:
|
||||
assert(pointInx + 1 <= nPoints);
|
||||
positionX = points[pointInx];
|
||||
transformedPosition = matrix.affineTransform(positionX, positionY);
|
||||
canvas.lineTo(transformedPosition.x, transformedPosition.y);
|
||||
pointInx += 1;
|
||||
break;
|
||||
case VECTOR_PRIM_VLINE:
|
||||
assert(pointInx + 1 <= nPoints);
|
||||
positionY = points[pointInx];
|
||||
transformedPosition = matrix.affineTransform(positionX, positionY);
|
||||
canvas.lineTo(transformedPosition.x, transformedPosition.y);
|
||||
pointInx += 1;
|
||||
break;
|
||||
case VECTOR_PRIM_BEZIER_QUAD:
|
||||
assert(pointInx + 4 <= nPoints);
|
||||
positionX = points[pointInx + 2];
|
||||
positionY = points[pointInx + 3];
|
||||
{
|
||||
const Matrix3x3::Point p1 = matrix.affineTransform(points[pointInx], points[pointInx + 1]);
|
||||
const Matrix3x3::Point p2 = matrix.affineTransform(positionX, positionY);
|
||||
canvas.quadraticBezierTo(transformedPosition.x, transformedPosition.y, p1.x, p1.y, p2.x, p2.y);
|
||||
transformedPosition = p2;
|
||||
}
|
||||
pointInx += 4;
|
||||
break;
|
||||
case VECTOR_PRIM_BEZIER_CUBIC:
|
||||
assert(pointInx + 6 <= nPoints);
|
||||
positionX = points[pointInx + 4];
|
||||
positionY = points[pointInx + 5];
|
||||
{
|
||||
const Matrix3x3::Point p1 = matrix.affineTransform(points[pointInx], points[pointInx + 1]);
|
||||
const Matrix3x3::Point p2 = matrix.affineTransform(points[pointInx + 2], points[pointInx + 3]);
|
||||
const Matrix3x3::Point p3 = matrix.affineTransform(positionX, positionY);
|
||||
canvas.cubicBezierTo(transformedPosition.x, transformedPosition.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||
transformedPosition = p3;
|
||||
}
|
||||
pointInx += 6;
|
||||
break;
|
||||
}
|
||||
cmdInx++;
|
||||
}
|
||||
|
||||
return canvas.render(LCD::div255(colorAlpha * alpha));
|
||||
}
|
||||
|
||||
bool CWRVectorRenderer::drawStroke(const uint8_t* cmds, uint32_t nCmds, const float* points, uint32_t nPoints, const Rect& area) const
|
||||
{
|
||||
(void)nPoints;
|
||||
uint32_t cmdInx = 0;
|
||||
uint32_t pointInx = 0;
|
||||
|
||||
if (strokeWidth <= 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
StrokeCanvas canvas(canvasPainter, canvasAreaAbsolute, area, 255U, matrix);
|
||||
canvas.setStroke(strokeWidth, strokeMiterLimit, strokeLineJoin, strokeLineCap, LCD::div255(colorAlpha * alpha));
|
||||
|
||||
float positionX = 0.0f;
|
||||
float positionY = 0.0f;
|
||||
float initialShapeX = 0.0f;
|
||||
float initialShapeY = 0.0f;
|
||||
uint32_t cmdInxPathStart = 0;
|
||||
while (cmdInx < nCmds)
|
||||
{
|
||||
assert(static_cast<VectorPrimitives>(cmds[cmdInx]) == VECTOR_PRIM_CLOSE || pointInx < nPoints);
|
||||
switch (static_cast<VectorPrimitives>(cmds[cmdInx]))
|
||||
{
|
||||
case VECTOR_PRIM_CLOSE:
|
||||
positionX = initialShapeX;
|
||||
positionY = initialShapeY;
|
||||
if (cmdInxPathStart < cmdInx) // A single "CLOSE" on it's own does nothing
|
||||
{
|
||||
// In case we are not back at the beginning, draw a line to that point
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
// Check if it a zero-length closed line and draw a double linecap
|
||||
if (!canvas.strokeDrawDoubleLineCap())
|
||||
{
|
||||
// The line has some extend - close the outside
|
||||
canvas.strokeClose(positionX, positionY);
|
||||
// Move back to the start
|
||||
canvas.strokeMoveTo(positionX, positionY);
|
||||
// Draw inside backwards without linecaps(false)
|
||||
drawStrokeBackwards(cmdInxPathStart, cmdInx + 1, pointInx, cmds, points, false, canvas);
|
||||
// Close the inside of the stroke
|
||||
canvas.strokeClose(positionX, positionY);
|
||||
}
|
||||
}
|
||||
// Next path starts after the CLOSE command
|
||||
cmdInxPathStart = cmdInx + 1;
|
||||
break;
|
||||
case VECTOR_PRIM_MOVE:
|
||||
assert(pointInx + 2 <= nPoints);
|
||||
if (cmdInxPathStart < cmdInx) // More than a singe "MOVE"
|
||||
{
|
||||
// Check if it a zero-length line and draw a double linecap
|
||||
if (!canvas.strokeDrawDoubleLineCap())
|
||||
{
|
||||
// The line has some extend, follow the commands backwards and draw linecaps(true)
|
||||
drawStrokeBackwards(cmdInxPathStart, cmdInx, pointInx, cmds, points, true, canvas);
|
||||
}
|
||||
}
|
||||
cmdInxPathStart = cmdInx;
|
||||
initialShapeX = positionX = points[pointInx];
|
||||
initialShapeY = positionY = points[pointInx + 1];
|
||||
canvas.strokeMoveTo(positionX, positionY);
|
||||
pointInx += 2;
|
||||
break;
|
||||
case VECTOR_PRIM_LINE:
|
||||
assert(pointInx + 2 <= nPoints);
|
||||
positionX = points[pointInx];
|
||||
positionY = points[pointInx + 1];
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
pointInx += 2;
|
||||
break;
|
||||
case VECTOR_PRIM_HLINE:
|
||||
assert(pointInx + 1 <= nPoints);
|
||||
positionX = points[pointInx];
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
pointInx += 1;
|
||||
break;
|
||||
case VECTOR_PRIM_VLINE:
|
||||
assert(pointInx + 1 <= nPoints);
|
||||
positionY = points[pointInx];
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
pointInx += 1;
|
||||
break;
|
||||
case VECTOR_PRIM_BEZIER_QUAD:
|
||||
assert(pointInx + 4 <= nPoints);
|
||||
canvas.strokeBezierQuad(positionX, positionY, points[pointInx], points[pointInx + 1], points[pointInx + 2], points[pointInx + 3]);
|
||||
positionX = points[pointInx + 2];
|
||||
positionY = points[pointInx + 3];
|
||||
pointInx += 4;
|
||||
break;
|
||||
case VECTOR_PRIM_BEZIER_CUBIC:
|
||||
assert(pointInx + 6 <= nPoints);
|
||||
canvas.strokeBezierCubic(positionX, positionY, points[pointInx], points[pointInx + 1], points[pointInx + 2], points[pointInx + 3], points[pointInx + 4], points[pointInx + 5]);
|
||||
positionX = points[pointInx + 4];
|
||||
positionY = points[pointInx + 5];
|
||||
pointInx += 6;
|
||||
break;
|
||||
}
|
||||
cmdInx++;
|
||||
}
|
||||
if (cmdInxPathStart < cmdInx) // More than a singe "MOVE"
|
||||
{
|
||||
// Check if it a zero-length line and draw a double linecap
|
||||
if (!canvas.strokeDrawDoubleLineCap())
|
||||
{
|
||||
// The line has some extend, follow the commands backwards and draw linecaps(true)
|
||||
drawStrokeBackwards(cmdInxPathStart, cmdInx, pointInx, cmds, points, true, canvas);
|
||||
}
|
||||
}
|
||||
return canvas.strokeRender();
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::drawStrokeBackwards(uint32_t cmdInxPathStart, uint32_t cmdInx, uint32_t pointInx, const uint8_t* cmds, const float* points, bool drawLineCaps, StrokeCanvas& canvas) const
|
||||
{
|
||||
float positionX = 0.0f;
|
||||
float positionY = 0.0f;
|
||||
canvas.lineCapInsteadOfLineJoin(drawLineCaps);
|
||||
while (cmdInx > cmdInxPathStart)
|
||||
{
|
||||
assert(cmdInx > 0);
|
||||
cmdInx--;
|
||||
switch (static_cast<VectorPrimitives>(cmds[cmdInx]))
|
||||
{
|
||||
case VECTOR_PRIM_CLOSE:
|
||||
getPreviousDestination(positionX, positionY, cmdInx, pointInx, cmds, points);
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
break;
|
||||
case VECTOR_PRIM_MOVE:
|
||||
assert(cmdInx == cmdInxPathStart);
|
||||
break;
|
||||
case VECTOR_PRIM_LINE:
|
||||
assert(pointInx >= 2);
|
||||
pointInx -= 2;
|
||||
getPreviousDestination(positionX, positionY, cmdInx, pointInx, cmds, points);
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
break;
|
||||
case VECTOR_PRIM_HLINE:
|
||||
case VECTOR_PRIM_VLINE:
|
||||
assert(pointInx >= 1);
|
||||
pointInx -= 1;
|
||||
getPreviousDestination(positionX, positionY, cmdInx, pointInx, cmds, points);
|
||||
canvas.strokeLineTo(positionX, positionY);
|
||||
break;
|
||||
case VECTOR_PRIM_BEZIER_QUAD:
|
||||
assert(pointInx >= 4);
|
||||
pointInx -= 4;
|
||||
getPreviousDestination(positionX, positionY, cmdInx, pointInx, cmds, points);
|
||||
canvas.strokeBezierQuad(points[pointInx + 2], points[pointInx + 3], //lint !e662 !e661
|
||||
points[pointInx], points[pointInx + 1], //lint !e662 !e661
|
||||
positionX, positionY);
|
||||
break;
|
||||
case VECTOR_PRIM_BEZIER_CUBIC:
|
||||
assert(pointInx >= 6);
|
||||
pointInx -= 6;
|
||||
getPreviousDestination(positionX, positionY, cmdInx, pointInx, cmds, points);
|
||||
canvas.strokeBezierCubic(points[pointInx + 4], points[pointInx + 5], //lint !e662 !e661
|
||||
points[pointInx + 2], points[pointInx + 3], //lint !e662 !e661
|
||||
points[pointInx], points[pointInx + 1], //lint !e662 !e661
|
||||
positionX, positionY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (drawLineCaps)
|
||||
{
|
||||
canvas.drawFinalLineCap();
|
||||
}
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::getPreviousDestination(float& positionX, float& positionY, uint32_t cmdInx, uint32_t pointInx, const uint8_t* cmds, const float* points) const
|
||||
{
|
||||
bool foundX = false;
|
||||
bool foundY = false;
|
||||
while (cmdInx > 0)
|
||||
{
|
||||
cmdInx--;
|
||||
switch (static_cast<VectorPrimitives>(cmds[cmdInx]))
|
||||
{
|
||||
case VECTOR_PRIM_CLOSE:
|
||||
break;
|
||||
case VECTOR_PRIM_MOVE:
|
||||
case VECTOR_PRIM_LINE:
|
||||
case VECTOR_PRIM_BEZIER_QUAD:
|
||||
case VECTOR_PRIM_BEZIER_CUBIC:
|
||||
assert(pointInx >= 2);
|
||||
pointInx -= 2;
|
||||
if (!foundX)
|
||||
{
|
||||
positionX = points[pointInx]; //lint !e662 !e661
|
||||
}
|
||||
if (!foundY)
|
||||
{
|
||||
positionY = points[pointInx + 1]; //lint !e662 !e661
|
||||
}
|
||||
return;
|
||||
case VECTOR_PRIM_HLINE:
|
||||
assert(pointInx >= 1);
|
||||
pointInx -= 1;
|
||||
if (!foundX)
|
||||
{
|
||||
positionX = points[pointInx]; //lint !e662 !e661
|
||||
}
|
||||
if (foundY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foundX = true;
|
||||
break;
|
||||
case VECTOR_PRIM_VLINE:
|
||||
assert(pointInx >= 1);
|
||||
pointInx -= 1;
|
||||
if (!foundY)
|
||||
{
|
||||
positionY = points[pointInx]; //lint !e662 !e661
|
||||
}
|
||||
if (foundX)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foundY = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundX)
|
||||
{
|
||||
positionX = 0.0f;
|
||||
}
|
||||
if (!foundY)
|
||||
{
|
||||
positionY = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::setStrokeWidth(float w)
|
||||
{
|
||||
strokeWidth = w;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::setColor(colortype c)
|
||||
{
|
||||
// Save color alpha for rendering
|
||||
colorAlpha = c >> 24;
|
||||
|
||||
getColorPainterColor().setColor(c);
|
||||
canvasPainter = &getColorPainter();
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::setAlpha(uint8_t a)
|
||||
{
|
||||
alpha = a;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::setLinearGradient(float x0, float y0, float x1, float y1,
|
||||
uint32_t stops,
|
||||
const float* /* stopPositions */,
|
||||
const uint32_t* stopColors,
|
||||
float width,
|
||||
float height,
|
||||
bool isSolid,
|
||||
const uint32_t* palette)
|
||||
{
|
||||
// If 'x1' = 'x2' and 'y1' = 'y2', then the area to be painted shall be painted
|
||||
// as a single color using the color and opacity of the last gradient stop.
|
||||
if ((x0 - x1) == 0.0f && (y0 - y1) == 0.0f)
|
||||
{
|
||||
setColor(stopColors[stops - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make colorAlpha neutral
|
||||
colorAlpha = 255;
|
||||
|
||||
AbstractPainterLinearGradient& linearPainter = getLinearPainter();
|
||||
linearPainter.setWidgetWidth(canvasAreaAbsolute.width);
|
||||
linearPainter.setGradientEndPoints(x0, y0, x1, y1, width, height, matrix);
|
||||
assert(palette && "A gradient palette is required by CWRVectorRenderer");
|
||||
linearPainter.setGradientTexture(palette, isSolid);
|
||||
canvasPainter = &linearPainter;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::setTransformationMatrix(const Matrix3x3& m)
|
||||
{
|
||||
matrix = m;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::strokeMoveTo(float x, float y)
|
||||
{
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
firstLineTo = true;
|
||||
noLineHasBeenDrawn = true;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::strokeLineTo(float x, float y)
|
||||
{
|
||||
const float dx = x - lastX;
|
||||
const float dy = y - lastY;
|
||||
float len = sqrtf(dx * dx + dy * dy);
|
||||
if (len > 0.0f)
|
||||
{
|
||||
noLineHasBeenDrawn = false;
|
||||
len /= strokeWidthHalf;
|
||||
const float thisStrokeStartX = lastX + dy / len;
|
||||
const float thisStrokeStartY = lastY - dx / len;
|
||||
const float thisStrokeEndX = x + dy / len;
|
||||
const float thisStrokeEndY = y - dx / len;
|
||||
if (firstLineTo)
|
||||
{
|
||||
initialStrokeStartX = thisStrokeStartX;
|
||||
initialStrokeStartY = thisStrokeStartY;
|
||||
initialStrokeEndX = thisStrokeEndX;
|
||||
initialStrokeEndY = thisStrokeEndY;
|
||||
moveToWithTransform(thisStrokeStartX, thisStrokeStartY);
|
||||
firstLineTo = false;
|
||||
}
|
||||
else if (drawLineCapInsteadOfLineJoin)
|
||||
{
|
||||
strokeDrawLineCap(lastStrokeEndX, lastStrokeEndY, thisStrokeStartX, thisStrokeStartY);
|
||||
drawLineCapInsteadOfLineJoin = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
strokeDrawLineJoin(lastX, lastY, lastStrokeStartX, lastStrokeStartY,
|
||||
lastStrokeEndX, lastStrokeEndY, thisStrokeStartX, thisStrokeStartY, thisStrokeEndX, thisStrokeEndY);
|
||||
}
|
||||
lastStrokeStartX = thisStrokeStartX;
|
||||
lastStrokeStartY = thisStrokeStartY;
|
||||
lastStrokeEndX = thisStrokeEndX;
|
||||
lastStrokeEndY = thisStrokeEndY;
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
}
|
||||
|
||||
bool CWRVectorRenderer::StrokeCanvas::strokeClose(float x, float y)
|
||||
{
|
||||
if (strokeDrawDoubleLineCap())
|
||||
{
|
||||
return false; // Created two line-caps, stop drawing, we are done
|
||||
}
|
||||
strokeDrawLineJoin(x, y, lastStrokeStartX, lastStrokeStartY,
|
||||
lastStrokeEndX, lastStrokeEndY, initialStrokeStartX, initialStrokeStartY, initialStrokeEndX, initialStrokeEndY);
|
||||
return true; // Closed as with a line-join, continue drawing on inside
|
||||
}
|
||||
|
||||
bool CWRVectorRenderer::StrokeCanvas::strokeDrawDoubleLineCap()
|
||||
{
|
||||
if (noLineHasBeenDrawn)
|
||||
{
|
||||
const float y1 = lastY - strokeWidthHalf;
|
||||
const float y2 = lastY + strokeWidthHalf;
|
||||
moveToWithTransform(lastX, y1);
|
||||
strokeDrawLineCap(lastX, y1, lastX, y2);
|
||||
strokeDrawLineCap(lastX, y2, lastX, y1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::lineCapInsteadOfLineJoin(bool drawLineCaps)
|
||||
{
|
||||
drawLineCapInsteadOfLineJoin = drawLineCaps;
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::drawFinalLineCap()
|
||||
{
|
||||
strokeDrawLineCap(lastStrokeEndX, lastStrokeEndY, initialStrokeStartX, initialStrokeStartY);
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::strokeDrawLineJoin(float x, float y, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
|
||||
{
|
||||
if (strokeLineJoin == VG_STROKE_LINEJOIN_ROUND)
|
||||
{
|
||||
lineToWithTransform(x2, y2);
|
||||
// Same formula as used below for MITER
|
||||
if ((x1 - x2) * (y3 - y4) >= (y1 - y2) * (x3 - x4))
|
||||
{
|
||||
strokeDrawLineJoinRound(x, y, x2, y2, x3, y3, 1);
|
||||
}
|
||||
lineToWithTransform(x3, y3);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strokeLineJoin == VG_STROKE_LINEJOIN_MITER)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||
const float divisor = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
||||
// If divisor>0, we are on the "outside" of the angle
|
||||
// If divisor<0, we are on the "inside" of the angle
|
||||
if (divisor != 0.0f)
|
||||
{
|
||||
const float miterX = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / divisor;
|
||||
const float miterY = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / divisor;
|
||||
const float distSquared = (x - miterX) * (x - miterX) + (y - miterY) * (y - miterY);
|
||||
if (distSquared <= miterLimitSquared)
|
||||
{
|
||||
lineToWithTransform(miterX, miterY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VG_STROKE_LINEJOIN_BEVEL (and fallback for VG_STROKE_LINEJOIN_MITER)
|
||||
lineToWithTransform(x2, y2);
|
||||
lineToWithTransform(x3, y3);
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::strokeDrawLineJoinRound(float x, float y, float x2, float y2, float x3, float y3, int level)
|
||||
{
|
||||
assert(level >= 1 && level <= 3);
|
||||
float midX = (x2 + x3) / 2.0f;
|
||||
float midY = (y2 + y3) / 2.0f;
|
||||
const float midLen = sqrtf((x - midX) * (x - midX) + (y - midY) * (y - midY));
|
||||
if (midLen == 0.0f)
|
||||
{
|
||||
const float dx = (x3 - x2) / 2.0f;
|
||||
const float dy = (y3 - y2) / 2.0f;
|
||||
midX = x2 + dx + dy;
|
||||
midY = y2 + dy - dx;
|
||||
}
|
||||
else
|
||||
{
|
||||
midX = x + (midX - x) * strokeWidthHalf / midLen;
|
||||
midY = y + (midY - y) * strokeWidthHalf / midLen;
|
||||
}
|
||||
if (level < 3)
|
||||
{
|
||||
strokeDrawLineJoinRound(x, y, x2, y2, midX, midY, level + 1);
|
||||
lineToWithTransform(midX, midY);
|
||||
strokeDrawLineJoinRound(x, y, midX, midY, x3, y3, level + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
lineToWithTransform(midX, midY);
|
||||
}
|
||||
}
|
||||
|
||||
void CWRVectorRenderer::StrokeCanvas::strokeDrawLineCap(float x2, float y2, float x3, float y3)
|
||||
{
|
||||
if (strokeLineCap == VG_STROKE_LINECAP_BUTT)
|
||||
{
|
||||
lineToWithTransform(x2, y2);
|
||||
lineToWithTransform(x3, y3);
|
||||
return;
|
||||
}
|
||||
const float dx = (x3 - x2) / 2.0f;
|
||||
const float dy = (y3 - y2) / 2.0f;
|
||||
// dx,dy is from (x2,y2) to the midpoint between (x2,y2) and (x3,y3)
|
||||
// +dy,-dx is orthogonal and points in the direction of where the cap should be drawn
|
||||
if (strokeLineCap == VG_STROKE_LINECAP_SQUARE)
|
||||
{
|
||||
lineToWithTransform(x2 + dy, y2 - dx);
|
||||
lineToWithTransform(x3 + dy, y3 - dx);
|
||||
return;
|
||||
}
|
||||
// VG_STROKE_LINECAP_ROUND:
|
||||
// Using 3^2 + 4^2 = 5^2 as well as 7^2 + 24^2 = 25^2
|
||||
lineToWithTransform(x2, y2);
|
||||
lineToWithTransform(x2 + dx * 0.04f + dy * 0.28f, y2 + dy * 0.04f - dx * 0.28f);
|
||||
lineToWithTransform(x2 + dx * 0.2f + dy * 0.6f, y2 + dy * 0.2f - dx * 0.6f);
|
||||
lineToWithTransform(x2 + dx * 0.4f + dy * 0.8f, y2 + dy * 0.4f - dx * 0.8f);
|
||||
lineToWithTransform(x2 + dx * 0.72f + dy * 0.96f, y2 + dy * 0.72f - dx * 0.96f);
|
||||
lineToWithTransform(x2 + dx + dy, y2 + dy - dx);
|
||||
lineToWithTransform(x2 + dx * 1.28f + dy * 0.96f, y2 + dy * 1.28f - dx * 0.96f);
|
||||
lineToWithTransform(x2 + dx * 1.6f + dy * 0.8f, y2 + dy * 1.6f - dx * 0.8f);
|
||||
lineToWithTransform(x2 + dx * 1.8f + dy * 0.6f, y2 + dy * 1.8f - dx * 0.6f);
|
||||
lineToWithTransform(x2 + dx * 1.96f + dy * 0.28f, y2 + dy * 1.96f - dx * 0.28f);
|
||||
lineToWithTransform(x3, y3);
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,515 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <touchgfx/Bitmap.hpp>
|
||||
#include <touchgfx/canvas_widget_renderer/CanvasWidgetRenderer.hpp>
|
||||
#include <touchgfx/canvas_widget_renderer/Rasterizer.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/Canvas.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Canvas::Canvas(const AbstractPainter* const painter, const Rect& canvasAreaAbs, const Rect& invalidatedAreaRel, uint8_t globalAlpha)
|
||||
: canvasPainter(painter),
|
||||
canvasAreaWidth(canvasAreaAbs.width),
|
||||
canvasAlpha(globalAlpha),
|
||||
rasterizer(),
|
||||
invalidatedAreaX(0),
|
||||
invalidatedAreaY(0),
|
||||
invalidatedAreaWidth(0),
|
||||
invalidatedAreaHeight(0),
|
||||
isPenDown(false),
|
||||
wasPenDown(false),
|
||||
previousX(0),
|
||||
previousY(0),
|
||||
previousOutside(0),
|
||||
penDownOutside(0),
|
||||
initialMoveToX(0),
|
||||
initialMoveToY(0)
|
||||
{
|
||||
assert(CanvasWidgetRenderer::hasBuffer() && "No buffer allocated for CanvasWidgetRenderer drawing");
|
||||
assert(Rasterizer::POLY_BASE_SHIFT == 5 && "CanvasWidget assumes Q5 but Rasterizer uses a different setting");
|
||||
|
||||
Rect dirtyArea = invalidatedAreaRel;
|
||||
dirtyAreaAbsolute = Rect(canvasAreaAbs.x + invalidatedAreaRel.x,
|
||||
canvasAreaAbs.y + invalidatedAreaRel.y,
|
||||
invalidatedAreaRel.width,
|
||||
invalidatedAreaRel.height);
|
||||
|
||||
// Transform rects to match framebuffer coordinates
|
||||
// This is needed if the display is rotated compared to the framebuffer
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(dirtyArea, canvasAreaAbs);
|
||||
DisplayTransformation::transformDisplayToFrameBuffer(dirtyAreaAbsolute);
|
||||
|
||||
// Re-size buffers for optimum memory buffer layout.
|
||||
rasterizer.reset(dirtyArea.x, dirtyArea.y);
|
||||
|
||||
invalidatedAreaX = CWRUtil::toQ5<int>(dirtyArea.x);
|
||||
invalidatedAreaY = CWRUtil::toQ5<int>(dirtyArea.y);
|
||||
invalidatedAreaWidth = CWRUtil::toQ5<int>(dirtyArea.width);
|
||||
invalidatedAreaHeight = CWRUtil::toQ5<int>(dirtyArea.height);
|
||||
|
||||
rasterizer.setMaxRender(dirtyAreaAbsolute.width, dirtyAreaAbsolute.height);
|
||||
}
|
||||
|
||||
void Canvas::moveTo(CWRUtil::Q5 x, CWRUtil::Q5 y)
|
||||
{
|
||||
if (isPenDown)
|
||||
{
|
||||
if (!close())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
transformFrameBufferToDisplay(x, y);
|
||||
x -= invalidatedAreaX;
|
||||
y -= invalidatedAreaY;
|
||||
|
||||
const uint8_t outside = isOutside(x, y, invalidatedAreaWidth, invalidatedAreaHeight);
|
||||
|
||||
if (outside)
|
||||
{
|
||||
isPenDown = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
penDownOutside = outside;
|
||||
rasterizer.moveTo(x, y);
|
||||
isPenDown = true;
|
||||
wasPenDown = true;
|
||||
}
|
||||
|
||||
initialMoveToX = x;
|
||||
initialMoveToY = y;
|
||||
|
||||
previousX = x;
|
||||
previousY = y;
|
||||
previousOutside = outside;
|
||||
}
|
||||
|
||||
void Canvas::lineTo(CWRUtil::Q5 x, CWRUtil::Q5 y)
|
||||
{
|
||||
transformFrameBufferToDisplay(x, y);
|
||||
x -= invalidatedAreaX;
|
||||
y -= invalidatedAreaY;
|
||||
|
||||
uint8_t outside = isOutside(x, y, invalidatedAreaWidth, invalidatedAreaHeight);
|
||||
|
||||
if (!previousOutside)
|
||||
{
|
||||
rasterizer.lineTo(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!outside || !(previousOutside & outside))
|
||||
{
|
||||
// x,y is inside, or on another side compared to previous
|
||||
if (!isPenDown)
|
||||
{
|
||||
penDownOutside = previousOutside;
|
||||
rasterizer.moveTo(previousX, previousY);
|
||||
isPenDown = true;
|
||||
wasPenDown = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
rasterizer.lineTo(previousX, previousY);
|
||||
}
|
||||
rasterizer.lineTo(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restrict "outside" to the same side as previous point.
|
||||
outside &= previousOutside;
|
||||
}
|
||||
}
|
||||
previousX = x;
|
||||
previousY = y;
|
||||
previousOutside = outside;
|
||||
}
|
||||
|
||||
bool Canvas::close()
|
||||
{
|
||||
if (isPenDown)
|
||||
{
|
||||
if (previousOutside & penDownOutside)
|
||||
{
|
||||
// We are outside on the same side as we started. No need
|
||||
// to close the path, CWR will do this for us.
|
||||
// lineTo(penDownX, penDownY);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (previousOutside)
|
||||
{
|
||||
rasterizer.lineTo(previousX, previousY);
|
||||
}
|
||||
rasterizer.lineTo(initialMoveToX, initialMoveToY);
|
||||
}
|
||||
}
|
||||
isPenDown = false;
|
||||
return !rasterizer.wasOutlineTooComplex();
|
||||
}
|
||||
|
||||
bool Canvas::render(uint8_t customAlpha)
|
||||
{
|
||||
const uint8_t alpha = LCD::div255(canvasAlpha * customAlpha);
|
||||
if (alpha == 0 || !wasPenDown)
|
||||
{
|
||||
return true; // Nothing. Done
|
||||
}
|
||||
|
||||
// If the invalidated rect is too wide compared to the allocated buffer for CWR,
|
||||
// redrawing will not help. The CanvasWidget needs to know about this situation
|
||||
// and maybe try to divide the area vertically instead, but this has not been
|
||||
// implemented. And probably should not.
|
||||
if (!close())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the rendering buffer
|
||||
uint8_t* RESTRICT framebuffer = reinterpret_cast<uint8_t*>(HAL::getInstance()->lockFrameBufferForRenderingMethod(canvasPainter->getRenderingMethod()));
|
||||
const int stride = HAL::lcd().framebufferStride();
|
||||
uint8_t xAdjust = 0;
|
||||
switch (HAL::lcd().framebufferFormat())
|
||||
{
|
||||
case Bitmap::BW:
|
||||
framebuffer += (dirtyAreaAbsolute.x / 8) + dirtyAreaAbsolute.y * stride;
|
||||
xAdjust = dirtyAreaAbsolute.x % 8;
|
||||
break;
|
||||
case Bitmap::GRAY2:
|
||||
framebuffer += (dirtyAreaAbsolute.x / 4) + dirtyAreaAbsolute.y * stride;
|
||||
xAdjust = dirtyAreaAbsolute.x % 4;
|
||||
break;
|
||||
case Bitmap::GRAY4:
|
||||
framebuffer += (dirtyAreaAbsolute.x / 2) + dirtyAreaAbsolute.y * stride;
|
||||
xAdjust = dirtyAreaAbsolute.x % 2;
|
||||
break;
|
||||
case Bitmap::RGB565:
|
||||
framebuffer += dirtyAreaAbsolute.x * 2 + dirtyAreaAbsolute.y * stride;
|
||||
break;
|
||||
case Bitmap::RGB888:
|
||||
framebuffer += dirtyAreaAbsolute.x * 3 + dirtyAreaAbsolute.y * stride;
|
||||
break;
|
||||
case Bitmap::RGBA2222:
|
||||
case Bitmap::BGRA2222:
|
||||
case Bitmap::ARGB2222:
|
||||
case Bitmap::ABGR2222:
|
||||
case Bitmap::L8:
|
||||
framebuffer += dirtyAreaAbsolute.x + dirtyAreaAbsolute.y * stride;
|
||||
break;
|
||||
case Bitmap::ARGB8888:
|
||||
framebuffer += dirtyAreaAbsolute.x * 4 + dirtyAreaAbsolute.y * stride;
|
||||
break;
|
||||
case Bitmap::BW_RLE:
|
||||
case Bitmap::A4:
|
||||
case Bitmap::CUSTOM:
|
||||
case Bitmap::COMPRESSED_RGB565:
|
||||
case Bitmap::COMPRESSED_RGB888:
|
||||
case Bitmap::COMPRESSED_ARGB8888:
|
||||
assert(false && "Unsupported bit depth");
|
||||
}
|
||||
const bool result = rasterizer.render(canvasPainter, framebuffer, stride, xAdjust, alpha);
|
||||
canvasPainter->tearDown();
|
||||
HAL::getInstance()->unlockFrameBuffer();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Canvas::transformFrameBufferToDisplay(CWRUtil::Q5& x, CWRUtil::Q5& y) const
|
||||
{
|
||||
if (HAL::DISPLAY_ROTATION == rotate90)
|
||||
{
|
||||
CWRUtil::Q5 const tmpY = y;
|
||||
y = CWRUtil::toQ5<int>(canvasAreaWidth) - x;
|
||||
x = tmpY;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: if you change these values, check the commented usage below
|
||||
//#define curve_angle_tolerance_epsilon 0.01f
|
||||
#define curve_collinearity_epsilon 1e-10f
|
||||
#define curve_recursion_limit 8
|
||||
|
||||
#define m_distance_tolerance 0.25f
|
||||
#define m_angle_tolerance 0.1f
|
||||
|
||||
void Canvas::recursiveQuadraticBezier(const float x1, const float y1, const float x2, const float y2, const float x3, const float y3, const unsigned level)
|
||||
{
|
||||
if (level > curve_recursion_limit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate all the mid-points of the line segments
|
||||
//----------------------
|
||||
const float x12 = (x1 + x2) / 2;
|
||||
const float y12 = (y1 + y2) / 2;
|
||||
const float x23 = (x2 + x3) / 2;
|
||||
const float y23 = (y2 + y3) / 2;
|
||||
const float x123 = (x12 + x23) / 2;
|
||||
const float y123 = (y12 + y23) / 2;
|
||||
|
||||
float dx = x3 - x1;
|
||||
float dy = y3 - y1;
|
||||
const float d = abs(((x2 - x3) * dy - (y2 - y3) * dx));
|
||||
|
||||
if (d > curve_collinearity_epsilon)
|
||||
{
|
||||
// Regular care
|
||||
//-----------------
|
||||
if (d * d <= m_distance_tolerance * (dx * dx + dy * dy))
|
||||
{
|
||||
// If the curvature doesn't exceed the distance_tolerance value
|
||||
// we tend to finish subdivisions.
|
||||
//----------------------
|
||||
// FGC: lint does not like this
|
||||
// if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||||
// {
|
||||
// lineTo(x123, y123);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Angle & Cusp Condition
|
||||
//----------------------
|
||||
float da = abs(atan2f(y3 - y2, x3 - x2) - atan2f(y2 - y1, x2 - x1));
|
||||
if (da >= PI)
|
||||
{
|
||||
da = 2 * PI - da;
|
||||
}
|
||||
|
||||
if (da < m_angle_tolerance)
|
||||
{
|
||||
// Finally we can stop the recursion
|
||||
//----------------------
|
||||
lineTo(x123, y123);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collinear case
|
||||
//-----------------
|
||||
dx = x123 - (x1 + x3) / 2;
|
||||
dy = y123 - (y1 + y3) / 2;
|
||||
if (dx * dx + dy * dy <= m_distance_tolerance)
|
||||
{
|
||||
lineTo(x123, y123);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue subdivision
|
||||
//----------------------
|
||||
recursiveQuadraticBezier(x1, y1, x12, y12, x123, y123, level + 1);
|
||||
recursiveQuadraticBezier(x123, y123, x23, y23, x3, y3, level + 1);
|
||||
}
|
||||
|
||||
//#define m_cusp_limit 0.0f
|
||||
|
||||
void Canvas::recursiveCubicBezier(const float x1, const float y1, const float x2, const float y2, const float x3, const float y3, const float x4, const float y4, const unsigned level)
|
||||
{
|
||||
if (level > curve_recursion_limit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate all the mid-points of the line segments
|
||||
//----------------------
|
||||
const float x12 = (x1 + x2) / 2;
|
||||
const float y12 = (y1 + y2) / 2;
|
||||
const float x23 = (x2 + x3) / 2;
|
||||
const float y23 = (y2 + y3) / 2;
|
||||
const float x34 = (x3 + x4) / 2;
|
||||
const float y34 = (y3 + y4) / 2;
|
||||
const float x123 = (x12 + x23) / 2;
|
||||
const float y123 = (y12 + y23) / 2;
|
||||
const float x234 = (x23 + x34) / 2;
|
||||
const float y234 = (y23 + y34) / 2;
|
||||
const float x1234 = (x123 + x234) / 2;
|
||||
const float y1234 = (y123 + y234) / 2;
|
||||
|
||||
if (level > 0) // Enforce subdivision first time
|
||||
{
|
||||
// Try to approximate the full cubic curve by a single straight line
|
||||
//------------------
|
||||
float dx = x4 - x1;
|
||||
float dy = y4 - y1;
|
||||
|
||||
const float d2 = abs(((x2 - x4) * dy - (y2 - y4) * dx));
|
||||
const float d3 = abs(((x3 - x4) * dy - (y3 - y4) * dx));
|
||||
|
||||
float da1, da2;
|
||||
|
||||
if (d2 > curve_collinearity_epsilon && d3 > curve_collinearity_epsilon)
|
||||
{
|
||||
// Regular care
|
||||
//-----------------
|
||||
if ((d2 + d3) * (d2 + d3) <= m_distance_tolerance * (dx * dx + dy * dy))
|
||||
{
|
||||
// If the curvature doesn't exceed the distance_tolerance value
|
||||
// we tend to finish subdivisions.
|
||||
//----------------------
|
||||
|
||||
// FGC: lint does not like this
|
||||
// if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||||
// {
|
||||
// lineTo(x1234, y1234);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Angle & Cusp Condition
|
||||
//----------------------
|
||||
const float a23 = atan2f(y3 - y2, x3 - x2);
|
||||
da1 = abs(a23 - atan2f(y2 - y1, x2 - x1));
|
||||
da2 = abs(atan2f(y4 - y3, x4 - x3) - a23);
|
||||
if (da1 >= PI)
|
||||
{
|
||||
da1 = 2 * PI - da1;
|
||||
}
|
||||
if (da2 >= PI)
|
||||
{
|
||||
da2 = 2 * PI - da2;
|
||||
}
|
||||
|
||||
if (da1 + da2 < m_angle_tolerance)
|
||||
{
|
||||
// Finally we can stop the recursion
|
||||
//----------------------
|
||||
lineTo(x1234, y1234);
|
||||
return;
|
||||
}
|
||||
|
||||
// FGC: lint does not like this
|
||||
// if (m_cusp_limit != 0.0f)
|
||||
// {
|
||||
// if (da1 > m_cusp_limit)
|
||||
// {
|
||||
// lineTo(x2, y2);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (da2 > m_cusp_limit)
|
||||
// {
|
||||
// lineTo(x3, y3);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (d2 > curve_collinearity_epsilon)
|
||||
{
|
||||
// p1,p3,p4 are collinear, p2 is considerable
|
||||
//----------------------
|
||||
if (d2 * d2 <= m_distance_tolerance * (dx * dx + dy * dy))
|
||||
{
|
||||
// FGC: lint does not like this
|
||||
// if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||||
// {
|
||||
// lineTo(x1234, y1234);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Angle Condition
|
||||
//----------------------
|
||||
da1 = abs(atan2f(y3 - y2, x3 - x2) - atan2f(y2 - y1, x2 - x1));
|
||||
if (da1 >= PI)
|
||||
{
|
||||
da1 = 2 * PI - da1;
|
||||
}
|
||||
|
||||
if (da1 < m_angle_tolerance)
|
||||
{
|
||||
lineTo(x2, y2);
|
||||
lineTo(x3, y3);
|
||||
return;
|
||||
}
|
||||
|
||||
// FGC: lint does not like this
|
||||
// if (m_cusp_limit != 0.0f)
|
||||
// {
|
||||
// if (da1 > m_cusp_limit)
|
||||
// {
|
||||
// lineTo(x2, y2);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
else if (d3 > curve_collinearity_epsilon)
|
||||
{
|
||||
// p1,p2,p4 are collinear, p3 is considerable
|
||||
//----------------------
|
||||
if (d3 * d3 <= m_distance_tolerance * (dx * dx + dy * dy))
|
||||
{
|
||||
// FGC: lint does not like this
|
||||
// if (m_angle_tolerance < curve_angle_tolerance_epsilon)
|
||||
// {
|
||||
// lineTo(x1234, y1234);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Angle Condition
|
||||
//----------------------
|
||||
da1 = abs(atan2f(y4 - y3, x4 - x3) - atan2f(y3 - y2, x3 - x2));
|
||||
if (da1 >= PI)
|
||||
{
|
||||
da1 = 2 * PI - da1;
|
||||
}
|
||||
|
||||
if (da1 < m_angle_tolerance)
|
||||
{
|
||||
lineTo(x2, y2);
|
||||
lineTo(x3, y3);
|
||||
return;
|
||||
}
|
||||
|
||||
// FGC: lint does not like this
|
||||
// if (m_cusp_limit != 0.0f)
|
||||
// {
|
||||
// if (da1 > m_cusp_limit)
|
||||
// {
|
||||
// lineTo(x3, y3);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collinear case
|
||||
//-----------------
|
||||
dx = x1234 - (x1 + x4) / 2;
|
||||
dy = y1234 - (y1 + y4) / 2;
|
||||
if (dx * dx + dy * dy <= m_distance_tolerance)
|
||||
{
|
||||
lineTo(x1234, y1234);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue subdivision
|
||||
//----------------------
|
||||
recursiveCubicBezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1);
|
||||
recursiveCubicBezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1);
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/canvas_widget_renderer/CanvasWidgetRenderer.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/widgets/canvas/CanvasWidget.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
CanvasWidget::CanvasWidget()
|
||||
: Widget(),
|
||||
alpha(255),
|
||||
canvasPainter(0)
|
||||
{
|
||||
}
|
||||
|
||||
void CanvasWidget::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
Rect area = invalidatedArea & getMinimalRect();
|
||||
if (!canvasPainter || !canvasPainter->setup(getRect()) || area.isEmpty())
|
||||
{
|
||||
// Painter not ready to paint or nothing to paint => done
|
||||
return;
|
||||
}
|
||||
|
||||
switch (HAL::DISPLAY_ROTATION)
|
||||
{
|
||||
case rotate0:
|
||||
{
|
||||
const int16_t bottom = area.bottom();
|
||||
while (area.y < bottom)
|
||||
{
|
||||
while (!drawCanvasWidget(area))
|
||||
{
|
||||
if (area.height == 1)
|
||||
{
|
||||
// Failed on a single line
|
||||
break;
|
||||
}
|
||||
area.height = (area.height + 1) >> 1; // Cannot become 0 as (2+1)>>1=1
|
||||
#ifdef SIMULATOR
|
||||
if (CanvasWidgetRenderer::getWriteMemoryUsageReport())
|
||||
{
|
||||
if (area.height > 1)
|
||||
{
|
||||
touchgfx_printf("CWR will split draw into multiple draws due to limited memory.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
touchgfx_printf("CWR was unable to complete a draw operation due to limited memory.\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
area.y += area.height;
|
||||
if (area.bottom() > bottom)
|
||||
{
|
||||
area.height = bottom - area.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case rotate90:
|
||||
{
|
||||
const int16_t right = area.right();
|
||||
while (area.x < right)
|
||||
{
|
||||
while (!drawCanvasWidget(area))
|
||||
{
|
||||
if (area.width == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
area.width = (area.width + 1) >> 1;
|
||||
#ifdef SIMULATOR
|
||||
if (CanvasWidgetRenderer::getWriteMemoryUsageReport())
|
||||
{
|
||||
if (area.width > 1)
|
||||
{
|
||||
touchgfx_printf("CWR will split draw into multiple draws due to limited memory.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
touchgfx_printf("CWR was unable to complete a draw operation due to limited memory.\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
area.x += area.width;
|
||||
if (area.right() > right)
|
||||
{
|
||||
area.width = right - area.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasWidget::invalidate() const
|
||||
{
|
||||
Rect minimalRect = getMinimalRect();
|
||||
minimalRect &= Rect(0, 0, getWidth(), getHeight());
|
||||
invalidateRect(minimalRect);
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,510 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/widgets/canvas/Circle.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Circle::Circle()
|
||||
: CanvasWidget(),
|
||||
circleCenterX(0), circleCenterY(0), circleRadius(0),
|
||||
circleArcAngleStart(CWRUtil::toQ5<int>(0)), circleArcAngleEnd(CWRUtil::toQ5<int>(360)),
|
||||
circleLineWidth(0), circleArcIncrement(5),
|
||||
circleCapArcIncrement(180)
|
||||
{
|
||||
Drawable::setWidthHeight(0, 0);
|
||||
}
|
||||
|
||||
void Circle::setPrecision(int precision)
|
||||
{
|
||||
if (precision < 1)
|
||||
{
|
||||
precision = 1;
|
||||
}
|
||||
if (precision > 120)
|
||||
{
|
||||
precision = 120;
|
||||
}
|
||||
circleArcIncrement = precision;
|
||||
}
|
||||
|
||||
int Circle::getPrecision() const
|
||||
{
|
||||
return circleArcIncrement;
|
||||
}
|
||||
|
||||
void Circle::setCapPrecision(int precision)
|
||||
{
|
||||
if (precision < 1)
|
||||
{
|
||||
precision = 1;
|
||||
}
|
||||
if (precision > 180)
|
||||
{
|
||||
precision = 180;
|
||||
}
|
||||
circleCapArcIncrement = precision;
|
||||
}
|
||||
|
||||
int Circle::getCapPrecision() const
|
||||
{
|
||||
return circleCapArcIncrement;
|
||||
}
|
||||
|
||||
bool Circle::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
CWRUtil::Q5 arcStart = circleArcAngleStart;
|
||||
CWRUtil::Q5 arcEnd = circleArcAngleEnd;
|
||||
|
||||
CWRUtil::Q5 const _360 = CWRUtil::toQ5<int>(360);
|
||||
|
||||
// Put start before end by swapping
|
||||
if (arcStart > arcEnd)
|
||||
{
|
||||
CWRUtil::Q5 const tmp = arcStart;
|
||||
arcStart = arcEnd;
|
||||
arcEnd = tmp;
|
||||
}
|
||||
|
||||
if ((arcEnd - arcStart) >= _360)
|
||||
{
|
||||
// The entire circle has to be drawn
|
||||
arcStart = CWRUtil::toQ5<int>(0);
|
||||
arcEnd = _360;
|
||||
}
|
||||
|
||||
const int16_t circleCenterX16 = circleCenterX.to<int16_t>();
|
||||
const int16_t circleCenterY16 = circleCenterY.to<int16_t>();
|
||||
int32_t routside = 0;
|
||||
if (circleLineWidth != 0)
|
||||
{
|
||||
// Check if invalidated area is completely inside the circle
|
||||
const int32_t dx1 = abs((int)circleCenterX16 - invalidatedArea.x); // Find distances between each corner and circle center
|
||||
const int32_t dx2 = abs((int)circleCenterX16 - invalidatedArea.right());
|
||||
const int32_t dy1 = abs((int)circleCenterY16 - invalidatedArea.y);
|
||||
const int32_t dy2 = abs((int)circleCenterY16 - invalidatedArea.bottom());
|
||||
const int32_t dx = MAX(dx1, dx2) + 1; // Largest horz/vert distance (round up)
|
||||
const int32_t dy = MAX(dy1, dy2) + 1;
|
||||
// From https://www.mathopenref.com/polygonincircle.html
|
||||
const int32_t rmin = ((circleRadius - (circleLineWidth / 2)) * CWRUtil::cosine((circleArcIncrement + 1) / 2)).to<int>();
|
||||
// Check if invalidatedArea is completely inside circle
|
||||
if ((dx * dx) + (dy * dy) < rmin * rmin)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
routside = (circleRadius + (circleLineWidth / 2)).to<int>() + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
routside = circleRadius.to<int>() + 1;
|
||||
}
|
||||
// From https://yal.cc/rectangle-circle-intersection-test/
|
||||
const int32_t invalidatedRight = invalidatedArea.right();
|
||||
const int32_t minimumX = MIN(circleCenterX16, invalidatedRight);
|
||||
const int32_t nearestX = circleCenterX16 - MAX(invalidatedArea.x, minimumX);
|
||||
const int32_t invalidatedBottom = invalidatedArea.bottom();
|
||||
const int32_t minimumY = MIN(circleCenterY16, invalidatedBottom);
|
||||
const int32_t nearestY = circleCenterY16 - MAX(invalidatedArea.y, minimumY);
|
||||
// Check if invalidatedArea is completely outside circle
|
||||
if ((nearestX * nearestX) + (nearestY * nearestY) > routside * routside)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidatedArea, getAlpha());
|
||||
|
||||
CWRUtil::Q5 radius = circleRadius;
|
||||
CWRUtil::Q5 lineWidth = circleLineWidth;
|
||||
if (circleLineWidth > circleRadius * 2)
|
||||
{
|
||||
lineWidth = (circleRadius + circleLineWidth / 2);
|
||||
radius = lineWidth / 2;
|
||||
}
|
||||
|
||||
CWRUtil::Q5 arc = arcStart;
|
||||
CWRUtil::Q5 const circleArcIncrementQ5 = CWRUtil::toQ5<int>(circleArcIncrement);
|
||||
moveToAngleRadius2(canvas, arc, (radius * 2) + lineWidth);
|
||||
CWRUtil::Q5 nextArc = CWRUtil::Q5(ROUNDUP((int)(arc + CWRUtil::toQ5<int>(1)), (int)circleArcIncrementQ5));
|
||||
while (nextArc <= arcEnd)
|
||||
{
|
||||
arc = nextArc;
|
||||
lineToAngleRadius2(canvas, arc, (radius * 2) + lineWidth);
|
||||
nextArc += circleArcIncrementQ5;
|
||||
}
|
||||
if (arc < arcEnd)
|
||||
{
|
||||
// "arc" is not updated. It is the last arc in steps of "circleArcIncrement"
|
||||
lineToAngleRadius2(canvas, arcEnd, (radius * 2) + lineWidth);
|
||||
}
|
||||
|
||||
if (lineWidth == CWRUtil::toQ5<int>(0))
|
||||
{
|
||||
// Draw a filled circle / pie / pacman
|
||||
if (arcEnd - arcStart < _360)
|
||||
{
|
||||
// Not a complete circle, line to center
|
||||
canvas.lineTo(circleCenterX, circleCenterY);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (canvas.wasOutlineTooComplex())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CWRUtil::Q5 const circleCapArcIncrementQ5 = CWRUtil::toQ5<int>(circleCapArcIncrement);
|
||||
CWRUtil::Q5 const _180 = CWRUtil::toQ5<int>(180);
|
||||
if (arcEnd - arcStart < _360)
|
||||
{
|
||||
// Draw the circle cap
|
||||
CWRUtil::Q5 const capX = circleCenterX + (radius * CWRUtil::sine(arcEnd));
|
||||
CWRUtil::Q5 const capY = circleCenterY - (radius * CWRUtil::cosine(arcEnd));
|
||||
for (CWRUtil::Q5 capAngle = arcEnd + circleCapArcIncrementQ5; capAngle < arcEnd + _180; capAngle += circleCapArcIncrementQ5)
|
||||
{
|
||||
lineToXYAngleRadius2(canvas, capX, capY, capAngle, lineWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// Not a filled circle, draw the path on the inside of the circle
|
||||
if (arc < arcEnd)
|
||||
{
|
||||
lineToAngleRadius2(canvas, arcEnd, (radius * 2) - lineWidth);
|
||||
}
|
||||
|
||||
nextArc = arc;
|
||||
while (nextArc >= arcStart)
|
||||
{
|
||||
arc = nextArc;
|
||||
lineToAngleRadius2(canvas, arc, (radius * 2) - lineWidth);
|
||||
nextArc -= circleArcIncrementQ5;
|
||||
}
|
||||
|
||||
if (arc > arcStart)
|
||||
{
|
||||
lineToAngleRadius2(canvas, arcStart, (radius * 2) - lineWidth);
|
||||
}
|
||||
|
||||
if (arcEnd - arcStart < _360)
|
||||
{
|
||||
// Draw the circle cap
|
||||
CWRUtil::Q5 const capX = circleCenterX + (radius * CWRUtil::sine(arcStart));
|
||||
CWRUtil::Q5 const capY = circleCenterY - (radius * CWRUtil::cosine(arcStart));
|
||||
for (CWRUtil::Q5 capAngle = (arcStart - _180) + circleCapArcIncrementQ5; capAngle < arcStart; capAngle += circleCapArcIncrementQ5)
|
||||
{
|
||||
lineToXYAngleRadius2(canvas, capX, capY, capAngle, lineWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return canvas.render();
|
||||
}
|
||||
|
||||
Rect Circle::getMinimalRect(CWRUtil::Q5 arcStart, CWRUtil::Q5 arcEnd) const
|
||||
{
|
||||
CWRUtil::Q5 xMin = CWRUtil::toQ5<int>(getWidth());
|
||||
CWRUtil::Q5 xMax = CWRUtil::toQ5<int>(0);
|
||||
CWRUtil::Q5 yMin = CWRUtil::toQ5<int>(getHeight());
|
||||
CWRUtil::Q5 yMax = CWRUtil::toQ5<int>(0);
|
||||
calculateMinimalRect(arcStart, arcEnd, xMin, xMax, yMin, yMax);
|
||||
return Rect(xMin.to<int>() - 1, yMin.to<int>() - 1,
|
||||
(xMax.to<int>() - xMin.to<int>()) + 2, (yMax.to<int>() - yMin.to<int>()) + 2);
|
||||
}
|
||||
|
||||
void Circle::updateArc(CWRUtil::Q5 setStartAngleQ5, CWRUtil::Q5 setEndAngleQ5)
|
||||
{
|
||||
CWRUtil::Q5 startAngleQ5 = setStartAngleQ5;
|
||||
CWRUtil::Q5 endAngleQ5 = setEndAngleQ5;
|
||||
if (circleArcAngleStart == startAngleQ5 && circleArcAngleEnd == endAngleQ5)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure old start < end
|
||||
if (circleArcAngleStart > circleArcAngleEnd)
|
||||
{
|
||||
CWRUtil::Q5 const tmp = circleArcAngleStart;
|
||||
circleArcAngleStart = circleArcAngleEnd;
|
||||
circleArcAngleEnd = tmp;
|
||||
}
|
||||
// Make sure new start < end
|
||||
if (startAngleQ5 > endAngleQ5)
|
||||
{
|
||||
CWRUtil::Q5 const tmp = startAngleQ5;
|
||||
startAngleQ5 = endAngleQ5;
|
||||
endAngleQ5 = tmp;
|
||||
}
|
||||
|
||||
// Nice constant
|
||||
const CWRUtil::Q5 _360 = CWRUtil::toQ5<int>(360);
|
||||
|
||||
// Get old circle range start in [0..360[
|
||||
if (circleArcAngleStart >= _360)
|
||||
{
|
||||
const int x = (circleArcAngleStart / _360).to<int>();
|
||||
circleArcAngleStart -= _360 * x;
|
||||
circleArcAngleEnd -= _360 * x;
|
||||
}
|
||||
else if (circleArcAngleStart < 0)
|
||||
{
|
||||
const int x = 1 + ((-circleArcAngleStart) / _360).to<int>();
|
||||
circleArcAngleStart += _360 * x;
|
||||
circleArcAngleEnd += _360 * x;
|
||||
}
|
||||
// Detect full circle
|
||||
if ((circleArcAngleEnd - circleArcAngleStart) > _360)
|
||||
{
|
||||
circleArcAngleEnd = circleArcAngleStart + _360;
|
||||
}
|
||||
|
||||
// Get new circle range start in [0..360[
|
||||
if (startAngleQ5 >= _360)
|
||||
{
|
||||
const int x = (startAngleQ5 / _360).to<int>();
|
||||
startAngleQ5 -= _360 * x;
|
||||
endAngleQ5 -= _360 * x;
|
||||
}
|
||||
else if (startAngleQ5 < 0)
|
||||
{
|
||||
const int x = 1 + (-startAngleQ5 / _360).to<int>();
|
||||
startAngleQ5 += _360 * x;
|
||||
endAngleQ5 += _360 * x;
|
||||
}
|
||||
// Detect full circle
|
||||
if ((endAngleQ5 - startAngleQ5) >= _360)
|
||||
{
|
||||
// Align full new circle with old start.
|
||||
// So old[90..270] -> new[0..360] becomes new[90..450] for smaller invalidated area
|
||||
startAngleQ5 = circleArcAngleStart;
|
||||
endAngleQ5 = startAngleQ5 + _360;
|
||||
}
|
||||
else if ((circleArcAngleEnd - circleArcAngleStart) >= _360)
|
||||
{
|
||||
// New circle is not full, but old is. Align old circle with new.
|
||||
// So old[0..360] -> new[90..270] becomes old[90..450] for smaller invalidated area
|
||||
circleArcAngleStart = startAngleQ5;
|
||||
circleArcAngleEnd = circleArcAngleStart + _360;
|
||||
}
|
||||
|
||||
// New start is after old end. Could be overlap
|
||||
// if old[10..30]->new[350..380] becomes new[-10..20]
|
||||
if (startAngleQ5 > circleArcAngleEnd && endAngleQ5 - _360 >= circleArcAngleStart)
|
||||
{
|
||||
startAngleQ5 -= _360;
|
||||
endAngleQ5 -= _360;
|
||||
}
|
||||
// Same as above but for old instead of new
|
||||
if (circleArcAngleStart > endAngleQ5 && circleArcAngleEnd - _360 >= startAngleQ5)
|
||||
{
|
||||
circleArcAngleStart -= _360;
|
||||
circleArcAngleEnd -= _360;
|
||||
}
|
||||
|
||||
Rect r;
|
||||
if (startAngleQ5 > circleArcAngleEnd || endAngleQ5 < circleArcAngleStart)
|
||||
{
|
||||
// Arcs do not overlap. Invalidate both arcs.
|
||||
r = getMinimalRect(circleArcAngleStart, circleArcAngleEnd);
|
||||
invalidateRect(r);
|
||||
|
||||
r = getMinimalRect(startAngleQ5, endAngleQ5);
|
||||
invalidateRect(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arcs overlap. Invalidate both ends.
|
||||
if (circleArcAngleStart != startAngleQ5)
|
||||
{
|
||||
r = getMinimalRectForUpdatedStartAngle(startAngleQ5);
|
||||
invalidateRect(r);
|
||||
}
|
||||
if (circleArcAngleEnd != endAngleQ5)
|
||||
{
|
||||
r = getMinimalRectForUpdatedEndAngle(endAngleQ5);
|
||||
invalidateRect(r);
|
||||
}
|
||||
}
|
||||
|
||||
circleArcAngleStart = setStartAngleQ5;
|
||||
circleArcAngleEnd = setEndAngleQ5;
|
||||
}
|
||||
|
||||
void Circle::calculateMinimalRect(CWRUtil::Q5 arcStart, CWRUtil::Q5 arcEnd, CWRUtil::Q5& xMin, CWRUtil::Q5& xMax, CWRUtil::Q5& yMin, CWRUtil::Q5& yMax) const
|
||||
{
|
||||
// Put start before end by swapping
|
||||
if (arcStart > arcEnd)
|
||||
{
|
||||
CWRUtil::Q5 const tmp = arcStart;
|
||||
arcStart = arcEnd;
|
||||
arcEnd = tmp;
|
||||
}
|
||||
|
||||
CWRUtil::Q5 const _90 = CWRUtil::toQ5<int>(90);
|
||||
CWRUtil::Q5 const _360 = CWRUtil::toQ5<int>(360);
|
||||
|
||||
if ((arcEnd - arcStart) >= _360)
|
||||
{
|
||||
// The entire circle has to be drawn
|
||||
arcStart = CWRUtil::toQ5<int>(0);
|
||||
arcEnd = _360;
|
||||
}
|
||||
|
||||
if (circleLineWidth == 0)
|
||||
{
|
||||
// Check start angle
|
||||
updateMinMaxAngleSolid(arcStart, xMin, xMax, yMin, yMax);
|
||||
// Here we have a up to 4 approximation steps on angles divisible by 90
|
||||
CWRUtil::Q5 i;
|
||||
for (i = CWRUtil::Q5(ROUNDUP((int)(arcStart + (CWRUtil::Q5)1), (int)_90)); i <= arcEnd; i += _90)
|
||||
{
|
||||
updateMinMaxAngleSolid(i, xMin, xMax, yMin, yMax);
|
||||
}
|
||||
// Check end angle
|
||||
if ((i - _90) < arcEnd)
|
||||
{
|
||||
updateMinMaxAngleSolid(arcEnd, xMin, xMax, yMin, yMax);
|
||||
}
|
||||
updateMinMaxX(circleCenterX, xMin, xMax);
|
||||
updateMinMaxY(circleCenterY, yMin, yMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
const CWRUtil::Q5 halfLineWidth = (circleLineWidth + (CWRUtil::Q5)1) / 2;
|
||||
// Check start angle
|
||||
updateMinMaxAngleLine(arcStart, halfLineWidth, xMin, xMax, yMin, yMax);
|
||||
// Here we have a up to 4 approximation steps on angles divisible by 90
|
||||
CWRUtil::Q5 i;
|
||||
for (i = CWRUtil::Q5(ROUNDUP((int)(arcStart + (CWRUtil::Q5)1), (int)_90)); i <= arcEnd; i += _90)
|
||||
{
|
||||
updateMinMaxAngleLine(i, halfLineWidth, xMin, xMax, yMin, yMax);
|
||||
}
|
||||
// Check end angle
|
||||
if ((i - _90) < arcEnd)
|
||||
{
|
||||
updateMinMaxAngleLine(arcEnd, halfLineWidth, xMin, xMax, yMin, yMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect Circle::getMinimalRectForUpdatedStartAngle(const CWRUtil::Q5& startAngleQ5) const
|
||||
{
|
||||
CWRUtil::Q5 minAngle = CWRUtil::Q5(0); // Unused default value
|
||||
CWRUtil::Q5 maxAngle = CWRUtil::Q5(0); // Unused default value
|
||||
const int circleArcIncrementQ5int = (int)CWRUtil::toQ5<int>(circleArcIncrement);
|
||||
if (circleArcAngleStart < circleArcAngleEnd)
|
||||
{
|
||||
// start is smaller than end
|
||||
if (startAngleQ5 < circleArcAngleStart)
|
||||
{
|
||||
// start moved even lower
|
||||
minAngle = startAngleQ5;
|
||||
maxAngle = CWRUtil::Q5(ROUNDUP((int)circleArcAngleStart, circleArcIncrementQ5int));
|
||||
maxAngle = MIN(maxAngle, circleArcAngleEnd); // No need to go higher than end
|
||||
}
|
||||
else if (startAngleQ5 < circleArcAngleEnd)
|
||||
{
|
||||
// start moved higher, but not higher than end
|
||||
minAngle = circleArcAngleStart;
|
||||
maxAngle = CWRUtil::Q5(ROUNDUP((int)startAngleQ5, circleArcIncrementQ5int));
|
||||
maxAngle = MIN(maxAngle, circleArcAngleEnd); // No need to go higher than end
|
||||
}
|
||||
else
|
||||
{
|
||||
// start moved past end
|
||||
minAngle = circleArcAngleStart;
|
||||
maxAngle = startAngleQ5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// start is higher than end
|
||||
if (startAngleQ5 > circleArcAngleStart)
|
||||
{
|
||||
// start moved even higher
|
||||
minAngle = CWRUtil::Q5(ROUNDDOWN((int)circleArcAngleStart, circleArcIncrementQ5int));
|
||||
minAngle = MAX(minAngle, circleArcAngleEnd); // No need to go lower then end
|
||||
maxAngle = startAngleQ5;
|
||||
}
|
||||
else if (startAngleQ5 > circleArcAngleEnd)
|
||||
{
|
||||
// start moved lower, but not lower than end
|
||||
minAngle = CWRUtil::Q5(ROUNDDOWN((int)startAngleQ5, circleArcIncrementQ5int));
|
||||
minAngle = MAX(minAngle, circleArcAngleEnd); // No need to go lower than end
|
||||
maxAngle = circleArcAngleStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
// start moved lower past end
|
||||
minAngle = startAngleQ5;
|
||||
maxAngle = circleArcAngleStart;
|
||||
}
|
||||
}
|
||||
return getMinimalRect(minAngle, maxAngle);
|
||||
}
|
||||
|
||||
Rect Circle::getMinimalRectForUpdatedEndAngle(const CWRUtil::Q5& endAngleQ5) const
|
||||
{
|
||||
CWRUtil::Q5 minAngle = CWRUtil::Q5(0); // Unused default value
|
||||
CWRUtil::Q5 maxAngle = CWRUtil::Q5(0); // Unused default value
|
||||
const int circleArcIncrementQ5int = (int)CWRUtil::toQ5<int>(circleArcIncrement);
|
||||
if (circleArcAngleStart < circleArcAngleEnd)
|
||||
{
|
||||
// start is smaller than end
|
||||
if (endAngleQ5 > circleArcAngleEnd)
|
||||
{
|
||||
// end moved even higher
|
||||
minAngle = CWRUtil::Q5(ROUNDDOWN((int)circleArcAngleEnd, circleArcIncrementQ5int));
|
||||
minAngle = MAX(minAngle, circleArcAngleStart);
|
||||
maxAngle = endAngleQ5;
|
||||
}
|
||||
else if (endAngleQ5 > circleArcAngleStart)
|
||||
{
|
||||
// end moved lower, but not past start
|
||||
minAngle = CWRUtil::Q5(ROUNDDOWN((int)endAngleQ5, circleArcIncrementQ5int));
|
||||
minAngle = MAX(minAngle, circleArcAngleStart); // No need to go lower than start
|
||||
maxAngle = circleArcAngleEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
// end move past start
|
||||
minAngle = endAngleQ5;
|
||||
maxAngle = circleArcAngleEnd;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// start is higher than end
|
||||
if (endAngleQ5 < circleArcAngleEnd)
|
||||
{
|
||||
// end moved even lower
|
||||
minAngle = endAngleQ5;
|
||||
maxAngle = CWRUtil::Q5(ROUNDUP((int)circleArcAngleEnd, circleArcIncrementQ5int));
|
||||
maxAngle = MIN(maxAngle, circleArcAngleStart); // No need to go higher than start
|
||||
}
|
||||
else if (endAngleQ5 < circleArcAngleStart)
|
||||
{
|
||||
// end moved higher, but not higher than start
|
||||
minAngle = circleArcAngleEnd;
|
||||
maxAngle = CWRUtil::Q5(ROUNDUP((int)endAngleQ5, circleArcIncrementQ5int));
|
||||
maxAngle = MIN(maxAngle, circleArcAngleStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
// end moved past start
|
||||
minAngle = circleArcAngleEnd;
|
||||
maxAngle = endAngleQ5;
|
||||
}
|
||||
}
|
||||
return getMinimalRect(minAngle, maxAngle);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,358 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/widgets/canvas/Canvas.hpp>
|
||||
#include <touchgfx/widgets/canvas/Line.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
Line::Line()
|
||||
: CanvasWidget(),
|
||||
startXQ5(0), startYQ5(0), endXQ5(0), endYQ5(0),
|
||||
lineWidthQ5(CWRUtil::toQ5<int>(1)),
|
||||
lineEnding(BUTT_CAP_ENDING),
|
||||
minimalRect(),
|
||||
lineCapArcIncrement(18)
|
||||
{
|
||||
Drawable::setWidthHeight(0, 0);
|
||||
}
|
||||
|
||||
void Line::setStart(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
||||
{
|
||||
if (startXQ5 == xQ5 && startYQ5 == yQ5)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
startXQ5 = xQ5;
|
||||
startYQ5 = yQ5;
|
||||
|
||||
updateCachedShape();
|
||||
}
|
||||
|
||||
void Line::updateStart(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
||||
{
|
||||
if (startXQ5 == xQ5 && startYQ5 == yQ5)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateContent();
|
||||
|
||||
startXQ5 = xQ5;
|
||||
startYQ5 = yQ5;
|
||||
|
||||
updateCachedShape();
|
||||
|
||||
invalidateContent();
|
||||
}
|
||||
|
||||
void Line::setEnd(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
||||
{
|
||||
if (endXQ5 == xQ5 && endYQ5 == yQ5)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
endXQ5 = xQ5;
|
||||
endYQ5 = yQ5;
|
||||
|
||||
updateCachedShape();
|
||||
}
|
||||
|
||||
void Line::updateEnd(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5)
|
||||
{
|
||||
if (endXQ5 == xQ5 && endYQ5 == yQ5)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateContent();
|
||||
|
||||
endXQ5 = xQ5;
|
||||
endYQ5 = yQ5;
|
||||
|
||||
updateCachedShape();
|
||||
|
||||
invalidateContent();
|
||||
}
|
||||
|
||||
void Line::setLineEndingStyle(Line::LINE_ENDING_STYLE lineEndingStyle)
|
||||
{
|
||||
lineEnding = lineEndingStyle;
|
||||
updateCachedShape();
|
||||
}
|
||||
|
||||
Line::LINE_ENDING_STYLE Line::getLineEndingStyle() const
|
||||
{
|
||||
return lineEnding;
|
||||
}
|
||||
|
||||
void Line::setCapPrecision(int precision)
|
||||
{
|
||||
if (precision < 1)
|
||||
{
|
||||
precision = 1;
|
||||
}
|
||||
if (precision > 180)
|
||||
{
|
||||
precision = 180;
|
||||
}
|
||||
lineCapArcIncrement = precision;
|
||||
}
|
||||
|
||||
bool Line::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidatedArea, getAlpha());
|
||||
|
||||
CWRUtil::Q5 radiusQ5;
|
||||
if (lineEnding == ROUND_CAP_ENDING)
|
||||
{
|
||||
const int angleInDegrees = CWRUtil::angle(cornerXQ5[0] - startXQ5, cornerYQ5[0] - startYQ5, radiusQ5);
|
||||
canvas.moveTo(cornerXQ5[0], cornerYQ5[0]);
|
||||
canvas.lineTo(cornerXQ5[1], cornerYQ5[1]);
|
||||
for (int i = lineCapArcIncrement; i < 180; i += lineCapArcIncrement)
|
||||
{
|
||||
canvas.lineTo(endXQ5 + radiusQ5 * CWRUtil::sine(angleInDegrees - i), endYQ5 - radiusQ5 * CWRUtil::cosine(angleInDegrees - i));
|
||||
}
|
||||
canvas.lineTo(cornerXQ5[2], cornerYQ5[2]);
|
||||
canvas.lineTo(cornerXQ5[3], cornerYQ5[3]);
|
||||
for (int i = 180 - lineCapArcIncrement; i > 0; i -= lineCapArcIncrement)
|
||||
{
|
||||
canvas.lineTo(startXQ5 + radiusQ5 * CWRUtil::sine(angleInDegrees + i), startYQ5 - radiusQ5 * CWRUtil::cosine(angleInDegrees + i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
canvas.moveTo(cornerXQ5[0], cornerYQ5[0]);
|
||||
canvas.lineTo(cornerXQ5[1], cornerYQ5[1]);
|
||||
canvas.lineTo(cornerXQ5[2], cornerYQ5[2]);
|
||||
canvas.lineTo(cornerXQ5[3], cornerYQ5[3]);
|
||||
}
|
||||
return canvas.render();
|
||||
}
|
||||
|
||||
void Line::updateCachedShape()
|
||||
{
|
||||
CWRUtil::Q5 dxQ5 = endXQ5 - startXQ5;
|
||||
CWRUtil::Q5 dyQ5 = endYQ5 - startYQ5;
|
||||
CWRUtil::Q5 dQ5 = CWRUtil::toQ5<int>(0);
|
||||
// Look for horizontal, vertical or no-line
|
||||
if (dxQ5 == 0)
|
||||
{
|
||||
if (dyQ5 == 0)
|
||||
{
|
||||
cornerXQ5[0] = cornerXQ5[1] = cornerXQ5[2] = cornerXQ5[3] = startXQ5;
|
||||
cornerYQ5[0] = cornerYQ5[1] = cornerYQ5[2] = cornerYQ5[3] = startYQ5;
|
||||
return;
|
||||
}
|
||||
dQ5 = abs(dyQ5);
|
||||
}
|
||||
else if (dyQ5 == 0)
|
||||
{
|
||||
dQ5 = abs(dxQ5);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We want to hit as close to the limit inside 32bits.
|
||||
// sqrt(0x7FFFFFFF / 2) = 46340, which is roughly toQ5(1448)
|
||||
static const int32_t MAXVAL = 46340;
|
||||
const int32_t common_divisor = gcd(abs((int32_t)dxQ5), abs((int32_t)dyQ5));
|
||||
// First try to scale down
|
||||
if (common_divisor != 1)
|
||||
{
|
||||
dxQ5 = CWRUtil::Q5((int32_t)dxQ5 / common_divisor);
|
||||
dyQ5 = CWRUtil::Q5((int32_t)dyQ5 / common_divisor);
|
||||
}
|
||||
// Neither dx or dy is zero, find the largest multiplier / smallest divisor to stay within limit
|
||||
if (abs((int32_t)dxQ5) <= MAXVAL || abs((int32_t)dyQ5) <= MAXVAL)
|
||||
{
|
||||
// Look for largest multiplier
|
||||
const int32_t mulx = MAXVAL / abs((int32_t)dxQ5);
|
||||
const int32_t muly = MAXVAL / abs((int32_t)dyQ5);
|
||||
const int32_t mult = MIN(mulx, muly);
|
||||
dxQ5 = CWRUtil::Q5(mult * (int32_t)dxQ5);
|
||||
dyQ5 = CWRUtil::Q5(mult * (int32_t)dyQ5);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look for smallest divisor
|
||||
const int32_t divx = abs((int32_t)dxQ5) / MAXVAL;
|
||||
const int32_t divy = abs((int32_t)dyQ5) / MAXVAL;
|
||||
const int32_t divi = MAX(divx, divy) + 1;
|
||||
dxQ5 = CWRUtil::Q5((int32_t)dxQ5 / divi);
|
||||
dyQ5 = CWRUtil::Q5((int32_t)dyQ5 / divi);
|
||||
}
|
||||
dQ5 = CWRUtil::length(dxQ5, dyQ5);
|
||||
}
|
||||
|
||||
dyQ5 = CWRUtil::muldivQ5(lineWidthQ5, dyQ5, dQ5) / 2;
|
||||
dxQ5 = CWRUtil::muldivQ5(lineWidthQ5, dxQ5, dQ5) / 2;
|
||||
|
||||
switch (lineEnding)
|
||||
{
|
||||
case BUTT_CAP_ENDING:
|
||||
cornerXQ5[0] = startXQ5 - dyQ5;
|
||||
cornerYQ5[0] = startYQ5 + dxQ5;
|
||||
cornerXQ5[1] = endXQ5 - dyQ5;
|
||||
cornerYQ5[1] = endYQ5 + dxQ5;
|
||||
cornerXQ5[2] = endXQ5 + dyQ5;
|
||||
cornerYQ5[2] = endYQ5 - dxQ5;
|
||||
cornerXQ5[3] = startXQ5 + dyQ5;
|
||||
cornerYQ5[3] = startYQ5 - dxQ5;
|
||||
break;
|
||||
case ROUND_CAP_ENDING:
|
||||
// Nothing cached, calculated on each draw, but extremes are same as SQUARE_CAP_ENDING, so
|
||||
// Fall Through (for calculations)
|
||||
case SQUARE_CAP_ENDING:
|
||||
cornerXQ5[0] = (startXQ5 - dyQ5) - dxQ5;
|
||||
cornerYQ5[0] = (startYQ5 + dxQ5) - dyQ5;
|
||||
cornerXQ5[1] = (endXQ5 - dyQ5) + dxQ5;
|
||||
cornerYQ5[1] = (endYQ5 + dxQ5) + dyQ5;
|
||||
cornerXQ5[2] = (endXQ5 + dyQ5) + dxQ5;
|
||||
cornerYQ5[2] = (endYQ5 - dxQ5) + dyQ5;
|
||||
cornerXQ5[3] = (startXQ5 + dyQ5) - dxQ5;
|
||||
cornerYQ5[3] = (startYQ5 - dxQ5) - dyQ5;
|
||||
break;
|
||||
}
|
||||
|
||||
CWRUtil::Q5 xMinQ5 = cornerXQ5[0];
|
||||
CWRUtil::Q5 xMaxQ5 = cornerXQ5[0];
|
||||
CWRUtil::Q5 yMinQ5 = cornerYQ5[0];
|
||||
CWRUtil::Q5 yMaxQ5 = cornerYQ5[0];
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
if (cornerXQ5[i] < xMinQ5)
|
||||
{
|
||||
xMinQ5 = cornerXQ5[i];
|
||||
}
|
||||
if (cornerXQ5[i] > xMaxQ5)
|
||||
{
|
||||
xMaxQ5 = cornerXQ5[i];
|
||||
}
|
||||
if (cornerYQ5[i] < yMinQ5)
|
||||
{
|
||||
yMinQ5 = cornerYQ5[i];
|
||||
}
|
||||
if (cornerYQ5[i] > yMaxQ5)
|
||||
{
|
||||
yMaxQ5 = cornerYQ5[i];
|
||||
}
|
||||
}
|
||||
const int16_t xMin = xMinQ5.floor();
|
||||
const int16_t yMin = yMinQ5.floor();
|
||||
const int16_t xMax = xMaxQ5.ceil();
|
||||
const int16_t yMax = yMaxQ5.ceil();
|
||||
minimalRect = Rect(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
|
||||
if (lineEnding == ROUND_CAP_ENDING)
|
||||
{
|
||||
cornerXQ5[0] = startXQ5 - dyQ5;
|
||||
cornerYQ5[0] = startYQ5 + dxQ5;
|
||||
cornerXQ5[1] = endXQ5 - dyQ5;
|
||||
cornerYQ5[1] = endYQ5 + dxQ5;
|
||||
cornerXQ5[2] = endXQ5 + dyQ5;
|
||||
cornerYQ5[2] = endYQ5 - dxQ5;
|
||||
cornerXQ5[3] = startXQ5 + dyQ5;
|
||||
cornerYQ5[3] = startYQ5 - dxQ5;
|
||||
}
|
||||
}
|
||||
|
||||
touchgfx::Rect Line::rectContainingPoints(const Rect& fullRect, CWRUtil::Q5 x0, CWRUtil::Q5 y0, CWRUtil::Q5 x1, CWRUtil::Q5 y1, CWRUtil::Q5 x2, CWRUtil::Q5 y2) const
|
||||
{
|
||||
const int16_t xMin = MIN(MIN(x0, x1), x2).floor();
|
||||
const int16_t yMin = MIN(MIN(y0, y1), y2).floor();
|
||||
const int16_t xMax = MAX(MAX(x0, x1), x2).ceil();
|
||||
const int16_t yMax = MAX(MAX(y0, y1), y2).ceil();
|
||||
Rect r(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
r &= fullRect;
|
||||
return r;
|
||||
}
|
||||
|
||||
Rect Line::getMinimalRect() const
|
||||
{
|
||||
return minimalRect;
|
||||
}
|
||||
|
||||
void Line::updateLengthAndAngle(CWRUtil::Q5 length, CWRUtil::Q5 angle)
|
||||
{
|
||||
updateEnd(startXQ5 + length * CWRUtil::sine(angle), startYQ5 - length * CWRUtil::cosine(angle));
|
||||
}
|
||||
|
||||
void Line::invalidateContent() const
|
||||
{
|
||||
if (alpha == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Rect smallRect = getMinimalRect();
|
||||
if (abs(startXQ5.to<int>() - endXQ5.to<int>()) < lineWidthQ5.to<int>() * 2 ||
|
||||
abs(startYQ5.to<int>() - endYQ5.to<int>()) < lineWidthQ5.to<int>() * 2)
|
||||
{
|
||||
invalidateRect(smallRect);
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t center_x = ((startXQ5 + endXQ5) / 2).to<int16_t>();
|
||||
const int16_t center_y = ((startYQ5 + endYQ5) / 2).to<int16_t>();
|
||||
// The following should be "lineWidth/sqrt(2)" but to speed up we take the slightly larger "lineWidth/1.3333"="(lineWidth*3)/4"
|
||||
const int16_t extra_width = ((CWRUtil::Q5)(((int)(lineWidthQ5)*3 + 3) >> 2)).ceil();
|
||||
#define same_sign(x, y) (((x) < 0 && (y) < 0) || ((x) > 0 && (y) > 0))
|
||||
if (smallRect.width < smallRect.height)
|
||||
{
|
||||
const int16_t left_x = center_x - extra_width;
|
||||
const int16_t right_x = center_x + extra_width;
|
||||
// "vertical" line
|
||||
if (same_sign(endXQ5 - startXQ5, endYQ5 - startYQ5))
|
||||
{
|
||||
// From top-left to bottom-right
|
||||
Rect topLeftRect(smallRect.x, smallRect.y, right_x - smallRect.x, center_y - smallRect.y);
|
||||
Rect bottomRightRect(left_x, center_y, smallRect.right() - left_x, smallRect.bottom() - center_y);
|
||||
invalidateRect(topLeftRect);
|
||||
invalidateRect(bottomRightRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
// From top-right to bottom-left
|
||||
Rect topRightRect(left_x, smallRect.y, smallRect.right() - left_x, center_y - smallRect.y);
|
||||
Rect bottomLeftRect(smallRect.x, center_y, right_x - smallRect.x, smallRect.bottom() - center_y);
|
||||
invalidateRect(topRightRect);
|
||||
invalidateRect(bottomLeftRect);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int16_t top_y = center_y - extra_width;
|
||||
const int16_t bottom_y = center_y + extra_width;
|
||||
// "horizontal" line
|
||||
if (same_sign(endXQ5 - startXQ5, endYQ5 - startYQ5))
|
||||
{
|
||||
// From top-left to bottom-right
|
||||
Rect topLeftRect(smallRect.x, smallRect.y, center_x - smallRect.x, bottom_y - smallRect.y);
|
||||
Rect bottomRightRect(center_x, top_y, smallRect.right() - center_x, smallRect.bottom() - top_y);
|
||||
invalidateRect(topLeftRect);
|
||||
invalidateRect(bottomRightRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
// From top-right to bottom-left
|
||||
Rect bottomLeftRect(smallRect.x, top_y, center_x - smallRect.x, smallRect.bottom() - top_y);
|
||||
Rect topRightRect(center_x, smallRect.y, smallRect.right() - center_x, bottom_y - smallRect.y);
|
||||
invalidateRect(bottomLeftRect);
|
||||
invalidateRect(topRightRect);
|
||||
}
|
||||
}
|
||||
#undef same_sign
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/canvas/PainterABGR2222.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterABGR2222::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = color8;
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = alphaBlend(painterRed, painterGreen, painterBlue, *framebuffer, alpha);
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD8bpp_ABGR2222.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterABGR2222Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterABGR2222Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::ABGR2222) && "PainterABGR2222Bitmap only works with ABGR2222 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
void PainterABGR2222Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint8_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t abgr = *bitmapPointer++;
|
||||
const uint8_t srcAlpha = (abgr >> 6) * 0x55;
|
||||
const uint8_t a = LCD::div255(alpha * srcAlpha);
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = abgr;
|
||||
}
|
||||
else if (a)
|
||||
{
|
||||
*framebuffer = alphaBlend(abgr, *framebuffer, a);
|
||||
}
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/canvas/PainterARGB2222.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterARGB2222::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = color8;
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = alphaBlend(painterRed, painterGreen, painterBlue, *framebuffer, alpha);
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD8bpp_ARGB2222.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterARGB2222Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterARGB2222Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::ARGB2222) && "PainterARGB2222Bitmap only works with ARGB2222 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
void PainterARGB2222Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint8_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t argb = *bitmapPointer++;
|
||||
const uint8_t srcAlpha = (argb >> 6) * 0x55;
|
||||
const uint8_t a = LCD::div255(alpha * srcAlpha);
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = argb;
|
||||
}
|
||||
else if (a)
|
||||
{
|
||||
*framebuffer = alphaBlend(argb, *framebuffer, a);
|
||||
}
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterARGB8888.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterARGB8888::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint32_t* RESTRICT framebuffer = reinterpret_cast<uint32_t*>(destination) + offset;
|
||||
paint::argb8888::lineFromColor(framebuffer, count, painterColor, alpha);
|
||||
}
|
||||
|
||||
void PainterARGB8888::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterARGB8888Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterARGB8888Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::RGB565 || bitmapFormat == Bitmap::RGB888 || bitmapFormat == Bitmap::ARGB8888) && "PainterARGB8888Bitmap only works with RGB565, RGB888 and ARGB8888 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getExtraData() == 0) && "PainterARGB8888Bitmap does not work with RGB565 with alphas");
|
||||
}
|
||||
|
||||
void PainterARGB8888Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset * 4;
|
||||
const uint8_t* const lineEnd = framebuffer + count * 4;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (bitmapFormat == Bitmap::ARGB8888)
|
||||
{
|
||||
const uint32_t* const bitmapLineStart = reinterpret_cast<const uint32_t*>(bitmapData) + rowSkip;
|
||||
const uint32_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::argb8888::lineFromARGB8888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length * 4;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else if (bitmapFormat == Bitmap::RGB888)
|
||||
{
|
||||
const uint8_t* const bitmapLineStart = reinterpret_cast<const uint8_t*>(bitmapData) + rowSkip * 3;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX * 3;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::argb8888::lineFromRGB888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length * 4;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint16_t* const bitmapLineStart = reinterpret_cast<const uint16_t*>(bitmapData) + rowSkip;
|
||||
const uint16_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
|
||||
paint::argb8888::lineFromRGB565(framebuffer, bitmapPointer, length, alpha);
|
||||
|
||||
framebuffer += length * 4;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterARGB8888Bitmap::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterARGB8888L8Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterARGB8888L8Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::L8) && "PainterARGB8888L8Bitmap only works with L8 bitmaps");
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
bitmapCLUT = bitmap.getExtraData();
|
||||
assert(bitmapData && bitmapCLUT && "The bitmap does not have any data or any CLUT");
|
||||
l8format = static_cast<Bitmap::ClutFormat>(*reinterpret_cast<const uint16_t*>(bitmapCLUT));
|
||||
assert((l8format == Bitmap::CLUT_FORMAT_L8_RGB565 || l8format == Bitmap::CLUT_FORMAT_L8_ARGB8888 || l8format == Bitmap::CLUT_FORMAT_L8_RGB888) && "The palette is not in the right format");
|
||||
bitmapCLUT += 4; // Skip header
|
||||
}
|
||||
}
|
||||
|
||||
void PainterARGB8888L8Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset * 4;
|
||||
const uint8_t* const lineEnd = framebuffer + count * 4;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (l8format == Bitmap::CLUT_FORMAT_L8_RGB565)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint8_t* const chunkend = framebuffer + length * 4;
|
||||
count -= length;
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint16_t srcpix = reinterpret_cast<const uint16_t*>(bitmapCLUT)[*bitmapPointer++];
|
||||
*framebuffer++ = Color::getBlueFromRGB565(srcpix);
|
||||
*framebuffer++ = Color::getGreenFromRGB565(srcpix);
|
||||
*framebuffer++ = Color::getRedFromRGB565(srcpix);
|
||||
*framebuffer++ = 0xFF;
|
||||
} while (framebuffer < chunkend);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint16_t srcpix = reinterpret_cast<const uint16_t*>(bitmapCLUT)[*bitmapPointer++];
|
||||
const uint8_t ialpha = 0xFF - alpha;
|
||||
*framebuffer = LCD::div255(Color::getBlueFromRGB565(srcpix) * alpha + *framebuffer * ialpha);
|
||||
framebuffer++;
|
||||
*framebuffer = LCD::div255(Color::getGreenFromRGB565(srcpix) * alpha + *framebuffer * ialpha);
|
||||
framebuffer++;
|
||||
*framebuffer = LCD::div255(Color::getRedFromRGB565(srcpix) * alpha + *framebuffer * ialpha);
|
||||
framebuffer++;
|
||||
*framebuffer = *framebuffer + alpha - LCD::div255(*framebuffer * alpha);
|
||||
framebuffer++;
|
||||
} while (framebuffer < chunkend);
|
||||
}
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else if (l8format == Bitmap::CLUT_FORMAT_L8_RGB888)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::argb8888::lineFromL8RGB888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length * 4;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else // if (l8format == Bitmap::CLUT_FORMAT_L8_ARGB8888)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::argb8888::lineFromL8ARGB8888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length * 4;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
|
||||
bool PainterARGB8888L8Bitmap::setup(const Rect& widgetRect) const
|
||||
{
|
||||
if (!AbstractPainterARGB8888::setup(widgetRect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
updateBitmapOffsets(widgetWidth);
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
paint::setL8Palette(bitmapCLUT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PainterARGB8888L8Bitmap::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterARGB8888LinearGradient.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterARGB8888LinearGradient::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint32_t* p = reinterpret_cast<uint32_t*>(destination) + offset;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
// The whole line is the same color
|
||||
uint32_t color;
|
||||
if (widgetY <= coord0)
|
||||
{
|
||||
color = texture[deltaColor > 0 ? 0 : 1023];
|
||||
}
|
||||
else if (widgetY >= coord1)
|
||||
{
|
||||
color = texture[deltaColor > 0 ? 1023 : 0];
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32_t colorOffset = (int)((widgetY - coord0) * deltaColor);
|
||||
color = texture[deltaColor > 0 ? colorOffset : 1023 + colorOffset];
|
||||
}
|
||||
|
||||
uint32_t* const p_end = p + count;
|
||||
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
const int32_t solidColor = (0xFFU << 24) | color;
|
||||
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = solidColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (color >> 24) & 0xFF;
|
||||
const uint8_t alphaFg = LCD::div255(a * alpha);
|
||||
|
||||
if (alphaFg)
|
||||
{
|
||||
const uint8_t b = color & 0xFF;
|
||||
const uint8_t g = (color >> 8) & 0xFF;
|
||||
const uint8_t r = (color >> 16) & 0xFF;
|
||||
|
||||
while (p < p_end)
|
||||
{
|
||||
const uint32_t rgbBg = *p;
|
||||
const uint8_t alphaBg = rgbBg >> 24;
|
||||
|
||||
// DMA2D blending algo
|
||||
const uint8_t alphaMult = LCD::div255(alphaFg * alphaBg);
|
||||
const uint8_t alphaOut = alphaFg + alphaBg - alphaMult;
|
||||
const uint8_t blueOut = (b * alphaFg + (rgbBg & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t greenOut = (g * alphaFg + ((rgbBg >> 8) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t redOut = (r * alphaFg + ((rgbBg >> 16) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
|
||||
*p++ = (alphaOut << 24) | (redOut << 16) | (greenOut << 8) | blueOut;
|
||||
}
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// vx0 is where first color line (y=clSlope*x+clOffset) intersects the horizontal line y=widgetY
|
||||
float vx0;
|
||||
if (isHorizontal)
|
||||
{
|
||||
vx0 = coord0;
|
||||
}
|
||||
else
|
||||
{
|
||||
vx0 = (widgetY - clOffset) / clSlope;
|
||||
if (deltaColor < 0.0f)
|
||||
{
|
||||
vx0 -= horizontalDistance;
|
||||
}
|
||||
}
|
||||
|
||||
int x = widgetX;
|
||||
|
||||
// Left of gradient
|
||||
// All pixels up to first gradient line, paint using fixed color
|
||||
if (x <= vx0)
|
||||
{
|
||||
// Starting before gradient
|
||||
const int pixels = (int)(MIN(vx0 + 1 - x, count));
|
||||
// Start in correct end of palette depending on direction
|
||||
const uint32_t colorStart = texture[deltaColor > 0 ? 0 : 1023];
|
||||
uint32_t* const p_end = p + pixels;
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
const int32_t solidColor = (0xFFU << 24) | colorStart;
|
||||
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = solidColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (colorStart >> 24) & 0xFF;
|
||||
const uint8_t alphaFg = LCD::div255(a * alpha);
|
||||
|
||||
if (alphaFg)
|
||||
{
|
||||
const uint8_t b = colorStart & 0xFF;
|
||||
const uint8_t g = (colorStart >> 8) & 0xFF;
|
||||
const uint8_t r = (colorStart >> 16) & 0xFF;
|
||||
|
||||
while (p < p_end)
|
||||
{
|
||||
const uint32_t rgbBg = *p;
|
||||
const uint8_t alphaBg = rgbBg >> 24;
|
||||
|
||||
// DMA2D blending algo
|
||||
const uint8_t alphaMult = LCD::div255(alphaFg * alphaBg);
|
||||
const uint8_t alphaOut = alphaFg + alphaBg - alphaMult;
|
||||
const uint8_t blueOut = (b * alphaFg + (rgbBg & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t greenOut = (g * alphaFg + ((rgbBg >> 8) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t redOut = (r * alphaFg + ((rgbBg >> 16) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
|
||||
*p++ = (alphaOut << 24) | (redOut << 16) | (greenOut << 8) | blueOut;
|
||||
}
|
||||
};
|
||||
}
|
||||
x += pixels;
|
||||
}
|
||||
|
||||
const int16_t right = widgetX + count;
|
||||
if (x < right) // More to draw
|
||||
{
|
||||
// vx1 is where y=widgetY intersects the last color line (fixed distance from vx0)
|
||||
const float vx1 = vx0 + horizontalDistance;
|
||||
|
||||
if (x < vx1) // Draw gradient part until last color line
|
||||
{
|
||||
const int endx = (int)(MIN(right, vx1));
|
||||
const uint32_t* const p_end = p + (endx - x);
|
||||
|
||||
// Start with first or last color depending on direction
|
||||
float colorF = deltaColor > 0 ? 0.0000f : 1023.9999f;
|
||||
colorF += (x - vx0) * deltaColor;
|
||||
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
const int colorIndex = (int)colorF;
|
||||
const uint32_t* color = (const uint32_t*)(texture + colorIndex);
|
||||
|
||||
*p++ = (0xFFU << 24) | *color;
|
||||
|
||||
colorF += deltaColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go to 8-bit pointers
|
||||
const uint32_t* const gradient = (const uint32_t*)texture;
|
||||
while (p < p_end)
|
||||
{
|
||||
const int colorIndex = (int)colorF;
|
||||
|
||||
const uint32_t* color = gradient + colorIndex;
|
||||
const uint8_t a = (*color >> 24) & 0xFF;
|
||||
const uint8_t alphaFg = LCD::div255(a * alpha);
|
||||
|
||||
if (alphaFg)
|
||||
{
|
||||
const uint8_t b = *color & 0xFF;
|
||||
const uint8_t g = (*color >> 8) & 0xFF;
|
||||
const uint8_t r = (*color >> 16) & 0xFF;
|
||||
|
||||
const uint32_t rgbBg = *p;
|
||||
const uint8_t alphaBg = rgbBg >> 24;
|
||||
|
||||
// DMA2D blending algo
|
||||
const uint8_t alphaMult = LCD::div255(alphaFg * alphaBg);
|
||||
const uint8_t alphaOut = alphaFg + alphaBg - alphaMult;
|
||||
const uint8_t blueOut = (b * alphaFg + (rgbBg & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t greenOut = (g * alphaFg + ((rgbBg >> 8) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t redOut = (r * alphaFg + ((rgbBg >> 16) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
|
||||
*p++ = (alphaOut << 24) | (redOut << 16) | (greenOut << 8) | blueOut;
|
||||
};
|
||||
|
||||
colorF += deltaColor;
|
||||
}
|
||||
}
|
||||
x = endx;
|
||||
}
|
||||
|
||||
if (x < right) // Second fixed color part
|
||||
{
|
||||
const uint32_t* const p_end = p + (right - x);
|
||||
const uint32_t colorEnd = texture[deltaColor > 0 ? 1023 : 0];
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
const int32_t solidColor = (0xFFU << 24) | colorEnd;
|
||||
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = solidColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t b = colorEnd & 0xFF;
|
||||
const uint8_t g = (colorEnd >> 8) & 0xFF;
|
||||
const uint8_t r = (colorEnd >> 16) & 0xFF;
|
||||
|
||||
const uint8_t a = (colorEnd >> 24) & 0xFF;
|
||||
const uint8_t alphaFg = LCD::div255(a * alpha);
|
||||
|
||||
while (p < p_end)
|
||||
{
|
||||
if (alphaFg)
|
||||
{
|
||||
const uint32_t rgbBg = *p;
|
||||
const uint8_t alphaBg = rgbBg >> 24;
|
||||
|
||||
// DMA2D blending algo
|
||||
const uint8_t alphaMult = LCD::div255(alphaFg * alphaBg);
|
||||
const uint8_t alphaOut = alphaFg + alphaBg - alphaMult;
|
||||
const uint8_t blueOut = (b * alphaFg + (rgbBg & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t greenOut = (g * alphaFg + ((rgbBg >> 8) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
const uint8_t redOut = (r * alphaFg + ((rgbBg >> 16) & 0xFF) * (alphaBg - alphaMult)) / alphaOut;
|
||||
|
||||
*p++ = (alphaOut << 24) | (redOut << 16) | (greenOut << 8) | blueOut;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PainterARGB8888LinearGradient::tearDown() const
|
||||
{
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/canvas/PainterBGRA2222.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterBGRA2222::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = color8;
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = alphaBlend(painterRed, painterGreen, painterBlue, *framebuffer, alpha);
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD8bpp_BGRA2222.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterBGRA2222Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterBGRA2222Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::BGRA2222) && "PainterBGRA2222Bitmap only works with BGRA2222 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
void PainterBGRA2222Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint8_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t bgra = *bitmapPointer++;
|
||||
const uint8_t srcAlpha = (bgra & 0x03) * 0x55;
|
||||
const uint8_t a = LCD::div255(alpha * srcAlpha);
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = bgra;
|
||||
}
|
||||
else if (a)
|
||||
{
|
||||
*framebuffer = alphaBlend(bgra, *framebuffer, a);
|
||||
}
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,41 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/canvas/PainterBW.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterBW::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* RESTRICT framebuffer = destination + (offset / 8);
|
||||
|
||||
if (alpha >= 0x80)
|
||||
{
|
||||
do
|
||||
{
|
||||
const unsigned pixel = 1 << (7 - (offset % 8));
|
||||
if (painterBW)
|
||||
{
|
||||
*framebuffer |= pixel;
|
||||
}
|
||||
else
|
||||
{
|
||||
*framebuffer &= ~pixel;
|
||||
}
|
||||
if (((++offset) % 8) == 0)
|
||||
{
|
||||
framebuffer++;
|
||||
}
|
||||
} while (--count);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterBWBitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterBWBitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::BW || bitmapFormat == Bitmap::BW_RLE) && "PainterBWBitmap only works with BW and BW_RLE bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
// Found in LCD1bpp
|
||||
void fillBits(uint8_t* fb, int16_t startX, int16_t startY, int16_t stride, int32_t count, uint8_t color);
|
||||
|
||||
void PainterBWBitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t /*alpha*/) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (bitmapFormat == Bitmap::BW_RLE)
|
||||
{
|
||||
const uint32_t rowSkip = widgetY * bitmapRect.width;
|
||||
LCD1bpp::bwRLEdata bw_rle(bitmapData);
|
||||
bw_rle.skipNext(widgetX + rowSkip);
|
||||
do
|
||||
{
|
||||
unsigned int length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint32_t bw_length = bw_rle.getLength();
|
||||
const uint32_t bitsToDraw = MIN(bw_length, length);
|
||||
|
||||
fillBits(destination, offset, 0, 0 /* not used */, bitsToDraw, bw_rle.getColor());
|
||||
offset += bitsToDraw;
|
||||
length -= bitsToDraw;
|
||||
bw_rle.skipNext(bitsToDraw);
|
||||
} while (length);
|
||||
bw_rle.init(bitmapData);
|
||||
bw_rle.skipNext(rowSkip);
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (count);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32_t rowSkip = widgetY * ((bitmapRect.width + 7) / 8);
|
||||
const uint8_t* bitmapPointer = bitmapData + rowSkip;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
|
||||
const uint8_t* src = bitmapPointer + widgetX / 8;
|
||||
uint8_t* RESTRICT dst = destination + (offset / 8);
|
||||
uint16_t srcBitX = widgetX % 8; // & 7
|
||||
const uint16_t dstBitX = offset % 8; // & 7
|
||||
|
||||
uint16_t remainingBits = length;
|
||||
|
||||
if (dstBitX > 0)
|
||||
{
|
||||
// Start by getting (dst-)aligned for faster transfer
|
||||
uint16_t neededBits = 8 - dstBitX;
|
||||
if (neededBits > remainingBits)
|
||||
{
|
||||
neededBits = remainingBits; // Very narrow src inside same word
|
||||
}
|
||||
const uint16_t bitmapAvailableBits = 8 - srcBitX;
|
||||
uint8_t mask = (1u << neededBits) - 1u;
|
||||
const uint8_t dstShift = static_cast<uint8_t>(8u - (dstBitX + neededBits));
|
||||
mask <<= dstShift;
|
||||
|
||||
uint8_t word = *src;
|
||||
|
||||
if (bitmapAvailableBits > neededBits)
|
||||
{
|
||||
word >>= bitmapAvailableBits - neededBits;
|
||||
}
|
||||
else if (bitmapAvailableBits < neededBits)
|
||||
{
|
||||
// Get the last required bits from src[1]
|
||||
word <<= neededBits - bitmapAvailableBits;
|
||||
word |= src[1] >> (8u - (neededBits - bitmapAvailableBits));
|
||||
}
|
||||
|
||||
word <<= dstShift;
|
||||
*dst = (*dst & ~mask) | (word & mask);
|
||||
|
||||
srcBitX = (srcBitX + neededBits) % 8; // & 7
|
||||
|
||||
if (bitmapAvailableBits <= neededBits)
|
||||
{
|
||||
src++;
|
||||
}
|
||||
dst++;
|
||||
remainingBits -= neededBits;
|
||||
}
|
||||
|
||||
// dstX is now word aligned (or we have transferred everything of a narrow image and remainingBits==0)
|
||||
if (remainingBits >= 8)
|
||||
{
|
||||
const uint16_t bytesPerLine = remainingBits / 8;
|
||||
if (srcBitX == 0)
|
||||
{
|
||||
HAL::getInstance()->blockCopy(dst, src, bytesPerLine);
|
||||
src += bytesPerLine;
|
||||
dst += bytesPerLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint16_t _remainingBits = remainingBits;
|
||||
|
||||
remainingBits = _remainingBits;
|
||||
while (remainingBits >= 8)
|
||||
{
|
||||
uint8_t word = *src++;
|
||||
word <<= srcBitX;
|
||||
word |= (*src) >> (8 - srcBitX);
|
||||
*dst++ = word;
|
||||
remainingBits -= 8;
|
||||
}
|
||||
}
|
||||
remainingBits %= 8; // &= 7
|
||||
}
|
||||
|
||||
// Take the last bits, again we need to mask dst
|
||||
if (remainingBits > 0)
|
||||
{
|
||||
uint8_t word = *src;
|
||||
if (srcBitX != 0)
|
||||
{
|
||||
word <<= srcBitX;
|
||||
word |= src[1] >> (8u - srcBitX);
|
||||
}
|
||||
const uint8_t mask = ((1u << remainingBits) - 1u) << (8u - remainingBits);
|
||||
*dst = (*dst & ~mask) | (word & mask);
|
||||
}
|
||||
|
||||
offset += length;
|
||||
count -= length;
|
||||
widgetX = 0;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (count);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterGRAY2.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterGRAY2::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
LCD2bpp::setPixel(destination, offset, painterGray);
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t framebufferGray = LCD2bpp::getPixel(destination, offset);
|
||||
LCD2bpp::setPixel(destination, offset, LCD::div255((painterGray * alpha + framebufferGray * (0xFF - alpha)) * 0x55) >> 6);
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD2bpp.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterGRAY2Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterGRAY2Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::GRAY2) && "PainterGRAY2Bitmap only works with GRAY2 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
bitmapExtraData = bitmap.getExtraData();
|
||||
}
|
||||
|
||||
void PainterGRAY2Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t rowSkip = widgetY * ((bitmapRect.width + 3) / 4);
|
||||
const uint8_t* bitmapPointer = bitmapData + rowSkip;
|
||||
if (bitmapExtraData)
|
||||
{
|
||||
const uint8_t* alphaPointer = bitmapExtraData + rowSkip;
|
||||
do
|
||||
{
|
||||
const uint8_t bitmapGray = LCD2bpp::getPixel(bitmapPointer, widgetX);
|
||||
const uint8_t a = LCD::div255(alpha * (LCD2bpp::getPixel(alphaPointer, widgetX) * 0x55));
|
||||
|
||||
if (a == 0xFF)
|
||||
{
|
||||
LCD2bpp::setPixel(destination, offset, bitmapGray);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t framebufferGray = LCD2bpp::getPixel(destination, offset);
|
||||
LCD2bpp::setPixel(destination, offset, LCD::div255((bitmapGray * a + framebufferGray * (0xFF - a)) * 0x55) >> 6);
|
||||
}
|
||||
widgetX++;
|
||||
widgetX %= bitmapRect.width;
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
else if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t bitmapGray = LCD2bpp::getPixel(bitmapPointer, widgetX);
|
||||
LCD2bpp::setPixel(destination, offset, bitmapGray);
|
||||
widgetX++;
|
||||
widgetX %= bitmapRect.width;
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t bitmapGray = LCD2bpp::getPixel(bitmapPointer, widgetX);
|
||||
const uint8_t framebufferGray = LCD2bpp::getPixel(destination, offset);
|
||||
LCD2bpp::setPixel(destination, offset, LCD::div255((bitmapGray * alpha + framebufferGray * (0xFF - alpha)) * 0x55) >> 6);
|
||||
widgetX++;
|
||||
widgetX %= bitmapRect.width;
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterGRAY4.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterGRAY4::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
LCD4bpp::setPixel(destination, offset, painterGray);
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t framebufferGray = LCD4bpp::getPixel(destination, offset);
|
||||
LCD4bpp::setPixel(destination, offset, LCD::div255((painterGray * alpha + framebufferGray * (0xFF - alpha)) * 0x11) >> 4);
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD4bpp.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterGRAY4Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterGRAY4Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::GRAY4) && "PainterGRAY4Bitmap only works with GRAY4 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
bitmapExtraData = bitmap.getExtraData();
|
||||
}
|
||||
|
||||
void PainterGRAY4Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t rowSkip = widgetY * ((bitmapRect.width + 1) / 2);
|
||||
const uint8_t* bitmapPointer = bitmapData + rowSkip;
|
||||
if (bitmapExtraData)
|
||||
{
|
||||
const uint8_t* alphaPointer = bitmapExtraData + rowSkip;
|
||||
do
|
||||
{
|
||||
const uint8_t bitmapGray = LCD4bpp::getPixel(bitmapPointer, widgetX);
|
||||
const uint8_t a = LCD::div255(alpha * (LCD4bpp::getPixel(alphaPointer, widgetX) * 0x11));
|
||||
if (a == 0xFF)
|
||||
{
|
||||
LCD4bpp::setPixel(destination, offset, bitmapGray);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t framebufferGray = LCD4bpp::getPixel(destination, offset);
|
||||
LCD4bpp::setPixel(destination, offset, LCD::div255((bitmapGray * a + framebufferGray * (0xFF - a)) * 0x11) >> 4);
|
||||
}
|
||||
widgetX++;
|
||||
widgetX %= bitmapRect.width;
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
else if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t bitmapGray = LCD4bpp::getPixel(bitmapPointer, widgetX);
|
||||
LCD4bpp::setPixel(destination, offset, bitmapGray);
|
||||
widgetX++;
|
||||
widgetX %= bitmapRect.width;
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t bitmapGray = LCD4bpp::getPixel(bitmapPointer, widgetX);
|
||||
const uint8_t framebufferGray = LCD4bpp::getPixel(destination, offset);
|
||||
LCD4bpp::setPixel(destination, offset, LCD::div255((bitmapGray * alpha + framebufferGray * (0xFF - alpha)) * 0x11) >> 4);
|
||||
widgetX++;
|
||||
widgetX %= bitmapRect.width;
|
||||
offset++;
|
||||
} while (--count != 0);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB565.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB565::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint16_t* const framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
|
||||
paint::rgb565::lineFromColor(framebuffer, count, painterColor, alpha, color565);
|
||||
}
|
||||
|
||||
void PainterRGB565::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD16bpp.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB565Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB565Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::RGB565 || bitmapFormat == Bitmap::ARGB8888) && "PainterRGB565Bitmap only works with RGB565 and ARGB8888 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
bitmapExtraData = bitmap.getExtraData();
|
||||
}
|
||||
|
||||
void PainterRGB565Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t* RESTRICT framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
|
||||
const uint16_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (bitmapFormat == Bitmap::RGB565)
|
||||
{
|
||||
const uint16_t* const bitmapLineStart = reinterpret_cast<const uint16_t*>(bitmapData) + rowSkip;
|
||||
const uint16_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
if (bitmapExtraData)
|
||||
{
|
||||
const uint8_t* const alpha_linestart = bitmapExtraData + rowSkip;
|
||||
const uint8_t* alphaPointer = alpha_linestart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint16_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t a = LCD::div255(alpha * (*alphaPointer++));
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = *bitmapPointer;
|
||||
}
|
||||
else if (a)
|
||||
{
|
||||
*framebuffer = alphaBlend(*bitmapPointer, *framebuffer, a);
|
||||
}
|
||||
bitmapPointer++;
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
alphaPointer = alpha_linestart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
|
||||
// Call external (DMA) painter function
|
||||
paint::rgb565::lineFromRGB565(framebuffer, bitmapPointer, length, alpha);
|
||||
|
||||
framebuffer += length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
else // if (bitmapFormat == Bitmap::ARGB8888)
|
||||
{
|
||||
const uint32_t* const bitmapLineStart = reinterpret_cast<const uint32_t*>(bitmapData) + rowSkip;
|
||||
const uint32_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
|
||||
//Call external (DMA) painter function
|
||||
paint::rgb565::lineFromARGB8888(framebuffer, bitmapPointer, length, alpha);
|
||||
|
||||
framebuffer += length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterRGB565Bitmap::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD16bpp.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB565L8Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB565L8Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::L8) && "PainterRGB565L8Bitmap only works with appropriate L8 bitmaps");
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
bitmapCLUT = bitmap.getExtraData();
|
||||
assert(bitmapData && bitmapCLUT && "The bitmap does not have any data or any CLUT");
|
||||
l8format = static_cast<Bitmap::ClutFormat>(*reinterpret_cast<const uint16_t*>(bitmapCLUT));
|
||||
assert((l8format == Bitmap::CLUT_FORMAT_L8_RGB565 || l8format == Bitmap::CLUT_FORMAT_L8_ARGB8888 || l8format == Bitmap::CLUT_FORMAT_L8_RGB888) && "The palette is not in the right format");
|
||||
bitmapCLUT += 4; // Skip header
|
||||
}
|
||||
}
|
||||
|
||||
void PainterRGB565L8Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t* RESTRICT framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
|
||||
const uint16_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
// Max number of pixels before we reach end of bitmap row
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (l8format == Bitmap::CLUT_FORMAT_L8_RGB565)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint16_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = reinterpret_cast<const uint16_t*>(bitmapCLUT)[*bitmapPointer++];
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = alphaBlend(reinterpret_cast<const uint16_t*>(bitmapCLUT)[*bitmapPointer++], *framebuffer, alpha);
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else if (l8format == Bitmap::CLUT_FORMAT_L8_RGB888)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::rgb565::lineFromL8RGB888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else // Bitmap::CLUT_FORMAT_L8_ARGB8888
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::rgb565::lineFromL8ARGB8888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
|
||||
bool PainterRGB565L8Bitmap::setup(const Rect& widgetRect) const
|
||||
{
|
||||
if (!AbstractPainterRGB565::setup(widgetRect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
updateBitmapOffsets(widgetWidth);
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
paint::setL8Palette(bitmapCLUT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PainterRGB565L8Bitmap::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Utils.hpp>
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB565LinearGradient.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
const uint16_t RMASK = 0xF800; // Mask for red (1111100000000000)
|
||||
const uint16_t GMASK = 0x07E0; // Mask for green (0000011111100000)
|
||||
const uint16_t BMASK = 0x001F; // Mask for blue (0000000000011111)
|
||||
|
||||
FORCE_INLINE_FUNCTION static uint16_t getNativeColor(colortype color)
|
||||
{
|
||||
return ((color >> 8) & 0xF800) | ((color >> 5) & 0x07E0) | ((color >> 3) & 0x001F);
|
||||
}
|
||||
|
||||
FORCE_INLINE_FUNCTION static uint16_t getNativeColorFromRGB(uint8_t red, uint8_t green, uint8_t blue)
|
||||
{
|
||||
return ((red << 8) & 0xF800) | ((green << 3) & 0x07E0) | ((blue >> 3) & 0x001F);
|
||||
}
|
||||
|
||||
FORCE_INLINE_FUNCTION static uint16_t alphaBlend(uint16_t R, uint16_t G, uint16_t B, uint16_t bufpix, uint8_t alpha)
|
||||
{
|
||||
const uint8_t ialpha = 0xFF - alpha;
|
||||
return (((R * alpha + (bufpix & RMASK) * ialpha) / 255) & RMASK) |
|
||||
(((G * alpha + (bufpix & GMASK) * ialpha) / 255) & GMASK) |
|
||||
(((B * alpha + (bufpix & BMASK) * ialpha) / 255) & BMASK);
|
||||
}
|
||||
|
||||
static uint16_t random(int x, int y)
|
||||
{
|
||||
int h = x * 374761393 + y * 668265263;
|
||||
h = (h ^ (h >> 13)) * 1274126177;
|
||||
return static_cast<uint16_t>(h ^ (h >> 16));
|
||||
}
|
||||
|
||||
static uint16_t applyNoise(int x, int y, uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
const uint16_t rounding = random(x, y);
|
||||
if (r < 0xF8)
|
||||
{
|
||||
r += rounding & 7;
|
||||
}
|
||||
if (g < 0xFC)
|
||||
{
|
||||
g += (rounding >> 3) & 3;
|
||||
}
|
||||
if (b < 0xF8)
|
||||
{
|
||||
b += (rounding >> 5) & 7;
|
||||
}
|
||||
return getNativeColorFromRGB(r, g, b);
|
||||
}
|
||||
|
||||
void PainterRGB565LinearGradient::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint16_t* p = reinterpret_cast<uint16_t*>(destination) + offset;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
// The whole line is the same color
|
||||
uint32_t color;
|
||||
bool noise = false;
|
||||
if (widgetY <= coord0)
|
||||
{
|
||||
color = texture[deltaColor > 0 ? 0 : 1023];
|
||||
}
|
||||
else if (widgetY >= coord1)
|
||||
{
|
||||
color = texture[deltaColor > 0 ? 1023 : 0];
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32_t colorOffset = (int)((widgetY - coord0) * deltaColor);
|
||||
color = texture[deltaColor > 0 ? colorOffset : 1023 + colorOffset];
|
||||
noise = true;
|
||||
}
|
||||
|
||||
const uint16_t color565 = getNativeColor(color);
|
||||
uint16_t* const p_end = p + count;
|
||||
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
if (noise)
|
||||
{
|
||||
const uint8_t b = color & 0xFF;
|
||||
const uint8_t g = (color >> 8) & 0xFF;
|
||||
const uint8_t r = (color >> 16) & 0xFF;
|
||||
int x = widgetX;
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = applyNoise(x, widgetY, r, g, b);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = color565;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (color >> 24) & 0xFF;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint16_t r = (color >> 8) & RMASK;
|
||||
const uint16_t g = (color >> 5) & GMASK;
|
||||
const uint16_t b = (color >> 3) & BMASK;
|
||||
while (p < p_end)
|
||||
{
|
||||
*p = alphaBlend(r, g, b, *p, alphaTot);
|
||||
p++;
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//vx0 is where first color line (y=clSlope*x+clOffset) intersects the horizontal line y=widgetY
|
||||
float vx0;
|
||||
if (isHorizontal)
|
||||
{
|
||||
vx0 = coord0;
|
||||
}
|
||||
else
|
||||
{
|
||||
vx0 = (widgetY - clOffset) / clSlope;
|
||||
if (deltaColor < 0.0f)
|
||||
{
|
||||
vx0 -= horizontalDistance;
|
||||
}
|
||||
}
|
||||
|
||||
int x = widgetX;
|
||||
|
||||
// Left of gradient
|
||||
// All pixels up to first gradient line, paint using fixed color
|
||||
if (x <= vx0) // All pixels up to first gradient line
|
||||
{
|
||||
// Starting before gradient
|
||||
const int pixels = (int)(MIN(vx0 + 1 - x, count));
|
||||
// Start in correct end of palette depending on direction
|
||||
const uint32_t colorStart = texture[deltaColor > 0 ? 0 : 1023];
|
||||
uint16_t* const p_end = p + pixels;
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
const uint16_t color565 = getNativeColor(colorStart);
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = color565;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (colorStart >> 24) & 0xFF;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint16_t r = (colorStart >> 8) & RMASK;
|
||||
const uint16_t g = (colorStart >> 5) & GMASK;
|
||||
const uint16_t b = (colorStart >> 3) & BMASK;
|
||||
while (p < p_end)
|
||||
{
|
||||
*p = alphaBlend(r, g, b, *p, alphaTot);
|
||||
p++;
|
||||
};
|
||||
}
|
||||
x += pixels;
|
||||
}
|
||||
|
||||
const int16_t right = widgetX + count;
|
||||
if (x < right) // More to draw
|
||||
{
|
||||
//vx1 is where y=widgetY intersects the last color line (fixed distance from vx0)
|
||||
const float vx1 = vx0 + horizontalDistance;
|
||||
|
||||
if (x < vx1) // Draw gradient part until last color line
|
||||
{
|
||||
const int endx = (int)(MIN(right, vx1));
|
||||
const uint16_t* const p_end = p + (endx - x);
|
||||
|
||||
// Start with first or last color depending on direction
|
||||
float colorF = deltaColor > 0 ? 0.0000f : 1023.9999f;
|
||||
colorF += (x - vx0) * deltaColor;
|
||||
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
const int colorIndex = (int)colorF;
|
||||
const uint32_t color = texture[colorIndex];
|
||||
const uint8_t b = color & 0xFF;
|
||||
const uint8_t g = (color >> 8) & 0xFF;
|
||||
const uint8_t r = (color >> 16) & 0xFF;
|
||||
*p++ = applyNoise(colorIndex, widgetY, r, g, b);
|
||||
colorF += deltaColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
const int colorIndex = (int)colorF;
|
||||
|
||||
const uint32_t color = texture[colorIndex];
|
||||
const uint8_t a = color >> 24;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint16_t r = (color >> 8) & RMASK;
|
||||
const uint16_t g = (color >> 5) & GMASK;
|
||||
const uint16_t b = (color >> 3) & BMASK;
|
||||
*p = alphaBlend(r, g, b, *p, alphaTot);
|
||||
p++;
|
||||
colorF += deltaColor;
|
||||
}
|
||||
}
|
||||
x = endx;
|
||||
}
|
||||
|
||||
if (x < right) // Second fixed color part
|
||||
{
|
||||
const uint16_t* const p_end = p + (right - x);
|
||||
const uint32_t colorEnd = texture[deltaColor > 0 ? 1023 : 0];
|
||||
const uint16_t color565 = getNativeColor(colorEnd);
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = color565;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (colorEnd >> 24) & 0xFF;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint16_t r = (colorEnd >> 8) & RMASK;
|
||||
const uint16_t g = (colorEnd >> 5) & GMASK;
|
||||
const uint16_t b = (colorEnd >> 3) & BMASK;
|
||||
while (p < p_end)
|
||||
{
|
||||
*p = alphaBlend(r, g, b, *p, alphaTot);
|
||||
p++;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PainterRGB565LinearGradient::tearDown() const
|
||||
{
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB888.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB888::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* RESTRICT framebuffer = destination + offset * 3;
|
||||
paint::rgb888::lineFromColor(framebuffer, count, painterColor, alpha);
|
||||
}
|
||||
|
||||
void PainterRGB888::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/DMA.hpp>
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB888Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB888Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::RGB888 || bitmapFormat == Bitmap::ARGB8888) && "PainterRGB888Bitmap only works with RGB888 and ARGB8888 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
void PainterRGB888Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset * 3;
|
||||
const uint8_t* const lineEnd = framebuffer + count * 3;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (bitmapFormat == Bitmap::RGB888)
|
||||
{
|
||||
const uint8_t* const bitmapLineStart = reinterpret_cast<const uint8_t*>(bitmapData) + rowSkip * 3;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX * 3;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::rgb888::lineFromRGB888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += length * 3;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else // if (bitmapFormat == Bitmap::ARGB8888)
|
||||
{
|
||||
const uint32_t* const bitmapLineStart = reinterpret_cast<const uint32_t*>(bitmapData) + rowSkip;
|
||||
const uint32_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::rgb888::lineFromARGB8888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += 3 * length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterRGB888Bitmap::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB888L8Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB888L8Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::L8) && "PainterRGB888L8Bitmap only works with appropriate L8 bitmaps");
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
bitmapCLUT = bitmap.getExtraData();
|
||||
assert(bitmapData && bitmapCLUT && "The bitmap does not have any data or any CLUT");
|
||||
l8format = static_cast<Bitmap::ClutFormat>(*reinterpret_cast<const uint16_t*>(bitmapCLUT));
|
||||
assert((l8format == Bitmap::CLUT_FORMAT_L8_RGB565 || l8format == Bitmap::CLUT_FORMAT_L8_ARGB8888 || l8format == Bitmap::CLUT_FORMAT_L8_RGB888) && "The palette is not in the right format");
|
||||
bitmapCLUT += 4; // Skip header
|
||||
}
|
||||
}
|
||||
|
||||
void PainterRGB888L8Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset * 3;
|
||||
const uint8_t* const lineEnd = framebuffer + count * 3;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (l8format == Bitmap::CLUT_FORMAT_L8_RGB888)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::rgb888::lineFromL8RGB888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += 3 * length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else // Bitmap::CLUT_FORMAT_L8_ARGB8888
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
count -= length;
|
||||
paint::rgb888::lineFromL8ARGB8888(framebuffer, bitmapPointer, length, alpha);
|
||||
framebuffer += 3 * length;
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
|
||||
bool PainterRGB888L8Bitmap::setup(const Rect& widgetRect) const
|
||||
{
|
||||
if (!AbstractPainterRGB888::setup(widgetRect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
updateBitmapOffsets(widgetWidth);
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
paint::setL8Palette(bitmapCLUT);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PainterRGB888L8Bitmap::tearDown() const
|
||||
{
|
||||
paint::tearDown();
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/hal/Paint.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGB888LinearGradient.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGB888LinearGradient::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* p = destination + offset * 3;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
// The whole line is the same color
|
||||
uint32_t color;
|
||||
if (widgetY <= coord0)
|
||||
{
|
||||
color = texture[deltaColor > 0 ? 0 : 1023];
|
||||
}
|
||||
else if (widgetY >= coord1)
|
||||
{
|
||||
color = texture[deltaColor > 0 ? 1023 : 0];
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32_t colorOffset = (int)((widgetY - coord0) * deltaColor);
|
||||
color = texture[deltaColor > 0 ? colorOffset : 1023 + colorOffset];
|
||||
}
|
||||
|
||||
const uint8_t b = color & 0xFF;
|
||||
const uint8_t g = (color >> 8) & 0xFF;
|
||||
const uint8_t r = (color >> 16) & 0xFF;
|
||||
uint8_t* const p_end = p + count * 3;
|
||||
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = b;
|
||||
*p++ = g;
|
||||
*p++ = r;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (color >> 24) & 0xFF;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint8_t ialpha = 0xFF - alphaTot;
|
||||
while (p < p_end)
|
||||
{
|
||||
uint8_t pByte = *p;
|
||||
*p++ = LCD::div255(b * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(g * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(r * alphaTot + pByte * ialpha);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//vx0 is where first color line (y=clSlope*x+clOffset) intersects the horizontal line y=widgetY
|
||||
float vx0;
|
||||
if (isHorizontal)
|
||||
{
|
||||
vx0 = coord0;
|
||||
}
|
||||
else
|
||||
{
|
||||
vx0 = (widgetY - clOffset) / clSlope;
|
||||
if (deltaColor < 0.0f)
|
||||
{
|
||||
vx0 -= horizontalDistance;
|
||||
}
|
||||
}
|
||||
|
||||
int x = widgetX;
|
||||
|
||||
// Left of gradient
|
||||
// All pixels up to first gradient line, paint using fixed color
|
||||
if (x <= vx0)
|
||||
{
|
||||
// Starting before gradient
|
||||
const int pixels = (int)(MIN(vx0 + 1 - x, count));
|
||||
// Start in correct end of palette depending on direction
|
||||
const uint32_t colorStart = texture[deltaColor > 0 ? 0 : 1023];
|
||||
uint8_t* const p_end = p + pixels * 3;
|
||||
const uint8_t b = colorStart & 0xFF;
|
||||
const uint8_t g = (colorStart >> 8) & 0xFF;
|
||||
const uint8_t r = (colorStart >> 16) & 0xFF;
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = b;
|
||||
*p++ = g;
|
||||
*p++ = r;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (colorStart >> 24) & 0xFF;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint8_t ialpha = 0xFF - alphaTot;
|
||||
while (p < p_end)
|
||||
{
|
||||
uint8_t pByte = *p;
|
||||
*p++ = LCD::div255(b * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(g * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(r * alphaTot + pByte * ialpha);
|
||||
};
|
||||
}
|
||||
x += pixels;
|
||||
}
|
||||
|
||||
const int16_t right = widgetX + count;
|
||||
if (x < right) // More to draw
|
||||
{
|
||||
//vx1 is where y=widgetY intersects the last color line (fixed distance from vx0)
|
||||
const float vx1 = vx0 + horizontalDistance;
|
||||
|
||||
if (x < vx1) // Draw gradient part until last color line
|
||||
{
|
||||
const int endx = (int)(MIN(right, vx1));
|
||||
const uint8_t* const p_end = p + (endx - x) * 3;
|
||||
|
||||
// Start with first or last color depending on direction
|
||||
float colorF = deltaColor > 0 ? 0.0000f : 1023.9999f;
|
||||
colorF += (x - vx0) * deltaColor;
|
||||
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
const int colorIndex = (int)colorF;
|
||||
const uint8_t* color = (const uint8_t*)(texture + colorIndex);
|
||||
*p++ = *color++;
|
||||
*p++ = *color++;
|
||||
*p++ = *color;
|
||||
colorF += deltaColor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go to 8-bit pointers
|
||||
const uint8_t* const gradient = (const uint8_t*)texture;
|
||||
while (p < p_end)
|
||||
{
|
||||
const int colorIndex = (int)colorF;
|
||||
|
||||
const uint8_t* color = gradient + colorIndex * 4;
|
||||
const uint8_t a = color[3];
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint8_t ialpha = 0xFF - alphaTot;
|
||||
|
||||
uint8_t pByte = *p;
|
||||
*p++ = LCD::div255(*color++ * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(*color++ * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(*color++ * alphaTot + pByte * ialpha);
|
||||
|
||||
colorF += deltaColor;
|
||||
}
|
||||
}
|
||||
x = endx;
|
||||
}
|
||||
|
||||
if (x < right) // Second fixed color part
|
||||
{
|
||||
const uint8_t* const p_end = p + (right - x) * 3;
|
||||
const uint32_t colorEnd = texture[deltaColor > 0 ? 1023 : 0];
|
||||
const uint8_t b = colorEnd & 0xFF;
|
||||
const uint8_t g = (colorEnd >> 8) & 0xFF;
|
||||
const uint8_t r = (colorEnd >> 16) & 0xFF;
|
||||
if (isSolid && (alpha == 255))
|
||||
{
|
||||
while (p < p_end)
|
||||
{
|
||||
*p++ = b;
|
||||
*p++ = g;
|
||||
*p++ = r;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t a = (colorEnd >> 24) & 0xFF;
|
||||
const uint8_t alphaTot = LCD::div255(a * alpha);
|
||||
const uint8_t ialpha = 0xFF - alphaTot;
|
||||
while (p < p_end)
|
||||
{
|
||||
uint8_t pByte = *p;
|
||||
*p++ = LCD::div255(b * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(g * alphaTot + pByte * ialpha);
|
||||
pByte = *p;
|
||||
*p++ = LCD::div255(r * alphaTot + pByte * ialpha);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PainterRGB888LinearGradient::tearDown() const
|
||||
{
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/canvas/PainterRGBA2222.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGBA2222::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = color8;
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = alphaBlend(painterRed, painterGreen, painterBlue, *framebuffer, alpha);
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD8bpp_RGBA2222.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterRGBA2222Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterRGBA2222Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::RGBA2222) && "PainterRGBA2222Bitmap only works with RGBA2222 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
void PainterRGBA2222Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* RESTRICT framebuffer = destination + offset;
|
||||
const uint8_t* const lineEnd = framebuffer + count;
|
||||
const int rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint8_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t rgba = *bitmapPointer++;
|
||||
const uint8_t srcAlpha = (rgba & 0x03) * 0x55;
|
||||
const uint8_t a = LCD::div255(alpha * srcAlpha);
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = rgba;
|
||||
}
|
||||
else if (a)
|
||||
{
|
||||
*framebuffer = alphaBlend(rgba, *framebuffer, a);
|
||||
}
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterXRGB8888.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterXRGB8888::paint(uint8_t* destination, int16_t offset, int16_t /*widgetX*/, int16_t /*widgetY*/, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
uint32_t* RESTRICT framebuffer = reinterpret_cast<uint32_t*>(destination) + offset;
|
||||
const uint32_t* const lineEnd = framebuffer + count;
|
||||
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = painterColor;
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint8_t ialpha = 0xFF - alpha;
|
||||
const uint32_t rgbBg = *framebuffer;
|
||||
*framebuffer = (LCD::div255(painterBlue * alpha + (rgbBg & 0xFF) * ialpha)) |
|
||||
(LCD::div255(painterGreen * alpha + ((rgbBg >> 8) & 0xFF) * ialpha) << 8) |
|
||||
(LCD::div255(painterRed * alpha + ((rgbBg >> 16) & 0xFF) * ialpha) << 16);
|
||||
} while (++framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD32bpp_XRGB8888.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterXRGB8888Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterXRGB8888Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmapFormat == Bitmap::RGB565 || bitmapFormat == Bitmap::RGB888 || bitmapFormat == Bitmap::ARGB8888) && !bitmap.getExtraData() && "PainterXRGB8888Bitmap only works with RGB565 (without alpha), RGB888 and ARGB8888 bitmaps");
|
||||
assert(bitmap.getId() == BITMAP_INVALID || bitmapData);
|
||||
}
|
||||
|
||||
void PainterXRGB8888Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t* RESTRICT framebuffer = reinterpret_cast<uint32_t*>(destination) + offset;
|
||||
const uint32_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (bitmapFormat == Bitmap::ARGB8888)
|
||||
{
|
||||
const uint32_t* const bitmapLineStart = reinterpret_cast<const uint32_t*>(bitmapData) + rowSkip;
|
||||
const uint32_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t srcAlpha = (*bitmapPointer) >> 24;
|
||||
const uint8_t a = LCD::div255(alpha * srcAlpha);
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = *bitmapPointer;
|
||||
}
|
||||
else
|
||||
{
|
||||
*framebuffer = LCD32bpp_XRGB8888::blendRgb888withXrgb8888(reinterpret_cast<const uint8_t*>(bitmapPointer), *framebuffer, a, 0xFF - a);
|
||||
}
|
||||
bitmapPointer++;
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else if (bitmapFormat == Bitmap::RGB888)
|
||||
{
|
||||
const uint8_t* const bitmapLineStart = reinterpret_cast<const uint8_t*>(bitmapData) + rowSkip * 3;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX * 3;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = LCD32bpp_XRGB8888::rgb888toXrgb8888(bitmapPointer);
|
||||
bitmapPointer += 3;
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = LCD32bpp_XRGB8888::blendRgb888withXrgb8888(bitmapPointer, *framebuffer, alpha, 0xFF - alpha);
|
||||
bitmapPointer += 3;
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else // if (bitmapFormat == Bitmap::RGB565)
|
||||
{
|
||||
const uint16_t* const bitmapLineStart = reinterpret_cast<const uint16_t*>(bitmapData) + (widgetY * bitmapRect.width);
|
||||
const uint16_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = Color::rgb565toXrgb8888(*bitmapPointer++);
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
*framebuffer = LCD32bpp_XRGB8888::blendRgb565withXrgb8888(*bitmapPointer++, *framebuffer, alpha, 0xFF - alpha);
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <platform/driver/lcd/LCD32bpp_XRGB8888.hpp>
|
||||
#include <touchgfx/Color.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/transforms/DisplayTransformation.hpp>
|
||||
#include <touchgfx/widgets/canvas/PainterXRGB8888L8Bitmap.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
void PainterXRGB8888L8Bitmap::setBitmap(const Bitmap& bmp)
|
||||
{
|
||||
AbstractPainterBitmap::setBitmap(bmp);
|
||||
assert((bitmap.getId() == BITMAP_INVALID || bitmap.getFormat() == Bitmap::L8) && "PainterXRGB8888L8Bitmap only works with L8 bitmaps");
|
||||
if (bitmap.getId() != BITMAP_INVALID)
|
||||
{
|
||||
bitmapCLUT = bitmap.getExtraData();
|
||||
assert(bitmapData && bitmapCLUT && "The bitmap does not have any data or any CLUT");
|
||||
l8format = static_cast<Bitmap::ClutFormat>(*reinterpret_cast<const uint16_t*>(bitmapCLUT));
|
||||
assert((l8format == Bitmap::CLUT_FORMAT_L8_RGB565 || l8format == Bitmap::CLUT_FORMAT_L8_ARGB8888 || l8format == Bitmap::CLUT_FORMAT_L8_RGB888) && "The palette is not in the right format");
|
||||
bitmapCLUT += 4; // Skip header
|
||||
}
|
||||
}
|
||||
|
||||
void PainterXRGB8888L8Bitmap::paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
|
||||
{
|
||||
if (!adjustBitmapXY(widgetX, widgetY, offset, count))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t* RESTRICT framebuffer = reinterpret_cast<uint32_t*>(destination) + offset;
|
||||
const uint32_t* const lineEnd = framebuffer + count;
|
||||
const int32_t rowSkip = widgetY * bitmapRect.width;
|
||||
const uint8_t* const bitmapLineStart = bitmapData + rowSkip;
|
||||
const uint8_t* bitmapPointer = bitmapLineStart + widgetX;
|
||||
int16_t bitmapAvailable = bitmapRect.width - widgetX;
|
||||
if (l8format == Bitmap::CLUT_FORMAT_L8_RGB565)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint16_t srcpix = reinterpret_cast<const uint16_t*>(bitmapCLUT)[*bitmapPointer++];
|
||||
*framebuffer = Color::rgb565toXrgb8888(srcpix);
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const uint16_t srcpix = reinterpret_cast<const uint16_t*>(bitmapCLUT)[*bitmapPointer++];
|
||||
*framebuffer = LCD32bpp_XRGB8888::blendRgb565withXrgb8888(srcpix, *framebuffer, alpha, 0xFF - alpha);
|
||||
} while (++framebuffer < chunkend);
|
||||
}
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else if (l8format == Bitmap::CLUT_FORMAT_L8_RGB888)
|
||||
{
|
||||
if (alpha == 0xFF)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t* src = bitmapCLUT + *bitmapPointer++ * 3;
|
||||
*framebuffer = LCD32bpp_XRGB8888::rgb888toXrgb8888(src);
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint8_t* src = bitmapCLUT + *bitmapPointer++ * 3;
|
||||
*framebuffer = LCD32bpp_XRGB8888::blendRgb888withXrgb8888(src, *framebuffer, alpha, 0xFF - alpha);
|
||||
} while (++framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
else // if (l8format == Bitmap::CLUT_FORMAT_L8_ARGB8888)
|
||||
{
|
||||
do
|
||||
{
|
||||
const int16_t length = MIN(bitmapAvailable, count);
|
||||
const uint32_t* const chunkend = framebuffer + length;
|
||||
count -= length;
|
||||
do
|
||||
{
|
||||
const uint32_t src = reinterpret_cast<const uint32_t*>(bitmapCLUT)[*bitmapPointer++];
|
||||
const uint8_t srcAlpha = src >> 24;
|
||||
const uint8_t a = LCD::div255(alpha * srcAlpha);
|
||||
if (a == 0xFF)
|
||||
{
|
||||
*framebuffer = src;
|
||||
}
|
||||
else
|
||||
{
|
||||
*framebuffer = LCD32bpp_XRGB8888::blendXrgb888withXrgb8888(src, *framebuffer, a, 0xFF - a);
|
||||
}
|
||||
framebuffer++;
|
||||
} while (framebuffer < chunkend);
|
||||
bitmapPointer = bitmapLineStart;
|
||||
bitmapAvailable = bitmapRect.width;
|
||||
} while (framebuffer < lineEnd);
|
||||
}
|
||||
}
|
||||
} // namespace touchgfx
|
||||
+600
@@ -0,0 +1,600 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Callback.hpp>
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/widgets/graph/AbstractDataGraph.hpp>
|
||||
#include <touchgfx/widgets/graph/GraphElements.hpp>
|
||||
#include <touchgfx/widgets/graph/GraphLabels.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
AbstractDataGraph::AbstractDataGraph(int16_t capacity)
|
||||
: Container(),
|
||||
alpha(255), xScale(1), yScale(1),
|
||||
graphArea(), leftArea(), rightArea(), topArea(), bottomArea(),
|
||||
topPadding(0), leftPadding(0), rightPadding(0), bottomPadding(0),
|
||||
maxCapacity(capacity), usedCapacity(0), gapBeforeIndex(0), clickAction(0), dragAction(0),
|
||||
graphRangeMinX(0), graphRangeMaxX(0), graphRangeMinY(0), graphRangeMaxY(0)
|
||||
{
|
||||
assert(capacity > 0);
|
||||
AbstractDataGraph::add(graphArea);
|
||||
AbstractDataGraph::add(topArea);
|
||||
AbstractDataGraph::add(leftArea);
|
||||
AbstractDataGraph::add(rightArea);
|
||||
AbstractDataGraph::add(bottomArea);
|
||||
// Place all areas properly:
|
||||
setGraphAreaMargin(0, 0, 0, 0);
|
||||
setTouchable(true);
|
||||
}
|
||||
|
||||
void AbstractDataGraph::setWidth(int16_t width)
|
||||
{
|
||||
topArea.setWidth(width);
|
||||
bottomArea.setWidth(width);
|
||||
rightArea.setX(width - rightArea.getWidth());
|
||||
graphArea.setWidth(width - (leftArea.getWidth() + rightArea.getWidth()));
|
||||
Container::setWidth(width);
|
||||
updateAreasPosition();
|
||||
}
|
||||
|
||||
void AbstractDataGraph::setHeight(int16_t height)
|
||||
{
|
||||
leftArea.setHeight(height);
|
||||
rightArea.setHeight(height);
|
||||
bottomArea.setY(height - bottomArea.getHeight());
|
||||
graphArea.setHeight(height - (topArea.getHeight() + bottomArea.getHeight()));
|
||||
Container::setHeight(height);
|
||||
updateAreasPosition();
|
||||
}
|
||||
|
||||
void AbstractDataGraph::setGraphAreaMargin(int16_t top, int16_t left, int16_t right, int16_t bottom)
|
||||
{
|
||||
graphArea.setPosition(left, top, getWidth() - (left + right), getHeight() - (top + bottom));
|
||||
topArea.setPosition(0, 0, getWidth(), top);
|
||||
leftArea.setPosition(0, 0, left, getHeight());
|
||||
rightArea.setPosition(getWidth() - right, 0, right, getHeight());
|
||||
bottomArea.setPosition(0, getHeight() - bottom, getWidth(), bottom);
|
||||
updateAreasPosition();
|
||||
}
|
||||
|
||||
void AbstractDataGraph::setGraphAreaPadding(int16_t top, int16_t left, int16_t right, int16_t bottom)
|
||||
{
|
||||
topPadding = top;
|
||||
leftPadding = left;
|
||||
rightPadding = right;
|
||||
bottomPadding = bottom;
|
||||
}
|
||||
|
||||
void AbstractDataGraph::clear()
|
||||
{
|
||||
usedCapacity = 0;
|
||||
invalidateGraphArea();
|
||||
}
|
||||
|
||||
void AbstractDataGraph::addGraphElement(AbstractGraphElement& d)
|
||||
{
|
||||
graphArea.add(d);
|
||||
d.setPosition(0, 0, graphArea.getWidth(), graphArea.getHeight());
|
||||
}
|
||||
|
||||
void AbstractDataGraph::addTopElement(AbstractGraphDecoration& d)
|
||||
{
|
||||
topArea.add(d);
|
||||
d.setPosition(0, 0, topArea.getWidth(), topArea.getHeight());
|
||||
}
|
||||
|
||||
void AbstractDataGraph::addLeftElement(AbstractGraphDecoration& d)
|
||||
{
|
||||
leftArea.add(d);
|
||||
d.setPosition(0, 0, leftArea.getWidth(), leftArea.getHeight());
|
||||
}
|
||||
|
||||
void AbstractDataGraph::addRightElement(AbstractGraphDecoration& d)
|
||||
{
|
||||
rightArea.add(d);
|
||||
d.setPosition(0, 0, rightArea.getWidth(), rightArea.getHeight());
|
||||
}
|
||||
|
||||
void AbstractDataGraph::addBottomElement(AbstractGraphDecoration& d)
|
||||
{
|
||||
bottomArea.add(d);
|
||||
d.setPosition(0, 0, bottomArea.getWidth(), bottomArea.getHeight());
|
||||
}
|
||||
|
||||
bool AbstractDataGraph::getNearestIndexForScreenXY(int16_t x, int16_t y, int16_t& index)
|
||||
{
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint32_t bestDist = 0xFFFFFFFF;
|
||||
for (int16_t ix = 0; ix < usedCapacity; ix++)
|
||||
{
|
||||
const int16_t xDist = indexToScreenX(ix) - x;
|
||||
const int16_t yDist = indexToScreenY(ix) - y;
|
||||
const uint32_t dist = xDist * xDist + yDist * yDist;
|
||||
if (dist < bestDist)
|
||||
{
|
||||
index = ix;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AbstractDataGraph::getNearestIndexForScreenX(int16_t x, int16_t& index) const
|
||||
{
|
||||
index = 0;
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uint32_t bestDist = abs(indexToScreenX(index) - x);
|
||||
for (int16_t ix = 1; ix < usedCapacity; ix++)
|
||||
{
|
||||
const uint32_t dist = abs(indexToScreenX(ix) - x);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
index = ix;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbstractDataGraph::handleClickEvent(const ClickEvent& event)
|
||||
{
|
||||
if (event.getType() == ClickEvent::CANCEL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int16_t x = event.getX() - graphArea.getX();
|
||||
int16_t index;
|
||||
if (getNearestIndexForScreenX(x, index))
|
||||
{
|
||||
if (event.getType() == ClickEvent::PRESSED || event.getType() == ClickEvent::RELEASED)
|
||||
{
|
||||
if (clickAction && clickAction->isValid())
|
||||
{
|
||||
const GraphClickEvent graphClickEvent(index, event);
|
||||
clickAction->execute(*this, graphClickEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractDataGraph::handleDragEvent(const DragEvent& event)
|
||||
{
|
||||
const int16_t x = event.getNewX() - graphArea.getX();
|
||||
int16_t index;
|
||||
if (getNearestIndexForScreenX(x, index))
|
||||
{
|
||||
if (event.getType() == DragEvent::DRAGGED)
|
||||
{
|
||||
if (dragAction && dragAction->isValid())
|
||||
{
|
||||
const GraphDragEvent graphDragEvent(index, event);
|
||||
dragAction->execute(*this, graphDragEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractDataGraph::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
if (index < usedCapacity)
|
||||
{
|
||||
AbstractGraphElement* d = (AbstractGraphElement*)graphArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->invalidateGraphPointAt(index);
|
||||
d = (AbstractGraphElement*)d->getNextSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractDataGraph::invalidateXAxisPointAt(int16_t index)
|
||||
{
|
||||
AbstractGraphElement* d = (AbstractGraphElement*)topArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->invalidateGraphPointAt(index);
|
||||
d = (AbstractGraphElement*)d->getNextSibling();
|
||||
}
|
||||
d = (AbstractGraphElement*)bottomArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->invalidateGraphPointAt(index);
|
||||
d = (AbstractGraphElement*)d->getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractDataGraph::invalidateAllXAxisPoints()
|
||||
{
|
||||
for (int index = 0; index <= usedCapacity; index++)
|
||||
{
|
||||
invalidateXAxisPointAt(index);
|
||||
}
|
||||
topArea.invalidate();
|
||||
bottomArea.invalidate();
|
||||
}
|
||||
|
||||
void AbstractDataGraph::updateAreasPosition()
|
||||
{
|
||||
Drawable* d = graphArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->setPosition(0, 0, graphArea.getWidth(), graphArea.getHeight());
|
||||
d = d->getNextSibling();
|
||||
}
|
||||
|
||||
d = topArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->setPosition(0, 0, topArea.getWidth(), topArea.getHeight());
|
||||
d = d->getNextSibling();
|
||||
}
|
||||
|
||||
d = leftArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->setPosition(0, 0, leftArea.getWidth(), leftArea.getHeight());
|
||||
d = d->getNextSibling();
|
||||
}
|
||||
|
||||
d = rightArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->setPosition(0, 0, rightArea.getWidth(), rightArea.getHeight());
|
||||
d = d->getNextSibling();
|
||||
}
|
||||
|
||||
d = bottomArea.getFirstChild();
|
||||
while (d)
|
||||
{
|
||||
d->setPosition(0, 0, bottomArea.getWidth(), bottomArea.getHeight());
|
||||
d = d->getNextSibling();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void AbstractDataGraph::setGraphRangeXScaled(int min, int max)
|
||||
{
|
||||
assert(min != max);
|
||||
if (max < min)
|
||||
{
|
||||
const int tmp = min;
|
||||
min = max;
|
||||
max = tmp;
|
||||
}
|
||||
if (min != graphRangeMinX || max != graphRangeMaxX)
|
||||
{
|
||||
graphRangeMinX = min;
|
||||
graphRangeMaxX = max;
|
||||
topArea.invalidate();
|
||||
graphArea.invalidate();
|
||||
bottomArea.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractDataGraph::setGraphRangeYScaled(int min, int max)
|
||||
{
|
||||
assert(min != max);
|
||||
if (max < min)
|
||||
{
|
||||
const int tmp = min;
|
||||
min = max;
|
||||
max = tmp;
|
||||
}
|
||||
if (min != graphRangeMinY || max != graphRangeMaxY)
|
||||
{
|
||||
graphRangeMinY = min;
|
||||
graphRangeMaxY = max;
|
||||
leftArea.invalidate();
|
||||
graphArea.invalidate();
|
||||
rightArea.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
int AbstractDataGraph::convertToNewScale(int value, int oldScale, int newScale)
|
||||
{
|
||||
if (oldScale == newScale)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return muldiv(value, newScale, oldScale);
|
||||
}
|
||||
|
||||
int16_t DynamicDataGraph::addDataPointScaled(int y)
|
||||
{
|
||||
beforeAddValue();
|
||||
dataCounter++;
|
||||
return addValue(y);
|
||||
}
|
||||
|
||||
void DynamicDataGraph::setGraphRangeYAutoScaled(bool showXaxis, int margin)
|
||||
{
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int16_t indexMin = (int16_t)getGraphRangeXMin();
|
||||
int16_t indexMax = (int16_t)getGraphRangeXMax();
|
||||
if (indexMin > indexMax)
|
||||
{
|
||||
const int tmp = indexMin;
|
||||
indexMin = indexMax;
|
||||
indexMax = tmp;
|
||||
}
|
||||
indexMin = MAX(indexMin, 0);
|
||||
indexMax = MIN(indexMax, usedCapacity);
|
||||
if (indexMin < usedCapacity && indexMax >= 0)
|
||||
{
|
||||
int yMin = showXaxis ? margin : yValues[indexMin];
|
||||
int yMax = showXaxis ? -margin : yValues[indexMin];
|
||||
for (int i = indexMin; i < indexMax; i++)
|
||||
{
|
||||
const int y = yValues[i];
|
||||
if (yMin > y)
|
||||
{
|
||||
yMin = y;
|
||||
}
|
||||
if (yMax < y)
|
||||
{
|
||||
yMax = y;
|
||||
}
|
||||
}
|
||||
yMin -= margin;
|
||||
yMax += margin;
|
||||
if (yMin != yMax)
|
||||
{
|
||||
setGraphRangeYScaled(yMin, yMax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicDataGraph::xScreenRangeToIndexRange(int16_t xMin, int16_t xMax, int16_t& indexMin, int16_t& indexMax) const
|
||||
{
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
indexMin = indexMax = -1;
|
||||
return false;
|
||||
}
|
||||
if (getGraphAreaWidth() <= 1)
|
||||
{
|
||||
indexMin = 0;
|
||||
indexMax = usedCapacity - 1;
|
||||
return true;
|
||||
}
|
||||
CWRUtil::Q5 xQ5Min = CWRUtil::muldivQ5(CWRUtil::toQ5(xMin - leftPadding), CWRUtil::Q5(scaled2intX(graphRangeMaxX - graphRangeMinX)), CWRUtil::Q5(getGraphAreaWidth() - 1)) + CWRUtil::toQ5(scaled2intX(graphRangeMinX));
|
||||
CWRUtil::Q5 xQ5Max = CWRUtil::muldivQ5(CWRUtil::toQ5(xMax - leftPadding), CWRUtil::Q5(scaled2intX(graphRangeMaxX - graphRangeMinX)), CWRUtil::Q5(getGraphAreaWidth() - 1)) + CWRUtil::toQ5(scaled2intX(graphRangeMinX));
|
||||
if (xQ5Min > xQ5Max)
|
||||
{
|
||||
const CWRUtil::Q5 tmp = xQ5Min;
|
||||
xQ5Min = xQ5Max;
|
||||
xQ5Max = tmp;
|
||||
}
|
||||
indexMin = xQ5Min.to<int16_t>(); // X is also index in AbstractDataGraphWithY
|
||||
indexMax = xQ5Max.ceil();
|
||||
if (indexMax < 0)
|
||||
{
|
||||
indexMin = indexMax = 0;
|
||||
return false;
|
||||
}
|
||||
if (indexMin > usedCapacity - 1)
|
||||
{
|
||||
indexMin = indexMax = usedCapacity - 1;
|
||||
return false;
|
||||
}
|
||||
indexMin = MAX(0, indexMin);
|
||||
indexMax = MIN(usedCapacity - 1, indexMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DynamicDataGraph::setScaleX(int scale, bool updateData /*= false*/)
|
||||
{
|
||||
const int oldScale = getScaleX();
|
||||
if (scale != oldScale)
|
||||
{
|
||||
setGraphRangeXScaled(convertToNewScale(graphRangeMinX, oldScale, scale), convertToNewScale(graphRangeMaxX, oldScale, scale));
|
||||
xAxisFactor = convertToNewScale(xAxisFactor, oldScale, scale);
|
||||
xAxisOffset = convertToNewScale(xAxisOffset, oldScale, scale);
|
||||
AbstractDataGraph::setScaleX(scale, updateData);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicDataGraph::setScaleY(int scale, bool updateData /*= false*/)
|
||||
{
|
||||
if (updateData)
|
||||
{
|
||||
const int oldScale = getScaleY();
|
||||
for (int16_t index = 0; index < usedCapacity; index++)
|
||||
{
|
||||
yValues[index] = convertToNewScale(yValues[index], oldScale, scale);
|
||||
}
|
||||
}
|
||||
AbstractDataGraph::setScaleY(scale);
|
||||
}
|
||||
|
||||
void StaticDataGraph::setScaleX(int scale, bool updateData /*= false*/)
|
||||
{
|
||||
if (updateData)
|
||||
{
|
||||
const int oldScale = getScaleX();
|
||||
for (int16_t index = 0; index < usedCapacity; index++)
|
||||
{
|
||||
xValues[index] = convertToNewScale(xValues[index], oldScale, scale);
|
||||
}
|
||||
}
|
||||
AbstractDataGraph::setScaleX(scale);
|
||||
}
|
||||
|
||||
void StaticDataGraph::setScaleY(int scale, bool updateData /*= false*/)
|
||||
{
|
||||
if (updateData)
|
||||
{
|
||||
const int oldScale = getScaleY();
|
||||
for (int16_t index = 0; index < usedCapacity; index++)
|
||||
{
|
||||
yValues[index] = convertToNewScale(yValues[index], oldScale, scale);
|
||||
}
|
||||
}
|
||||
AbstractDataGraph::setScaleY(scale);
|
||||
}
|
||||
|
||||
void StaticDataGraph::setGraphRangeXAutoScaled(bool showYaxis, int margin)
|
||||
{
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int xMin = xValues[0]; // First x value is the lowest
|
||||
int xMax = xValues[usedCapacity - 1]; // Last x value is the highest
|
||||
if (showYaxis)
|
||||
{
|
||||
// The y axis (x=0) must be shown, so make sure 0 is included after subtracting/adding the margin
|
||||
xMin = MIN(xMin, margin);
|
||||
xMax = MAX(xMax, -margin);
|
||||
}
|
||||
xMin -= margin;
|
||||
xMax += margin;
|
||||
if (xMin != xMax)
|
||||
{
|
||||
setGraphRangeXScaled(xMin, xMax);
|
||||
}
|
||||
}
|
||||
|
||||
void StaticDataGraph::setGraphRangeYAutoScaled(bool showXaxis /*= true*/, int margin /*= 0*/)
|
||||
{
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int xValueMin = getGraphRangeXMinScaled(); // Get shown x range
|
||||
int xValueMax = getGraphRangeXMaxScaled();
|
||||
if (xValueMin > xValueMax)
|
||||
{
|
||||
// Swap so min<=max.
|
||||
const int16_t tmp = xValueMin;
|
||||
xValueMin = xValueMax;
|
||||
xValueMax = tmp;
|
||||
}
|
||||
int index = 0;
|
||||
while (xValues[index] < xValueMin && index < usedCapacity)
|
||||
{
|
||||
// Skip all indices that have x outside visible area
|
||||
index++;
|
||||
}
|
||||
if (xValues[index] > xValueMax)
|
||||
{
|
||||
// No x values are in the visible area
|
||||
return;
|
||||
}
|
||||
int yMin = yValues[index++]; // The index'th value must be visible
|
||||
int yMax = yMin;
|
||||
// Go through all points where x is in the visible range
|
||||
while (xValues[index] < xValueMax && index < usedCapacity)
|
||||
{
|
||||
const int y = yValues[index++];
|
||||
yMin = MIN(yMin, y);
|
||||
yMax = MAX(yMax, y);
|
||||
}
|
||||
if (showXaxis)
|
||||
{
|
||||
// The x axis (y=0) must be shown, so make sure 0 is included after subtracting/adding the margin
|
||||
yMin = MIN(yMin, margin);
|
||||
yMax = MAX(yMax, -margin);
|
||||
}
|
||||
yMin -= margin;
|
||||
yMax += margin;
|
||||
if (yMin != yMax)
|
||||
{
|
||||
setGraphRangeYScaled(yMin, yMax);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t StaticDataGraph::deleteValue(int xvalue)
|
||||
{
|
||||
for (int index = 0; index < usedCapacity; index++)
|
||||
{
|
||||
if (xValues[index] == xvalue)
|
||||
{
|
||||
return deleteIndex(index);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int16_t StaticDataGraph::deleteIndex(int index)
|
||||
{
|
||||
assert(index >= 0 && index < usedCapacity);
|
||||
invalidateGraphPointAt(index);
|
||||
usedCapacity--;
|
||||
for (int i = index; i < usedCapacity; i++)
|
||||
{
|
||||
xValues[i] = xValues[i + 1];
|
||||
yValues[i] = yValues[i + 1];
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
bool StaticDataGraph::xScreenRangeToIndexRange(int16_t xMin, int16_t xMax, int16_t& indexMin, int16_t& indexMax) const
|
||||
{
|
||||
if (usedCapacity == 0)
|
||||
{
|
||||
indexMin = indexMax = -1;
|
||||
return false;
|
||||
}
|
||||
if (getGraphAreaWidth() <= 1)
|
||||
{
|
||||
indexMin = 0;
|
||||
indexMax = usedCapacity - 1;
|
||||
return true;
|
||||
}
|
||||
CWRUtil::Q5 xQ5Min = CWRUtil::muldivQ5(CWRUtil::toQ5(xMin - leftPadding), CWRUtil::Q5(graphRangeMaxX - graphRangeMinX), CWRUtil::Q5(getGraphAreaWidth() - 1)) + CWRUtil::toQ5(graphRangeMinX);
|
||||
CWRUtil::Q5 xQ5Max = CWRUtil::muldivQ5(CWRUtil::toQ5(xMax - leftPadding), CWRUtil::Q5(graphRangeMaxX - graphRangeMinX), CWRUtil::Q5(getGraphAreaWidth() - 1)) + CWRUtil::toQ5(graphRangeMinX);
|
||||
if (xQ5Min > xQ5Max)
|
||||
{
|
||||
const CWRUtil::Q5 tmp = xQ5Min;
|
||||
xQ5Min = xQ5Max;
|
||||
xQ5Max = tmp;
|
||||
}
|
||||
const int xValueMin = xQ5Min.to<int>(); // These are REAL x values
|
||||
const int xValueMax = xQ5Max.ceil();
|
||||
indexMin = -1;
|
||||
while (indexMin < usedCapacity - 1 && xValues[indexMin + 1] <= xValueMin)
|
||||
{
|
||||
indexMin++;
|
||||
}
|
||||
indexMax = usedCapacity;
|
||||
while (indexMax > 0 && xValues[indexMax - 1] >= xValueMax)
|
||||
{
|
||||
indexMax--;
|
||||
}
|
||||
if (indexMax <= 0)
|
||||
{
|
||||
indexMin = indexMax = 0;
|
||||
return false;
|
||||
}
|
||||
if (indexMin >= usedCapacity - 1)
|
||||
{
|
||||
indexMin = indexMax = usedCapacity - 1;
|
||||
return false;
|
||||
}
|
||||
indexMin = MAX(0, indexMin);
|
||||
indexMax = MIN(usedCapacity - 1, indexMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,58 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/graph/Graph.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
int16_t GraphData::addValue(int xvalue, int yvalue)
|
||||
{
|
||||
int16_t index = 0;
|
||||
if (usedCapacity == 0 || xValues[usedCapacity - 1] < xvalue)
|
||||
{
|
||||
index = usedCapacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (index < usedCapacity && xValues[index] < xvalue)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
if (index < usedCapacity && xValues[index] == xvalue) // Pointing to an element to replace
|
||||
{
|
||||
// Replace point
|
||||
invalidateGraphPointAt(index);
|
||||
yValues[index] = yvalue;
|
||||
invalidateGraphPointAt(index);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
if ((index == usedCapacity || xValues[index] > xvalue) && usedCapacity == maxCapacity)
|
||||
{
|
||||
// Should add at end OR insert in the middle, but no room for more data
|
||||
return -1;
|
||||
}
|
||||
// Insert at 'index', move following points up the array
|
||||
for (int16_t i = usedCapacity; i > index; i--)
|
||||
{
|
||||
xValues[i] = xValues[i - 1];
|
||||
yValues[i] = yValues[i - 1];
|
||||
}
|
||||
usedCapacity++;
|
||||
xValues[index] = xvalue;
|
||||
yValues[index] = yvalue;
|
||||
invalidateGraphPointAt(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+656
@@ -0,0 +1,656 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/widgets/graph/GraphElements.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
Rect AbstractGraphElement::rectFromQ5Coordinates(CWRUtil::Q5 screenXminQ5, CWRUtil::Q5 screenYminQ5, CWRUtil::Q5 screenXmaxQ5, CWRUtil::Q5 screenYmaxQ5) const
|
||||
{
|
||||
if (screenXminQ5 > screenXmaxQ5)
|
||||
{
|
||||
const CWRUtil::Q5 tmp = screenXminQ5;
|
||||
screenXminQ5 = screenXmaxQ5;
|
||||
screenXmaxQ5 = tmp;
|
||||
}
|
||||
if (screenYminQ5 > screenYmaxQ5)
|
||||
{
|
||||
const CWRUtil::Q5 tmp = screenYminQ5;
|
||||
screenYminQ5 = screenYmaxQ5;
|
||||
screenYmaxQ5 = tmp;
|
||||
}
|
||||
const int16_t xMin = screenXminQ5.to<int>();
|
||||
const int16_t yMin = screenYminQ5.to<int>();
|
||||
const int16_t xMax = screenXmaxQ5.ceil();
|
||||
const int16_t yMax = screenYmaxQ5.ceil();
|
||||
return Rect(xMin, yMin, xMax - xMin, yMax - yMin);
|
||||
}
|
||||
|
||||
Rect AbstractGraphElement::rectAround(CWRUtil::Q5 xQ5, CWRUtil::Q5 yQ5, CWRUtil::Q5 diameterQ5) const
|
||||
{
|
||||
const CWRUtil::Q5 d2Q5 = CWRUtil::Q5((int32_t)diameterQ5 + 1) / 2;
|
||||
return rectFromQ5Coordinates(xQ5 - d2Q5, yQ5 - d2Q5, xQ5 + d2Q5, yQ5 + d2Q5);
|
||||
}
|
||||
|
||||
bool AbstractGraphElement::isCenterInvisible(const AbstractDataGraph* graph, int16_t index) const
|
||||
{
|
||||
const int16_t screenXCenter = indexToScreenXQ5(graph, index).round();
|
||||
const int16_t screenYCenter = indexToScreenYQ5(graph, index).round();
|
||||
return screenXCenter < graph->getGraphAreaPaddingLeft() ||
|
||||
screenXCenter >= graph->getGraphAreaPaddingLeft() + graph->getGraphAreaWidth() ||
|
||||
screenYCenter < graph->getGraphAreaPaddingTop() ||
|
||||
screenYCenter >= graph->getGraphAreaPaddingTop() + graph->getGraphAreaHeight();
|
||||
}
|
||||
|
||||
void AbstractGraphElementNoCWR::normalizeRect(Rect& r) const
|
||||
{
|
||||
if (r.width < 0)
|
||||
{
|
||||
r.x += r.width;
|
||||
r.width = -r.width;
|
||||
}
|
||||
if (r.height < 0)
|
||||
{
|
||||
r.y += r.height;
|
||||
r.height = -r.height;
|
||||
}
|
||||
}
|
||||
|
||||
void GraphElementGridBase::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const uint8_t a = LCD::div255(getAlpha() * graph->getAlpha());
|
||||
if (lineWidth == 0 || a == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const Rect invalidRect = enclosingArea(graph) & invalidatedArea;
|
||||
|
||||
const int minorInterval = getCorrectlyScaledGridInterval(graph);
|
||||
const int majorInterval = getCorrectlyScaledMajorInterval(graph);
|
||||
|
||||
const CWRUtil::Q5 lw2Q5minor = CWRUtil::toQ5(lineWidth) / 2;
|
||||
const int16_t start = getGraphAreaStart(graph);
|
||||
const int16_t length = getGraphAreaLength(graph);
|
||||
if (minorInterval == 0 && majorInterval == 0)
|
||||
{
|
||||
const int16_t pos = (valueToScreenQ5(graph, 0) - lw2Q5minor).round();
|
||||
const int16_t graphPos = pos - getGraphAreaStartPos(graph);
|
||||
if (graphPos + lineWidth >= 0 && graphPos < getGraphAreaEndPos(graph))
|
||||
{
|
||||
drawLine(invalidRect, start, length, pos, lineWidth, a);
|
||||
}
|
||||
}
|
||||
else if (minorInterval > 0)
|
||||
{
|
||||
int rangeMin = getGraphRangeMinScaled(graph);
|
||||
int rangeMax = getGraphRangeMaxScaled(graph);
|
||||
if (abs(rangeMax - rangeMin) / minorInterval > 500)
|
||||
{
|
||||
return; // Too many grid lines
|
||||
}
|
||||
|
||||
if (valueToScreenQ5(graph, rangeMin) > valueToScreenQ5(graph, rangeMax))
|
||||
{
|
||||
const int tmp = rangeMin;
|
||||
rangeMin = rangeMax;
|
||||
rangeMax = tmp;
|
||||
}
|
||||
// Now rangeMax has a higher screen coordinate than rangeMin
|
||||
|
||||
const int margin = rangeMin < rangeMax ? +1 : -1;
|
||||
const int minorLo = (rangeMin / minorInterval) - margin;
|
||||
const int minorHi = (rangeMax / minorInterval) + margin;
|
||||
const int minorStep = minorLo < minorHi ? +1 : -1;
|
||||
if (majorInterval == 0)
|
||||
{
|
||||
// No major lines, simply draw all lines
|
||||
for (int minorIndex = minorLo; minorIndex != minorHi + minorStep; minorIndex += minorStep)
|
||||
{
|
||||
const int16_t pos = (valueToScreenQ5(graph, minorInterval * minorIndex) - lw2Q5minor).round();
|
||||
const int16_t graphPos = pos - getGraphAreaStartPos(graph);
|
||||
if (graphPos + lineWidth >= 0 && graphPos < getGraphAreaEndPos(graph))
|
||||
{
|
||||
drawLine(invalidRect, start, length, pos, lineWidth, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int majorLo = (rangeMin / majorInterval) - margin;
|
||||
const int majorHi = (rangeMax / majorInterval) + margin;
|
||||
const int majorStep = majorLo < majorHi ? +1 : -1;
|
||||
int majorIndex = majorLo;
|
||||
int16_t majorCoord = valueToScreenQ5(graph, majorInterval * majorIndex).round();
|
||||
int minorIndex = minorLo;
|
||||
int16_t minorCoord = valueToScreenQ5(graph, minorInterval * minorIndex).round();
|
||||
for (;;)
|
||||
{
|
||||
// Draw minor lines up to the major line
|
||||
while (minorCoord < majorCoord)
|
||||
{
|
||||
const int16_t pos = (valueToScreenQ5(graph, minorInterval * minorIndex) - lw2Q5minor).round();
|
||||
const int16_t graphPos = pos - getGraphAreaStartPos(graph);
|
||||
if (graphPos + lineWidth >= 0 && graphPos < getGraphAreaEndPos(graph))
|
||||
{
|
||||
drawLine(invalidRect, start, length, pos, lineWidth, a);
|
||||
}
|
||||
minorIndex += minorStep;
|
||||
minorCoord = valueToScreenQ5(graph, minorInterval * minorIndex).round();
|
||||
}
|
||||
// Advance minor past the major line we are about to draw
|
||||
while (minorCoord <= majorCoord)
|
||||
{
|
||||
minorIndex += minorStep;
|
||||
minorCoord = valueToScreenQ5(graph, minorInterval * minorIndex).round();
|
||||
}
|
||||
if (majorCoord <= minorCoord)
|
||||
{
|
||||
majorIndex += majorStep;
|
||||
if (majorIndex == majorHi + majorStep)
|
||||
{
|
||||
break;
|
||||
}
|
||||
majorCoord = valueToScreenQ5(graph, majorInterval * majorIndex).round();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphElementGridBase::drawLine(const Rect& invalidatedArea, int16_t start, int16_t length, int16_t pos, int16_t width, uint8_t a) const
|
||||
{
|
||||
Rect r = lineRect(start, length, pos, width);
|
||||
normalizeRect(r);
|
||||
r &= invalidatedArea;
|
||||
if (!r.isEmpty())
|
||||
{
|
||||
translateRectToAbsolute(r);
|
||||
HAL::lcd().fillRect(r, color, a);
|
||||
}
|
||||
}
|
||||
|
||||
bool GraphElementArea::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (graph->getUsedCapacity() <= 1)
|
||||
{
|
||||
return true; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
int16_t indexMin;
|
||||
int16_t indexMax;
|
||||
if (!xScreenRangeToIndexRange(graph, invalidatedArea.x, invalidatedArea.right(), indexMin, indexMax))
|
||||
{
|
||||
return true; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const int16_t gapIndex = graph->getGapBeforeIndex();
|
||||
const int baseline = convertToGraphScaleY(graph, yBaseline, dataScale);
|
||||
const CWRUtil::Q5 screenYbaseQ5 = roundQ5(valueToScreenYQ5(graph, baseline));
|
||||
CWRUtil::Q5 screenXQ5;
|
||||
if (indexMin + 1 == gapIndex)
|
||||
{
|
||||
if (indexMin > 0)
|
||||
{
|
||||
indexMin--; // Draw the last line segment before the gap
|
||||
}
|
||||
else
|
||||
{
|
||||
indexMin++; // Do not draw a 1 segment line (a "dot")
|
||||
}
|
||||
}
|
||||
|
||||
const Rect invalidRect = Rect(graph->getGraphAreaPaddingLeft(), graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidth(), graph->getGraphAreaHeight()) & invalidatedArea;
|
||||
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidRect, getAlpha());
|
||||
|
||||
canvas.moveTo(roundQ5(indexToScreenXQ5(graph, indexMin)), screenYbaseQ5);
|
||||
for (int16_t index = indexMin; index <= indexMax; index++)
|
||||
{
|
||||
if (index == gapIndex)
|
||||
{
|
||||
canvas.lineTo(screenXQ5, screenYbaseQ5);
|
||||
screenXQ5 = roundQ5(indexToScreenXQ5(graph, index));
|
||||
canvas.lineTo(screenXQ5, screenYbaseQ5);
|
||||
}
|
||||
else
|
||||
{
|
||||
screenXQ5 = roundQ5(indexToScreenXQ5(graph, index));
|
||||
}
|
||||
canvas.lineTo(screenXQ5, roundQ5(indexToScreenYQ5(graph, index)));
|
||||
}
|
||||
canvas.lineTo(screenXQ5, screenYbaseQ5);
|
||||
return canvas.render(graph->getAlpha());
|
||||
}
|
||||
|
||||
void GraphElementArea::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const int baseline = convertToGraphScaleY(graph, yBaseline, dataScale);
|
||||
const CWRUtil::Q5 screenYbaseQ5 = roundQ5(valueToScreenYQ5(graph, baseline));
|
||||
CWRUtil::Q5 screenXminQ5 = indexToScreenXQ5(graph, index);
|
||||
CWRUtil::Q5 screenXmaxQ5 = screenXminQ5;
|
||||
CWRUtil::Q5 screenYminQ5 = indexToScreenYQ5(graph, index);
|
||||
CWRUtil::Q5 screenYmaxQ5 = screenYminQ5;
|
||||
|
||||
if (index > 0)
|
||||
{
|
||||
screenXminQ5 = indexToScreenXQ5(graph, index - 1);
|
||||
const CWRUtil::Q5 screenYnewQ5 = indexToScreenYQ5(graph, index - 1);
|
||||
screenYminQ5 = MIN(screenYminQ5, screenYnewQ5);
|
||||
screenYmaxQ5 = MAX(screenYmaxQ5, screenYnewQ5);
|
||||
}
|
||||
if (index < graph->getUsedCapacity() - 1)
|
||||
{
|
||||
screenXmaxQ5 = indexToScreenXQ5(graph, index + 1);
|
||||
const CWRUtil::Q5 screenYnewQ5 = indexToScreenYQ5(graph, index + 1);
|
||||
screenYminQ5 = MIN(screenYminQ5, screenYnewQ5);
|
||||
screenYmaxQ5 = MAX(screenYmaxQ5, screenYnewQ5);
|
||||
}
|
||||
screenYminQ5 = MIN(screenYminQ5, screenYbaseQ5);
|
||||
screenYmaxQ5 = MAX(screenYmaxQ5, screenYbaseQ5);
|
||||
Rect dirty(rectFromQ5Coordinates(screenXminQ5, screenYminQ5, screenXmaxQ5, screenYmaxQ5));
|
||||
dirty &= Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
|
||||
bool GraphElementLine::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (graph->getUsedCapacity() <= 1)
|
||||
{
|
||||
return true; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const CWRUtil::Q5 lineWidthQ5 = CWRUtil::toQ5(lineWidth);
|
||||
const uint16_t lineWidthHalf = CWRUtil::Q5(((int)lineWidthQ5 + 1) / 2).ceil();
|
||||
int16_t indexMin;
|
||||
int16_t indexMax;
|
||||
if (!xScreenRangeToIndexRange(graph, invalidatedArea.x - lineWidthHalf, invalidatedArea.right() + lineWidthHalf, indexMin, indexMax))
|
||||
{
|
||||
return true; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const Rect invalidRect = Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight()) & invalidatedArea;
|
||||
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidRect, getAlpha());
|
||||
|
||||
const int16_t gapIndex = graph->getGapBeforeIndex();
|
||||
if (gapIndex <= 0 || gapIndex <= indexMin || gapIndex > indexMax)
|
||||
{
|
||||
drawIndexRange(canvas, graph, indexMin, indexMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawIndexRange(canvas, graph, indexMin, gapIndex - 1);
|
||||
drawIndexRange(canvas, graph, gapIndex, indexMax);
|
||||
}
|
||||
return canvas.render(graph->getAlpha());
|
||||
}
|
||||
|
||||
void GraphElementLine::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
CWRUtil::Q5 const lineWidthQ5 = CWRUtil::toQ5(lineWidth);
|
||||
Rect dirty(rectAround(indexToScreenXQ5(graph, index), indexToScreenYQ5(graph, index), lineWidthQ5));
|
||||
if (index > 0)
|
||||
{
|
||||
const Rect other(rectAround(indexToScreenXQ5(graph, index - 1), indexToScreenYQ5(graph, index - 1), lineWidthQ5));
|
||||
dirty.expandToFit(other);
|
||||
}
|
||||
if (index < graph->getUsedCapacity() - 1)
|
||||
{
|
||||
const Rect other(rectAround(indexToScreenXQ5(graph, index + 1), indexToScreenYQ5(graph, index + 1), lineWidthQ5));
|
||||
dirty.expandToFit(other);
|
||||
}
|
||||
dirty &= Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
|
||||
void GraphElementLine::drawIndexRange(Canvas& canvas, const AbstractDataGraph* graph, int16_t indexMin, int16_t indexMax) const
|
||||
{
|
||||
if (indexMin == indexMax)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const CWRUtil::Q5 lineWidthQ5 = CWRUtil::toQ5(lineWidth);
|
||||
|
||||
CWRUtil::Q5 screenXstartQ5 = roundQ5(indexToScreenXQ5(graph, indexMin));
|
||||
CWRUtil::Q5 screenYstartQ5 = roundQ5(indexToScreenYQ5(graph, indexMin));
|
||||
canvas.moveTo(screenXstartQ5, screenYstartQ5);
|
||||
int16_t index = indexMin;
|
||||
int16_t advance = 1;
|
||||
do
|
||||
{
|
||||
if (index == indexMax)
|
||||
{
|
||||
advance = -1;
|
||||
}
|
||||
index += advance;
|
||||
const CWRUtil::Q5 screenXendQ5 = roundQ5(indexToScreenXQ5(graph, index));
|
||||
const CWRUtil::Q5 screenYendQ5 = roundQ5(indexToScreenYQ5(graph, index));
|
||||
CWRUtil::Q5 dxQ5 = screenXendQ5 - screenXstartQ5;
|
||||
CWRUtil::Q5 dyQ5 = screenYendQ5 - screenYstartQ5;
|
||||
const CWRUtil::Q5 dQ5 = CWRUtil::length(dxQ5, dyQ5);
|
||||
if (dQ5)
|
||||
{
|
||||
dyQ5 = CWRUtil::muldivQ5(lineWidthQ5, dyQ5, dQ5) / 2;
|
||||
dxQ5 = CWRUtil::muldivQ5(lineWidthQ5, dxQ5, dQ5) / 2;
|
||||
canvas.lineTo(screenXstartQ5 - dyQ5, screenYstartQ5 + dxQ5);
|
||||
canvas.lineTo(screenXendQ5 - dyQ5, screenYendQ5 + dxQ5);
|
||||
screenXstartQ5 = screenXendQ5;
|
||||
screenYstartQ5 = screenYendQ5;
|
||||
}
|
||||
} while (index > indexMin);
|
||||
}
|
||||
|
||||
void GraphElementVerticalGapLine::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const int16_t gapIndex = graph->getGapBeforeIndex();
|
||||
const uint8_t a = LCD::div255(getAlpha() * graph->getAlpha());
|
||||
if (gapIndex == 0 || a == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t screenXmin = indexToScreenXQ5(graph, gapIndex - 1).round();
|
||||
int16_t screenXmax = screenXmin + lineWidth;
|
||||
if (lineWidth == 0)
|
||||
{
|
||||
screenXmax = indexToScreenXQ5(graph, gapIndex).round();
|
||||
}
|
||||
Rect r(screenXmin, graph->getGraphAreaPaddingTop(), screenXmax - screenXmin, graph->getGraphAreaHeight());
|
||||
normalizeRect(r);
|
||||
r &= invalidatedArea;
|
||||
if (!r.isEmpty())
|
||||
{
|
||||
translateRectToAbsolute(r);
|
||||
HAL::lcd().fillRect(r, color, a);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphElementVerticalGapLine::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
invalidateIndex(graph, graph->getGapBeforeIndex());
|
||||
invalidateIndex(graph, index);
|
||||
}
|
||||
|
||||
void GraphElementVerticalGapLine::invalidateIndex(const AbstractDataGraph* graph, int16_t index) const
|
||||
{
|
||||
if (index > 0)
|
||||
{
|
||||
const int16_t screenXmin = indexToScreenXQ5(graph, index - 1).round();
|
||||
int16_t screenXmax = screenXmin + lineWidth;
|
||||
if (lineWidth == 0)
|
||||
{
|
||||
screenXmax = indexToScreenXQ5(graph, index).round();
|
||||
}
|
||||
Rect dirty(screenXmin, graph->getGraphAreaPaddingTop(), screenXmax - screenXmin, graph->getGraphAreaHeight());
|
||||
normalizeRect(dirty);
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphElementHistogram::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const uint8_t a = LCD::div255(getAlpha() * graph->getAlpha());
|
||||
if (graph->getUsedCapacity() == 0 || barWidth == 0 || a == 0)
|
||||
{
|
||||
return; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const int baseline = convertToGraphScaleY(graph, yBaseline, dataScale);
|
||||
const CWRUtil::Q5 barOffsetQ5 = CWRUtil::toQ5(barOffset);
|
||||
const CWRUtil::Q5 barWidthQ5 = CWRUtil::toQ5(barWidth);
|
||||
const CWRUtil::Q5 barWidthHalfQ5 = CWRUtil::Q5(((int)barWidthQ5 + 1) / 2);
|
||||
const int16_t barWidthHalf = barWidthHalfQ5.ceil();
|
||||
const int16_t screenYzero = valueToScreenYQ5(graph, baseline).round();
|
||||
int16_t indexMin;
|
||||
int16_t indexMax;
|
||||
if (!xScreenRangeToIndexRange(graph, invalidatedArea.x + barOffset - barWidthHalf, invalidatedArea.right() + barOffset + barWidthHalf, indexMin, indexMax))
|
||||
{
|
||||
return; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const Rect invalidRect = Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight()) & invalidatedArea;
|
||||
for (int16_t index = indexMin; index <= indexMax; index++)
|
||||
{
|
||||
const int16_t screenX = (indexToScreenXQ5(graph, index) + barOffsetQ5 - barWidthHalfQ5).round();
|
||||
const int16_t screenY = indexToScreenYQ5(graph, index).round();
|
||||
Rect r(screenX, screenY, barWidth, screenYzero - screenY);
|
||||
normalizeRect(r);
|
||||
r &= invalidRect;
|
||||
if (!r.isEmpty())
|
||||
{
|
||||
translateRectToAbsolute(r);
|
||||
HAL::lcd().fillRect(r, color, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphElementHistogram::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const CWRUtil::Q5 screenXQ5 = indexToScreenXQ5(graph, index);
|
||||
const CWRUtil::Q5 barWidthHalfQ5 = CWRUtil::Q5((int)CWRUtil::toQ5(barWidth) + 1) / 2;
|
||||
const CWRUtil::Q5 barOffsetQ5 = CWRUtil::toQ5(barOffset);
|
||||
const int baseline = convertToGraphScaleY(graph, yBaseline, dataScale);
|
||||
Rect dirty(rectFromQ5Coordinates(screenXQ5 + barOffsetQ5 - barWidthHalfQ5, indexToScreenYQ5(graph, index), screenXQ5 + barOffsetQ5 + barWidthHalfQ5, valueToScreenYQ5(graph, baseline)));
|
||||
dirty &= Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
|
||||
void GraphElementBoxes::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const uint8_t a = LCD::div255(getAlpha() * graph->getAlpha());
|
||||
if (graph->getUsedCapacity() == 0 || boxWidth == 0 || a == 0)
|
||||
{
|
||||
return; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const CWRUtil::Q5 boxWidthQ5 = CWRUtil::toQ5(boxWidth);
|
||||
const CWRUtil::Q5 boxWidthHalfQ5 = boxWidthQ5 / 2;
|
||||
const uint16_t boxWidthHalf = CWRUtil::Q5(((int)boxWidthQ5 + 1) / 2).ceil();
|
||||
int16_t indexMin;
|
||||
int16_t indexMax;
|
||||
if (!xScreenRangeToIndexRange(graph, invalidatedArea.x - boxWidthHalf, invalidatedArea.right() + boxWidthHalf, indexMin, indexMax))
|
||||
{
|
||||
return; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
for (int16_t index = indexMin; index <= indexMax; index++)
|
||||
{
|
||||
if (isCenterInvisible(graph, index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const CWRUtil::Q5 screenXQ5 = indexToScreenXQ5(graph, index);
|
||||
const CWRUtil::Q5 screenYQ5 = indexToScreenYQ5(graph, index);
|
||||
Rect r((screenXQ5 - boxWidthHalfQ5).round(), (screenYQ5 - boxWidthHalfQ5).round(), boxWidth, boxWidth);
|
||||
r &= invalidatedArea;
|
||||
if (!r.isEmpty())
|
||||
{
|
||||
translateRectToAbsolute(r);
|
||||
HAL::lcd().fillRect(r, color, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphElementBoxes::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (isCenterInvisible(graph, index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const CWRUtil::Q5 boxWidthQ5 = CWRUtil::toQ5(boxWidth);
|
||||
const CWRUtil::Q5 boxWidthHalfQ5 = boxWidthQ5 / 2;
|
||||
const CWRUtil::Q5 screenXQ5 = indexToScreenXQ5(graph, index);
|
||||
const CWRUtil::Q5 screenYQ5 = indexToScreenYQ5(graph, index);
|
||||
Rect dirty((screenXQ5 - boxWidthHalfQ5).round(), (screenYQ5 - boxWidthHalfQ5).round(), boxWidth, boxWidth);
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
|
||||
bool GraphElementDots::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (graph->getUsedCapacity() == 0)
|
||||
{
|
||||
return true; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const CWRUtil::Q5 dotWidthQ5 = CWRUtil::toQ5(dotWidth);
|
||||
const CWRUtil::Q5 dotWidth3Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(3), CWRUtil::toQ5(10));
|
||||
const CWRUtil::Q5 dotWidth4Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(4), CWRUtil::toQ5(10));
|
||||
const CWRUtil::Q5 dotWidth7Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(7), CWRUtil::toQ5(50));
|
||||
const CWRUtil::Q5 dotWidth24Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(24), CWRUtil::toQ5(50));
|
||||
const CWRUtil::Q5 dotWidth2Q5 = CWRUtil::muldivQ5(dotWidthQ5, CWRUtil::toQ5(1), CWRUtil::toQ5(2));
|
||||
const uint16_t dotWidthHalf = CWRUtil::Q5(((int)dotWidthQ5 + 1) / 2).ceil(); // Round up
|
||||
int16_t indexMin;
|
||||
int16_t indexMax;
|
||||
if (!xScreenRangeToIndexRange(graph, invalidatedArea.x - dotWidthHalf, invalidatedArea.right() + dotWidthHalf, indexMin, indexMax))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool bigDots = (dotWidth > 6);
|
||||
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidatedArea, getAlpha());
|
||||
|
||||
for (int16_t index = indexMin; index <= indexMax; index++)
|
||||
{
|
||||
if (isCenterInvisible(graph, index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const CWRUtil::Q5 screenXcenterQ5 = roundQ5(indexToScreenXQ5(graph, index));
|
||||
const CWRUtil::Q5 screenYcenterQ5 = roundQ5(indexToScreenYQ5(graph, index));
|
||||
const Rect dirty(rectAround(screenXcenterQ5, screenYcenterQ5, dotWidthQ5) & invalidatedArea);
|
||||
if (!dirty.isEmpty())
|
||||
{
|
||||
if (bigDots)
|
||||
{
|
||||
canvas.moveTo(screenXcenterQ5 - dotWidth2Q5, screenYcenterQ5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth24Q5, screenYcenterQ5 - dotWidth7Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth7Q5, screenYcenterQ5 - dotWidth24Q5);
|
||||
canvas.lineTo(screenXcenterQ5, screenYcenterQ5 - dotWidth2Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth7Q5, screenYcenterQ5 - dotWidth24Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth24Q5, screenYcenterQ5 - dotWidth7Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth2Q5, screenYcenterQ5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth24Q5, screenYcenterQ5 + dotWidth7Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth7Q5, screenYcenterQ5 + dotWidth24Q5);
|
||||
canvas.lineTo(screenXcenterQ5, screenYcenterQ5 + dotWidth2Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth7Q5, screenYcenterQ5 + dotWidth24Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth24Q5, screenYcenterQ5 + dotWidth7Q5);
|
||||
}
|
||||
else
|
||||
{
|
||||
canvas.moveTo(screenXcenterQ5 - dotWidth2Q5, screenYcenterQ5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5, screenYcenterQ5 - dotWidth2Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 - dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 - dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth2Q5, screenYcenterQ5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5, screenYcenterQ5 + dotWidth2Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth3Q5, screenYcenterQ5 + dotWidth4Q5);
|
||||
canvas.lineTo(screenXcenterQ5 - dotWidth4Q5, screenYcenterQ5 + dotWidth3Q5);
|
||||
}
|
||||
}
|
||||
}
|
||||
return canvas.render(graph->getAlpha());
|
||||
}
|
||||
|
||||
void GraphElementDots::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (isCenterInvisible(graph, index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Rect dirty(rectAround(indexToScreenXQ5(graph, index), indexToScreenYQ5(graph, index), CWRUtil::toQ5(dotWidth)));
|
||||
dirty &= Rect(0, graph->getGraphAreaPaddingTop(), graph->getGraphAreaWidthIncludingPadding(), graph->getGraphAreaHeight());
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
|
||||
bool GraphElementDiamonds::drawCanvasWidget(const Rect& invalidatedArea) const
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (graph->getUsedCapacity() == 0)
|
||||
{
|
||||
return true; // Nothing to draw, everything is fine!
|
||||
}
|
||||
|
||||
const CWRUtil::Q5 diamondWidthQ5 = CWRUtil::toQ5(diamondWidth);
|
||||
const CWRUtil::Q5 dotWidthHalfQ5 = diamondWidthQ5 / 2;
|
||||
const uint16_t dotWidthHalf = CWRUtil::Q5(((int)diamondWidthQ5 + 1) / 2).ceil(); // Round up
|
||||
int16_t indexMin;
|
||||
int16_t indexMax;
|
||||
if (!xScreenRangeToIndexRange(graph, invalidatedArea.x - dotWidthHalf, invalidatedArea.right() + dotWidthHalf, indexMin, indexMax))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Canvas canvas(getPainter(), getAbsoluteRect(), invalidatedArea, getAlpha());
|
||||
|
||||
for (int16_t index = indexMin; index <= indexMax; index++)
|
||||
{
|
||||
if (isCenterInvisible(graph, index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const CWRUtil::Q5 screenXcenterQ5 = roundQ5(indexToScreenXQ5(graph, index));
|
||||
const CWRUtil::Q5 screenYcenterQ5 = roundQ5(indexToScreenYQ5(graph, index));
|
||||
const Rect dirty(rectAround(screenXcenterQ5, screenYcenterQ5, diamondWidthQ5) & invalidatedArea);
|
||||
if (!dirty.isEmpty())
|
||||
{
|
||||
canvas.moveTo(screenXcenterQ5 - dotWidthHalfQ5, screenYcenterQ5);
|
||||
canvas.lineTo(screenXcenterQ5, screenYcenterQ5 - dotWidthHalfQ5);
|
||||
canvas.lineTo(screenXcenterQ5 + dotWidthHalfQ5, screenYcenterQ5);
|
||||
canvas.lineTo(screenXcenterQ5, screenYcenterQ5 + dotWidthHalfQ5);
|
||||
}
|
||||
}
|
||||
return canvas.render(graph->getAlpha());
|
||||
}
|
||||
|
||||
void GraphElementDiamonds::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
if (isCenterInvisible(graph, index))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Rect dirty(rectAround(indexToScreenXQ5(graph, index), indexToScreenYQ5(graph, index), CWRUtil::toQ5(diamondWidth)));
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/Drawable.hpp>
|
||||
#include <touchgfx/hal/HAL.hpp>
|
||||
#include <touchgfx/lcd/LCD.hpp>
|
||||
#include <touchgfx/widgets/graph/GraphLabels.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
void GraphLabelsBase::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (!labelTypedText.hasValidId())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const Font* fontToDraw = labelTypedText.getFont();
|
||||
if (!fontToDraw)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const uint8_t a = LCD::div255(getAlpha() * graph->getAlpha());
|
||||
if (a == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int minorInterval = getCorrectlyScaledLabelInterval(graph);
|
||||
const int majorInterval = getCorrectlyScaledMajorInterval(graph);
|
||||
|
||||
if (majorInterval == 0 && minorInterval == 0)
|
||||
{
|
||||
drawString(invalidatedArea, fontToDraw, graph, 0, 0, a);
|
||||
}
|
||||
else if (minorInterval > 0)
|
||||
{
|
||||
int rangeMin = getGraphRangeMinScaled(graph);
|
||||
int rangeMax = getGraphRangeMaxScaled(graph);
|
||||
if (rangeMin > rangeMax)
|
||||
{
|
||||
const int tmp = rangeMin;
|
||||
rangeMin = rangeMax;
|
||||
rangeMax = tmp;
|
||||
}
|
||||
if ((rangeMax - rangeMin) / minorInterval > 100)
|
||||
{
|
||||
return; // Too many labels
|
||||
}
|
||||
drawIndexRange(invalidatedArea, fontToDraw, graph, rangeMin, rangeMax, minorInterval, majorInterval, a);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLabelsBase::drawIndexRange(const Rect& invalidatedArea, const Font* fontToDraw, const AbstractDataGraph* graph, const int rangeMin, const int rangeMax, const int minorInterval, const int majorInterval, const uint8_t a) const
|
||||
{
|
||||
if (minorInterval == 0)
|
||||
{
|
||||
if ((0 >= rangeMin && 0 <= rangeMax) || (0 >= rangeMax && 0 <= rangeMin))
|
||||
{
|
||||
drawString(invalidatedArea, fontToDraw, graph, 0, 0, a);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const int minorLo = (int)(rangeMin / minorInterval) - 1;
|
||||
const int minorHi = (int)(rangeMax / minorInterval) + 1;
|
||||
if (majorInterval == 0)
|
||||
{
|
||||
for (int minorIndex = minorLo; minorIndex != minorHi + 1; minorIndex++)
|
||||
{
|
||||
const int minorValue = (int)(minorInterval * minorIndex);
|
||||
if ((minorValue >= rangeMin && minorValue <= rangeMax) || (minorValue >= rangeMax && minorValue <= rangeMin))
|
||||
{
|
||||
drawString(invalidatedArea, fontToDraw, graph, minorValue, labelInterval * minorIndex, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int majorLo = (int)(rangeMin / majorInterval) - 1;
|
||||
const int majorHi = (int)(rangeMax / majorInterval) + 1;
|
||||
int majorIndex = majorLo;
|
||||
int majorValue = majorInterval * majorIndex;
|
||||
int minorIndex = minorLo;
|
||||
int minorValue = minorInterval * minorIndex;
|
||||
for (;;)
|
||||
{
|
||||
// Draw strings up to the major
|
||||
while (minorValue < majorValue)
|
||||
{
|
||||
if ((minorValue >= rangeMin && minorValue <= rangeMax) || (minorValue >= rangeMax && minorValue <= rangeMin))
|
||||
{
|
||||
drawString(invalidatedArea, fontToDraw, graph, minorValue, labelInterval * minorIndex, a);
|
||||
}
|
||||
minorIndex++;
|
||||
minorValue += minorInterval;
|
||||
}
|
||||
// Advance minor past the major we are about to draw
|
||||
while (minorValue <= majorValue)
|
||||
{
|
||||
minorIndex++;
|
||||
minorValue += minorInterval;
|
||||
}
|
||||
if (majorValue < minorValue)
|
||||
{
|
||||
majorIndex++;
|
||||
if (majorIndex == majorHi + 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
majorValue += majorInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLabelsBase::formatLabel(Unicode::UnicodeChar* buffer, int16_t bufferSize, int label, int decimals, Unicode::UnicodeChar decimalPoint, int scale) const
|
||||
{
|
||||
int length = 0;
|
||||
if (label < 0 && length < bufferSize - 1)
|
||||
{
|
||||
buffer[length++] = '-';
|
||||
label = -label;
|
||||
}
|
||||
if (decimals == 0)
|
||||
{
|
||||
Unicode::snprintf(buffer + length, bufferSize - length, "%d", (label + scale / 2) / scale);
|
||||
}
|
||||
else if (decimals > 0)
|
||||
{
|
||||
Unicode::snprintf(buffer + length, bufferSize - length, "%d", label / scale);
|
||||
length = Unicode::strlen(buffer);
|
||||
if (length < bufferSize - 1)
|
||||
{
|
||||
buffer[length++] = decimalPoint;
|
||||
int remainder = label % scale;
|
||||
for (int i = 0; i < decimals && length < bufferSize - 1; i++)
|
||||
{
|
||||
remainder *= 10;
|
||||
if (i == decimals - 1 || length == bufferSize - 1)
|
||||
{
|
||||
remainder += scale / 2; // Rounding on the last (visible) digit
|
||||
}
|
||||
const int digit = (remainder / scale);
|
||||
buffer[length++] = (Unicode::UnicodeChar)('0' + digit);
|
||||
remainder %= scale;
|
||||
}
|
||||
buffer[length] = (Unicode::UnicodeChar)0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLabelsX::invalidateGraphPointAt(int16_t index)
|
||||
{
|
||||
if (!labelTypedText.hasValidId())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const Font* fontToDraw = labelTypedText.getFont();
|
||||
if (!fontToDraw)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const AbstractDataGraph* graph = getGraph();
|
||||
const uint8_t a = LCD::div255(getAlpha() * graph->getAlpha());
|
||||
if (a == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int scaledIndex = graph->int2scaledX(index);
|
||||
const int minorInterval = getCorrectlyScaledLabelInterval(graph);
|
||||
const int majorInterval = getCorrectlyScaledMajorInterval(graph);
|
||||
|
||||
const bool isOnMinor = (minorInterval > 0 && scaledIndex == minorInterval * (int)(scaledIndex / minorInterval));
|
||||
const bool isOnMajor = (majorInterval > 0 && scaledIndex == majorInterval * (int)(scaledIndex / majorInterval));
|
||||
if ((majorInterval == 0 && minorInterval == 0 && scaledIndex == 0) || (isOnMinor && !isOnMajor))
|
||||
{
|
||||
Unicode::UnicodeChar wildcard[20];
|
||||
const int labelScaled = (minorInterval == 0) ? 0 : (scaledIndex / minorInterval) * labelInterval;
|
||||
formatLabel(wildcard, 20, getIndexToXAxis(graph, scaledIndex, labelScaled), labelDecimals, labelDecimalPoint, dataScale);
|
||||
// Adjust to make label centered
|
||||
int16_t labelWidth;
|
||||
const Unicode::UnicodeChar* text = labelTypedText.getText();
|
||||
if (labelRotation == TEXT_ROTATE_0 || labelRotation == TEXT_ROTATE_180)
|
||||
{
|
||||
labelWidth = fontToDraw->getStringWidth(text, wildcard);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get full height for all lines, except the last line for which we "replace" the part under
|
||||
// the baseline with the 'spacing above'.
|
||||
labelWidth = fontToDraw->getHeight() * fontToDraw->getNumberOfLines(text, wildcard) + fontToDraw->getSpacingAbove(text, wildcard);
|
||||
}
|
||||
const int16_t offset = (labelRotation == TEXT_ROTATE_0 || labelRotation == TEXT_ROTATE_90) ? (labelWidth / 2) : ((labelWidth + 1) / 2);
|
||||
Rect dirty((graph->getGraphAreaMarginLeft() + valueToScreenXQ5(graph, scaledIndex).round()) - offset, 0, labelWidth, getHeight());
|
||||
invalidateRect(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLabelsX::drawIndexRange(const Rect& invalidatedArea, const Font* fontToDraw, const AbstractDataGraph* graph, const int rangeMin, const int rangeMax, const int minorInterval, const int majorInterval, const uint8_t a) const
|
||||
{
|
||||
const int16_t gapIndex = graph->getGapBeforeIndex();
|
||||
if (gapIndex <= 0 || gapIndex <= rangeMin || gapIndex > rangeMax)
|
||||
{
|
||||
GraphLabelsBase::drawIndexRange(invalidatedArea, fontToDraw, graph, rangeMin, rangeMax, minorInterval, majorInterval, a);
|
||||
}
|
||||
else
|
||||
{
|
||||
GraphLabelsBase::drawIndexRange(invalidatedArea, fontToDraw, graph, rangeMin, (int)gapIndex - 1, minorInterval, majorInterval, a);
|
||||
GraphLabelsBase::drawIndexRange(invalidatedArea, fontToDraw, graph, (int)gapIndex, rangeMax, minorInterval, majorInterval, a);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLabelsX::drawString(const Rect& invalidatedArea, const Font* fontToDraw, const AbstractDataGraph* graph, const int valueScaled, const int labelScaled, const uint8_t a) const
|
||||
{
|
||||
const int16_t labelX = valueToScreenXQ5(graph, valueScaled).round() - graph->getGraphAreaPaddingLeft();
|
||||
if (labelX < 0 || labelX >= graph->getGraphAreaWidth())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Unicode::UnicodeChar wildcard[20];
|
||||
formatLabel(wildcard, 20, getIndexToXAxis(graph, valueScaled, labelScaled), labelDecimals, labelDecimalPoint, graph->getScaleX());
|
||||
// Adjust to make label centered
|
||||
int16_t labelWidth;
|
||||
const Unicode::UnicodeChar* text = labelTypedText.getText();
|
||||
if (labelRotation == TEXT_ROTATE_0 || labelRotation == TEXT_ROTATE_180)
|
||||
{
|
||||
labelWidth = fontToDraw->getStringWidth(text, wildcard);
|
||||
}
|
||||
else
|
||||
{
|
||||
labelWidth = fontToDraw->getHeight() * fontToDraw->getNumberOfLines(text, wildcard) + fontToDraw->getSpacingAbove(text, wildcard);
|
||||
}
|
||||
const int16_t offset = (labelRotation == TEXT_ROTATE_0 || labelRotation == TEXT_ROTATE_90) ? (labelWidth / 2) : ((labelWidth + 1) / 2);
|
||||
Rect labelRect((graph->getGraphAreaMarginLeft() + valueToScreenXQ5(graph, valueScaled).round()) - offset, 0, labelWidth, getHeight());
|
||||
|
||||
Rect dirty = labelRect & invalidatedArea;
|
||||
if (!dirty.isEmpty())
|
||||
{
|
||||
dirty.x -= labelRect.x;
|
||||
dirty.y -= labelRect.y;
|
||||
translateRectToAbsolute(labelRect);
|
||||
const LCD::StringVisuals visuals(fontToDraw, color, a, labelTypedText.getAlignment(), 0, labelRotation, labelTypedText.getTextDirection(), 0, WIDE_TEXT_NONE);
|
||||
HAL::lcd().drawString(labelRect, dirty, visuals, text, wildcard, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphLabelsY::drawString(const Rect& invalidatedArea, const Font* fontToDraw, const AbstractDataGraph* graph, const int valueScaled, const int labelScaled, const uint8_t a) const
|
||||
{
|
||||
const int16_t labelCoord = valueToScreenYQ5(graph, valueScaled).round() - graph->getGraphAreaPaddingTop();
|
||||
if (labelCoord < 0 || labelCoord >= graph->getGraphAreaHeight())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Unicode::UnicodeChar wildcard[20];
|
||||
formatLabel(wildcard, 20, labelScaled, labelDecimals, labelDecimalPoint, dataScale);
|
||||
|
||||
// Adjust to make label centered
|
||||
int16_t labelHeight;
|
||||
const Unicode::UnicodeChar* text = labelTypedText.getText();
|
||||
if (labelRotation == TEXT_ROTATE_0 || labelRotation == TEXT_ROTATE_180)
|
||||
{
|
||||
// Get full height for all lines, except the last line for which we "replace" the part under
|
||||
// the baseline with the 'spacing above'.
|
||||
labelHeight = fontToDraw->getHeight() * fontToDraw->getNumberOfLines(text, wildcard) + fontToDraw->getSpacingAbove(text, wildcard);
|
||||
}
|
||||
else
|
||||
{
|
||||
labelHeight = fontToDraw->getStringWidth(text, wildcard);
|
||||
}
|
||||
const int16_t offset = (labelRotation == TEXT_ROTATE_0 || labelRotation == TEXT_ROTATE_90) ? (labelHeight / 2) : ((labelHeight + 1) / 2);
|
||||
Rect labelRect(0, (graph->getGraphAreaMarginTop() + valueToScreenYQ5(graph, valueScaled).round()) - offset, getWidth(), labelHeight);
|
||||
|
||||
Rect dirty = labelRect & invalidatedArea;
|
||||
if (!dirty.isEmpty())
|
||||
{
|
||||
dirty.x -= labelRect.x;
|
||||
dirty.y -= labelRect.y;
|
||||
translateRectToAbsolute(labelRect);
|
||||
const LCD::StringVisuals visuals(fontToDraw, color, a, labelTypedText.getAlignment(), 0, labelRotation, labelTypedText.getTextDirection(), 0, WIDE_TEXT_NONE);
|
||||
HAL::lcd().drawString(labelRect, dirty, visuals, text, wildcard, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphTitle::draw(const Rect& invalidatedArea) const
|
||||
{
|
||||
if (!titleTypedText.hasValidId())
|
||||
{
|
||||
return;
|
||||
}
|
||||
const Font* fontToDraw = titleTypedText.getFont();
|
||||
if (!fontToDraw)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t a = LCD::div255(getAlpha() * getGraph()->getAlpha());
|
||||
if (a == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t lineHeight = fontToDraw->getHeight() * fontToDraw->getNumberOfLines(titleTypedText.getText());
|
||||
|
||||
Rect labelRect(rect);
|
||||
// Adjust to make label centered
|
||||
if (titleRotation == TEXT_ROTATE_0 || titleRotation == TEXT_ROTATE_180)
|
||||
{
|
||||
labelRect.y += (labelRect.height - lineHeight) / 2;
|
||||
labelRect.height = lineHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelRect.x += (labelRect.width - lineHeight) / 2;
|
||||
labelRect.width = lineHeight;
|
||||
}
|
||||
|
||||
Rect dirty = labelRect & invalidatedArea;
|
||||
if (!dirty.isEmpty())
|
||||
{
|
||||
dirty.x -= labelRect.x;
|
||||
dirty.y -= labelRect.y;
|
||||
translateRectToAbsolute(labelRect);
|
||||
const LCD::StringVisuals visuals(fontToDraw, getColor(), a, titleTypedText.getAlignment(), 0, titleRotation, titleTypedText.getTextDirection(), 0, WIDE_TEXT_NONE);
|
||||
HAL::lcd().drawString(labelRect, dirty, visuals, titleTypedText.getText(), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
@@ -0,0 +1,64 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/graph/GraphScroll.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
void GraphScrollData::clear()
|
||||
{
|
||||
DynamicDataGraph::clear();
|
||||
current = 0;
|
||||
dataCounter = 0; // Restart X-axis numbering
|
||||
}
|
||||
|
||||
int32_t GraphScrollData::indexToGlobalIndex(int16_t index) const
|
||||
{
|
||||
if (usedCapacity < maxCapacity)
|
||||
{
|
||||
return dataIndex(index);
|
||||
}
|
||||
return (dataCounter - maxCapacity) + index;
|
||||
}
|
||||
|
||||
void GraphScrollData::beforeAddValue()
|
||||
{
|
||||
if (usedCapacity == maxCapacity)
|
||||
{
|
||||
invalidateAllXAxisPoints();
|
||||
}
|
||||
}
|
||||
|
||||
int16_t GraphScrollData::addValue(int value)
|
||||
{
|
||||
const bool graphFull = usedCapacity == maxCapacity;
|
||||
const int16_t index = current++;
|
||||
current %= maxCapacity;
|
||||
if (index == usedCapacity)
|
||||
{
|
||||
usedCapacity++;
|
||||
}
|
||||
yValues[index] = value;
|
||||
if (graphFull)
|
||||
{
|
||||
invalidateGraphArea();
|
||||
invalidateAllXAxisPoints();
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidateGraphPointAt(index);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/graph/GraphWrapAndClear.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
void GraphWrapAndClearData::clear()
|
||||
{
|
||||
invalidateAllXAxisPoints();
|
||||
DynamicDataGraph::clear();
|
||||
}
|
||||
|
||||
void GraphWrapAndClearData::beforeAddValue()
|
||||
{
|
||||
if (usedCapacity >= maxCapacity)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
int16_t GraphWrapAndClearData::addValue(int value)
|
||||
{
|
||||
const bool clearGraph = (usedCapacity == 0);
|
||||
const int16_t index = usedCapacity;
|
||||
usedCapacity++;
|
||||
yValues[dataIndex(index)] = value;
|
||||
if (clearGraph)
|
||||
{
|
||||
// Label sizes might have grown, also invalidate new sizes
|
||||
invalidateAllXAxisPoints();
|
||||
}
|
||||
invalidateGraphPointAt(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/******************************************************************************
|
||||
* Copyright (c) 2018(-2026) STMicroelectronics.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of the TouchGFX 4.26.1 distribution.
|
||||
*
|
||||
* This software is licensed under terms that can be found in the LICENSE file in
|
||||
* the root directory of this software component.
|
||||
* If no LICENSE file comes with this software, it is provided AS-IS.
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <touchgfx/widgets/graph/GraphWrapAndOverwrite.hpp>
|
||||
|
||||
namespace touchgfx
|
||||
{
|
||||
|
||||
void GraphWrapAndOverwriteData::clear()
|
||||
{
|
||||
DynamicDataGraph::clear();
|
||||
current = 0;
|
||||
}
|
||||
|
||||
int32_t GraphWrapAndOverwriteData::indexToGlobalIndex(int16_t index) const
|
||||
{
|
||||
if (this->usedCapacity < this->maxCapacity)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
const int16_t gapIndex = this->getGapBeforeIndex();
|
||||
if (index < gapIndex)
|
||||
{
|
||||
return (this->dataCounter - gapIndex) + index;
|
||||
}
|
||||
return ((this->dataCounter - gapIndex) - this->maxCapacity) + index;
|
||||
}
|
||||
|
||||
void GraphWrapAndOverwriteData::beforeAddValue()
|
||||
{
|
||||
if (current == 0 && usedCapacity >= maxCapacity)
|
||||
{
|
||||
const int xMin = getGraphRangeXMin();
|
||||
const int xMax = getGraphRangeXMax();
|
||||
for (int i = xMin; i < 0; i++)
|
||||
{
|
||||
invalidateXAxisPointAt(i);
|
||||
}
|
||||
for (int i = maxCapacity; i <= xMax; i++)
|
||||
{
|
||||
invalidateXAxisPointAt(i);
|
||||
}
|
||||
}
|
||||
if (usedCapacity >= maxCapacity)
|
||||
{
|
||||
invalidateGraphPointAt(current);
|
||||
invalidateXAxisPointAt(current);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t GraphWrapAndOverwriteData::addValue(int value)
|
||||
{
|
||||
const int16_t index = current++;
|
||||
current %= maxCapacity;
|
||||
if (index == usedCapacity)
|
||||
{
|
||||
usedCapacity++;
|
||||
}
|
||||
yValues[dataIndex(index)] = value;
|
||||
setGapBeforeIndex(index + 1);
|
||||
invalidateGraphPointAt(index);
|
||||
if (usedCapacity >= maxCapacity)
|
||||
{
|
||||
invalidateXAxisPointAt(index);
|
||||
}
|
||||
if (index == 0 && usedCapacity >= maxCapacity)
|
||||
{
|
||||
const int xMin = getGraphRangeXMin();
|
||||
const int xMax = getGraphRangeXMax();
|
||||
for (int i = xMin; i < 0; i++)
|
||||
{
|
||||
invalidateXAxisPointAt(i);
|
||||
}
|
||||
for (int i = maxCapacity; i <= xMax; i++)
|
||||
{
|
||||
invalidateXAxisPointAt(i);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
} // namespace touchgfx
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user