move into firmware subfolder

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-08-20 10:27:03 +02:00
parent d316bb9f2c
commit 5a08c2e09d
117 changed files with 0 additions and 0 deletions

286
firmware/src/ui/Device.cpp Normal file
View File

@@ -0,0 +1,286 @@
#include "ui/Device.h"
#include <hal/u8g2_hal_sdl.h>
#include <u8g2.h>
#include "MenuOptions.h"
#include "common/InactivityTracker.h"
#include "hal_native/PersistenceManager.h"
#include "ui/ScreenSaver.h"
#include "ui/SplashScreen.h"
#include "ui/widgets/Button.h"
#include "ui/widgets/D_Pad.h"
u8g2_t u8g2;
menu_options_t options;
std::unique_ptr<InactivityTracker> m_inactivityTracker;
static void set_pixel_rgba(const SDL_Surface *surface, const int x, const int y, const uint32_t pixel_color)
{
if (!surface || x < 0 || x >= surface->w || y < 0 || y >= surface->h)
{
return;
}
const auto p = static_cast<uint8_t *>(surface->pixels) + y * surface->pitch + x * sizeof(uint32_t);
*reinterpret_cast<uint32_t *>(p) = pixel_color;
}
Device::Device(void *appstate) : UIWidget(appstate)
{
auto dpad_callback = [](const D_Pad::Direction direction) {
SDL_Keycode key = 0;
switch (direction)
{
case D_Pad::Direction::UP:
key = SDLK_UP;
break;
case D_Pad::Direction::DOWN:
key = SDLK_DOWN;
break;
case D_Pad::Direction::LEFT:
key = SDLK_LEFT;
break;
case D_Pad::Direction::RIGHT:
key = SDLK_RIGHT;
break;
case D_Pad::Direction::NONE: // Fallthrough oder keine Aktion
default:
break;
}
if (key != 0)
{
PushKey(key);
}
};
m_children.push_back(std::make_shared<Button>(
GetContext(), U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 3 * U8G2_SCREEN_PADDING + DPAD_WIDTH,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - BUTTON_WIDTH, BUTTON_WIDTH,
[]() {
PushKey(SDLK_RETURN);
}));
m_children.push_back(std::make_shared<Button>(
GetContext(), U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 4 * U8G2_SCREEN_PADDING + DPAD_WIDTH + BUTTON_WIDTH,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - 2 * BUTTON_WIDTH, BUTTON_WIDTH,
[]() {
PushKey(SDLK_BACKSPACE);
}));
m_children.push_back(std::make_shared<D_Pad>(
GetContext(), U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 2 * U8G2_SCREEN_PADDING,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - DPAD_WIDTH, DPAD_WIDTH, dpad_callback));
u8g2_Setup_sh1106_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_sdl_hw_spi, u8x8_gpio_and_delay_sdl);
u8x8_InitDisplay(u8g2_GetU8x8(&u8g2));
options = {
.u8g2 = &u8g2,
.setScreen = [this](const std::shared_ptr<Widget> &screen) {
this->SetScreen(screen);
},
.pushScreen = [this](const std::shared_ptr<Widget> &screen) {
this->PushScreen(screen);
},
.popScreen = [this]() {
this->PopScreen();
},
.persistenceManager = std::make_shared<PersistenceManager>(),
};
options.persistenceManager->Load();
m_widget = std::make_shared<SplashScreen>(&options);
m_inactivityTracker = std::make_unique<InactivityTracker>(60000, []() {
const auto screensaver = std::make_shared<ScreenSaver>(&options);
options.pushScreen(screensaver);
});
}
void Device::SetScreen(const std::shared_ptr<Widget> &screen)
{
if (screen != nullptr)
{
m_widget = screen;
m_history.clear();
m_history.emplace_back(m_widget);
m_widget->enter();
}
}
void Device::PushScreen(const std::shared_ptr<Widget> &screen)
{
if (screen != nullptr)
{
if (m_widget)
{
m_widget->pause();
}
m_widget = screen;
m_history.emplace_back(m_widget);
m_widget->enter();
}
}
void Device::PopScreen()
{
if (m_history.size() >= 2)
{
if (m_widget)
{
m_widget->exit();
}
m_history.pop_back();
m_widget = m_history.back();
m_widget->resume();
}
}
void Device::PushKey(const SDL_Keycode key)
{
SDL_Event ev;
ev.type = SDL_EVENT_KEY_DOWN;
ev.key.key = key;
SDL_PushEvent(&ev);
}
void Device::RenderU8G2() const
{
SDL_Surface *u8g2_surface = nullptr;
SDL_Texture *u8g2_texture = nullptr;
u8g2_surface = SDL_CreateSurface(U8G2_SCREEN_WIDTH, U8G2_SCREEN_HEIGHT, SDL_PIXELFORMAT_RGBA8888);
if (!u8g2_surface)
{
SDL_Log("SDL_CreateSurfaceFrom Error: %s\n", SDL_GetError());
}
else
{
const auto color_black = SDL_MapSurfaceRGBA(u8g2_surface, 0, 0, 0, 255);
const auto color_white = SDL_MapSurfaceRGBA(u8g2_surface, 255, 255, 255, 255);
const auto u8g2_buf = u8g2_GetBufferPtr(&u8g2);
for (auto y = 0; y < U8G2_SCREEN_HEIGHT; ++y)
{
for (auto x = 0; x < U8G2_SCREEN_WIDTH; ++x)
{
const auto page = y / 8;
const auto bit_index = y % 8;
const auto byte_ptr = u8g2_buf + page * U8G2_SCREEN_WIDTH + x;
const auto pixel_is_set = (*byte_ptr >> bit_index) & 0x01;
set_pixel_rgba(u8g2_surface, x, y, pixel_is_set ? color_white : color_black);
}
}
u8g2_texture = SDL_CreateTextureFromSurface(GetContext()->MainRenderer(), u8g2_surface);
if (!u8g2_texture)
{
SDL_Log("SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError());
}
if (!SDL_SetTextureScaleMode(u8g2_texture, SDL_SCALEMODE_NEAREST))
{
SDL_Log("SDL_SetTextureScaleMode Error: %s\n", SDL_GetError());
}
SDL_DestroySurface(u8g2_surface);
}
if (u8g2_texture)
{
SDL_FRect destRect;
destRect.x = U8G2_SCREEN_PADDING;
destRect.y = U8G2_SCREEN_PADDING;
destRect.w = U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR;
destRect.h = U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR;
SDL_RenderTexture(GetContext()->MainRenderer(), u8g2_texture, nullptr, &destRect);
SDL_DestroyTexture(u8g2_texture);
}
}
void Device::DrawBackground() const
{
int windowWidth = 0;
int windowHeight = 0;
if (!SDL_GetWindowSize(GetContext()->MainWindow(), &windowWidth, &windowHeight))
{
SDL_Log("SDL_GetWindowSize Error: %s\n", SDL_GetError());
}
const auto rect = SDL_FRect{0.0f, 0.0f, static_cast<float>(windowWidth), static_cast<float>(windowHeight)};
SDL_SetRenderDrawColor(GetContext()->MainRenderer(), 193, 46, 31, 255);
SDL_RenderFillRect(GetContext()->MainRenderer(), &rect);
}
void Device::DrawScreen(const uint64_t dt) const
{
u8g2_ClearBuffer(&u8g2);
if (m_widget != nullptr)
{
m_widget->update(dt);
m_widget->render();
}
RenderU8G2();
}
void Device::Render(const uint64_t dt) const
{
DrawBackground();
DrawScreen(dt);
for (const auto &child : m_children)
{
child->Render(dt);
}
m_inactivityTracker->update(dt);
}
void Device::HandleTap(const SDL_MouseButtonEvent *event) const
{
// SDL_Log("HandleTap: x=%f, y=%f, button=%d", event->x, event->y, event->button);
for (const auto &child : m_children)
{
if (child->IsHit(static_cast<int>(event->x), static_cast<int>(event->y)))
{
child->OnTap(static_cast<int>(event->x), static_cast<int>(event->y));
break;
}
}
}
void Device::ReleaseTap(const SDL_MouseButtonEvent *event) const
{
// SDL_Log("ReleaseTap: x=%f, y=%f, button=%d", event->x, event->y, event->button);
m_inactivityTracker->reset();
for (const auto &child : m_children)
{
child->ReleaseTap(static_cast<int>(event->x), static_cast<int>(event->y));
}
}
void Device::OnButtonClicked(const ButtonType button) const
{
m_inactivityTracker->reset();
if (m_widget != nullptr)
{
m_widget->onButtonClicked(button);
}
}
bool Device::IsHit(int mouse_x, int mouse_y) const
{
return false;
}
void Device::OnTap(int mouse_x, int mouse_y)
{
////
}
void Device::ReleaseTap(int mouse_x, int mouse_y)
{
///
}

49
firmware/src/ui/Device.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include "UIWidget.h"
#include "common/Common.h"
#include "common/Widget.h"
#include "model/AppContext.h"
class Device final : public UIWidget
{
public:
explicit Device(void *appstate);
void Render(uint64_t dt) const override;
void HandleTap(const SDL_MouseButtonEvent *event) const;
void ReleaseTap(const SDL_MouseButtonEvent *event) const;
void OnButtonClicked(ButtonType button) const;
[[nodiscard]] bool IsHit(int mouse_x, int mouse_y) const override;
void OnTap(int mouse_x, int mouse_y) override;
void ReleaseTap(int mouse_x, int mouse_y) override;
private:
void DrawBackground() const;
void DrawScreen(uint64_t dt) const;
void RenderU8G2() const;
void SetScreen(const std::shared_ptr<Widget> &screen);
void PushScreen(const std::shared_ptr<Widget> &screen);
void PopScreen();
static void PushKey(SDL_Keycode key);
std::vector<std::shared_ptr<UIWidget>> m_children{};
std::shared_ptr<Widget> m_widget;
std::vector<std::shared_ptr<Widget>> m_history;
};

View File

@@ -0,0 +1,12 @@
#include "ui/UIWidget.h"
UIWidget::UIWidget(void *appstate) : m_context(static_cast<AppContext *>(appstate))
{
}
UIWidget::~UIWidget() = default;
auto UIWidget::GetContext() const -> AppContext *
{
return m_context;
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "model/AppContext.h"
class UIWidget
{
public:
explicit UIWidget(void *appstate);
virtual ~UIWidget();
virtual void Render(uint64_t dt) const = 0;
[[nodiscard]] virtual bool IsHit(int mouse_x, int mouse_y) const = 0;
virtual void OnTap(int mouse_x, int mouse_y) = 0;
virtual void ReleaseTap(int mouse_x, int mouse_y) = 0;
[[nodiscard]] AppContext *GetContext() const;
private:
AppContext *m_context{};
};

View File

@@ -0,0 +1,49 @@
#include "ui/widgets/Button.h"
#include "../../manager/ResourceManager.h"
#include <functional>
#include <utility>
Button::Button(void *appState, const float x, const float y, const float width, std::function<void()> callback)
: UIWidget(appState), m_x(x), m_y(y), m_width(width), m_callback(std::move(callback))
{
}
void Button::Render(const uint64_t dt) const
{
const auto button =
ResourceManager::Instance().GetTextureByName(GetContext()->MainRenderer(), "button_normal.png");
const auto dst = SDL_FRect(m_x, m_y, m_width, m_width);
SDL_RenderTexture(GetContext()->MainRenderer(), button, nullptr, &dst);
SDL_DestroyTexture(button);
if (m_isPressed)
{
const auto overlay =
ResourceManager::Instance().GetTextureByName(GetContext()->MainRenderer(), "button_pressed_overlay.png");
SDL_RenderTexture(GetContext()->MainRenderer(), overlay, nullptr, &dst);
SDL_DestroyTexture(overlay);
}
}
bool Button::IsHit(const int mouse_x, const int mouse_y) const
{
const auto fx = static_cast<float>(mouse_x);
const auto fy = static_cast<float>(mouse_y);
return (fx >= m_x && fx <= (m_x + m_width) &&
fy >= m_y && fy <= (m_y + m_width)) || m_isPressed;
}
void Button::OnTap(int mouse_x, int mouse_y)
{
if (m_callback)
{
m_isPressed = true;
m_callback();
}
}
void Button::ReleaseTap(int mouse_x, int mouse_y)
{
m_isPressed = false;
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "ui/UIWidget.h"
#include <functional>
#define BUTTON_WIDTH (35)
class Button final : public UIWidget
{
public:
Button(void *appState, float x, float y, float width, std::function<void()> callback);
void Render(uint64_t dt) const override;
[[nodiscard]] bool IsHit(int mouse_x, int mouse_y) const override;
void OnTap(int mouse_x, int mouse_y) override;
void ReleaseTap(int mouse_x, int mouse_y) override;
private:
float m_x;
float m_y;
float m_width;
std::function<void()> m_callback;
bool m_isPressed = false;
};

View File

@@ -0,0 +1,112 @@
#include "D_Pad.h"
#include "ui/widgets/D_Pad.h"
#include "../../manager/ResourceManager.h"
D_Pad::D_Pad(void *appState, const float x, const float y, const float width, std::function<void(Direction)> callback)
: UIWidget(appState), m_x(x), m_y(y), m_width(width), m_callback(std::move(callback))
{
}
void D_Pad::Render(const uint64_t dt) const
{
const auto dPad =
ResourceManager::Instance().GetTextureByName(GetContext()->MainRenderer(), "d-pad_normal.png");
auto dst = SDL_FRect(m_x, m_y, m_width, m_width);
SDL_RenderTexture(GetContext()->MainRenderer(), dPad, nullptr, &dst);
SDL_DestroyTexture(dPad);
if (m_isPressed != Direction::NONE)
{
const auto overlay =
ResourceManager::Instance().GetTextureByName(GetContext()->MainRenderer(), "button_pressed_overlay.png");
switch (m_isPressed)
{
case Direction::UP:
dst = SDL_FRect(m_x + m_width / 3.0f, m_y, m_width / 3.0f, m_width / 3.0f);
break;
case Direction::DOWN:
dst = SDL_FRect(m_x + m_width / 3.0f, m_y + 2 * m_width / 3.0f, m_width / 3.0f, m_width / 3.0f);
break;
case Direction::LEFT:
dst = SDL_FRect(m_x, m_y + m_width / 3.0f, m_width / 3.0f, m_width / 3.0f);
break;
case Direction::RIGHT:
dst = SDL_FRect(m_x + 2 * m_width / 3.0f, m_y + m_width / 3.0f, m_width / 3.0f, m_width / 3.0f);
break;
case Direction::NONE:
break;
}
SDL_RenderTexture(GetContext()->MainRenderer(), overlay, nullptr, &dst);
SDL_DestroyTexture(overlay);
}
}
bool D_Pad::IsHit(const int mouse_x, const int mouse_y) const
{
const auto fx = static_cast<float>(mouse_x);
const auto fy = static_cast<float>(mouse_y);
return (fx >= m_x && fx <= (m_x + m_width) &&
fy >= m_y && fy <= (m_y + m_width));
}
D_Pad::Direction D_Pad::GetDirectionFromTap(const float local_x, const float local_y) const
{
const float segment = m_width / 3.0f;
int col = -1;
if (local_x < segment)
col = 0;
else if (local_x < 2 * segment)
col = 1;
else if (local_x <= m_width)
col = 2;
else
return Direction::NONE;
int row = -1;
if (local_y < segment)
row = 0;
else if (local_y < 2 * segment)
row = 1;
else if (local_y <= m_width)
row = 2;
else
return Direction::NONE;
if (col == 1 && row == 0)
return Direction::UP;
if (col == 1 && row == 2)
return Direction::DOWN;
if (col == 0 && row == 1)
return Direction::LEFT;
if (col == 2 && row == 1)
return Direction::RIGHT;
return Direction::NONE;
}
void D_Pad::OnTap(const int mouse_x, const int mouse_y)
{
if (m_callback)
{
const auto local_x = static_cast<float>(mouse_x) - m_x;
if (const auto local_y = static_cast<float>(mouse_y) - m_y;
local_x >= 0 && local_x <= m_width && local_y >= 0 && local_y <= m_width)
{
m_isPressed = GetDirectionFromTap(local_x, local_y);
m_callback(m_isPressed);
}
}
}
void D_Pad::ReleaseTap(const int mouse_x, const int mouse_y)
{
m_isPressed = Direction::NONE;
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "functional"
#include "ui/UIWidget.h"
#define DPAD_WIDTH (105)
class D_Pad final : public UIWidget
{
public:
enum class Direction { NONE, UP, DOWN, LEFT, RIGHT };
D_Pad(void *appState, float x, float y, float width, std::function<void(Direction)> callback);
void Render(uint64_t dt) const override;
[[nodiscard]] bool IsHit(int mouse_x, int mouse_y) const override;
void OnTap(int mouse_x, int mouse_y) override;
void ReleaseTap(int mouse_x, int mouse_y) override;
private:
float m_x, m_y, m_width;
std::function<void(Direction)> m_callback;
Direction m_isPressed = Direction::NONE;
[[nodiscard]] Direction GetDirectionFromTap(float local_x, float local_y) const;
};