This commit is contained in:
Rasmus Rasmussen 2025-12-14 13:00:01 +01:00
parent 1b51fbd552
commit 6bd3102593
23 changed files with 739 additions and 0 deletions

View File

@ -0,0 +1,121 @@
#include "window_manager.h"
#include "desktop_hander.h"
#include "input_manager.h"
#include "tft_handler.h"
#include "GLOBALS.h"
WindowManager* wm; // Globals
Desktop_Hander* dh;
InputManager* im;
TFT_Handler* th;
DISPLAY_STATE* _display_state;
CLICK_EVENT* _click_event;
uint32_t totalHeap = ESP.getHeapSize();
uint32_t freeHeap = ESP.getFreeHeap();
uint32_t usedHeap = totalHeap - freeHeap;
float percentUsed = (float)usedHeap * 100.0 / totalHeap;
TaskHandle_t Task1;
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
_display_state = new DISPLAY_STATE();
_display_state->update_display.store(false);
_click_event = new CLICK_EVENT();
_click_event->event = CLICK_EVENTS::NONE;
wm = new WindowManager();
dh = new Desktop_Hander();
im = new InputManager();
th = new TFT_Handler();
th->init(_display_state);
im->init(_display_state, th, _click_event);
dh->init(th);
wm->init(th);
delay(500);
// Create InputManager thread
xTaskCreatePinnedToCore(
task1, /* Task function. */
"task1", /* name of task. */
10000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
&Task1, /* Task handle to keep track of created task */
0); /* pin task to core 0 */
delay(500);
Window win1 = {
.id = 0,
.x = 10,
.y = 30,
.width = 350,
.height = 250,
.background_color = 0xbdf7, // Blue
.foreground_color = COL_DARK_BLUE, // White
.focused = false,
.title = "Hello Worl!",
.window_actions = {},
};
wm->create_window(win1);
Window win2 = {
.id = 1,
.x = 30,
.y = 50,
.width = 350,
.height = 250,
.background_color = 0xbdf7, // Blue
.foreground_color = COL_DARK_BLUE, // White
.focused = true,
.title = "lmao",
.window_actions = {},
};
wm->create_window(win2);
}
void loop() {
totalHeap = ESP.getHeapSize();
freeHeap = ESP.getFreeHeap();
usedHeap = totalHeap - freeHeap;
percentUsed = (float)usedHeap * 100.0 / totalHeap;
int start = millis();
int id = wm->click_event(*_click_event);
if (id >= 0)
_display_state->update_display.store(true);
_click_event->event = CLICK_EVENTS::NONE;
if (_display_state->update_display.load()) {
dh->re_draw_desktop();
wm->draw_all();
im->draw_button();
_display_state->update_display.store(false);
}
int stop = millis();
//Serial.print("Total mem: "); Serial.println(ESP.getHeapSize());
//Serial.print("Mem left: "); Serial.println(ESP.getFreeHeap());
//Serial.print("Mem % used: "); Serial.println(percentUsed);
//Serial.println(stop - start);
delay(50);
}
void task1(void * pvParameters) {
for(;;) {
im->update();
vTaskDelay(1);
}
}

18
Desktop_Test/GLOBALS.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <atomic>
enum CLICK_EVENTS {
NONE = 0,
LEFT_CLICK = 1,
RIGHT_CLICK = 2,
};
struct CLICK_EVENT {
int x;
int y;
CLICK_EVENTS event;
};
struct DISPLAY_STATE {
std::atomic<bool> update_display;
};

10
Desktop_Test/desktop.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include "WString.h"
struct Desktop_Item {
int id;
int place_x;
int place_y;
int icon_size_x;
int icon_size_y;
};

View File

@ -0,0 +1,59 @@
#include "desktop_hander.h"
#include "tft_handler.h"
void Desktop_Hander::init(TFT_Handler* th) {
tft = th;
for (int i; i < 5; i++) {
Desktop_Item di = {
.id = i,
.place_x = i * 50 + 1,
.place_y = 10,
.icon_size_x = 45,
.icon_size_y = 50,
};
items.push_back(di);
}
re_draw_desktop();
}
void Desktop_Hander::re_draw_desktop() {
draw_background(0x0410);
draw_task_bar(0xbdf7);
for (const auto& item : items) {
draw_desktop_Item(item);
}
}
void Desktop_Hander::draw_background(int color) {
tft->fill_screen(color);
}
void Desktop_Hander::draw_task_bar(int color) {
// bar
tft->draw_box(0, 293, 480, 40, color);
tft->draw_line(0, 294, 480, 294, 0xffff);
// Outer shadow
tft->draw_box(5, 297, 53, 21, 0x0000);
// Inner shadow
tft->draw_box(6, 298, 51, 19, 0x8410);
// Highlight
tft->draw_box(5, 297, 51, 19, 0xffff);
// Button
tft->draw_box(6, 298, 50, 18, color);
}
void Desktop_Hander::draw_desktop_Item(Desktop_Item desktop_item) {
tft->draw_box(desktop_item.place_x, desktop_item.place_y, desktop_item.icon_size_x, desktop_item.icon_size_y, 0x4648);
}
int Desktop_Hander::click_event(CLICK_EVENT event) {
return 0;
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "desktop.h"
#include "tft_handler.h"
#include <vector>
#include "GLOBALS.h"
class Desktop_Hander {
private:
TFT_Handler* tft;
std::vector<Desktop_Item> items;
public:
void init(TFT_Handler* th);
void re_draw_desktop();
void draw_background(int color);
void draw_task_bar(int color);
void draw_desktop_Item(Desktop_Item desktop_item);
int click_event(CLICK_EVENT event);
};

9
Desktop_Test/icons.h Normal file
View File

@ -0,0 +1,9 @@
#pragma once
struct Mouse_Icon {
int x;
int y;
int size_x;
int size_y;
int color;
};

View File

@ -0,0 +1,95 @@
#include "GLOBALS.h"
#include "tft_handler.h"
#include "HardwareSerial.h"
#include "input_manager.h"
void InputManager::init(DISPLAY_STATE* display_state, TFT_Handler* tf, CLICK_EVENT* event) {
_display_state = display_state;
_event = event;
_tf = tf;
mi = {
.x = 5,
.y = 5,
.size_x = 4,
.size_y = 4,
.color = 0x0000,
};
for (int i = 0; i < NUM_BUTTONS; i++) {
pinMode(BUTTON_PINS[i], INPUT_PULLUP);
}
}
void InputManager::update() {
for (int i = 0; i < NUM_BUTTONS; i++) {
int reading = digitalRead(BUTTON_PINS[i]);
// Check if button state changed
if (reading != lastButtonState[i]) {
lastDebounceTime[i] = millis();
}
// Only register change after debounce delay
if ((millis() - lastDebounceTime[i]) > DEBOUNCE_DELAY) {
if (reading != buttonState[i]) {
buttonState[i] = reading;
// Button was pressed (went from HIGH to LOW)
if (buttonState[i] == LOW) {
// Add your button handling code here
handle_button_press(i);
}
}
}
lastButtonState[i] = reading;
}
}
void InputManager::handle_button_press(int buttonIndex) {
// Add your custom logic here for each button
switch(buttonIndex) {
case 0:
if (mi.x > 480)
mi.x = 479;
else
mi.x += 5;
break;
case 1:
if (mi.y > 320)
mi.y = 319;
else
mi.y += 5;
break;
case 2:
if (mi.y < 0)
mi.y = 1;
else
mi.y -= 5;
break;
case 3:
if (mi.x < 0)
mi.x = 1;
else
mi.x -= 5;
break;
case 4:
_event->event = CLICK_EVENTS::LEFT_CLICK;
_event->x = mi.x;
_event->y = mi.y;
break;
case 5:
break;
}
_display_state->update_display.store(true);
}
void InputManager::draw_button() {
_tf->draw_box(mi.x, mi.y, mi.size_x, mi.size_y, mi.color);
}
bool InputManager::are_buttons_pressed(int btn1, int btn2) {
return (buttonState[btn1] == LOW && buttonState[btn2] == LOW);
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "tft_handler.h"
#include "GLOBALS.h"
#include "icons.h"
#include <Arduino.h>
class InputManager {
private:
int BUTTON_PINS[6] = {4, 5, 6, 7, 17, 16};
static constexpr int NUM_BUTTONS = 6;
const unsigned long DEBOUNCE_DELAY = 25; // milliseconds
unsigned long lastDebounceTime[NUM_BUTTONS] = {0};
int lastButtonState[NUM_BUTTONS] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
int buttonState[NUM_BUTTONS] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
DISPLAY_STATE* _display_state;
CLICK_EVENT* _event;
TFT_Handler* _tf;
Mouse_Icon mi;
void handle_button_press(int buttonIndex);
bool are_buttons_pressed(int btn1, int btn2);
public:
void init(DISPLAY_STATE* display_state, TFT_Handler* tf, CLICK_EVENT* event);
void update();
void draw_button();
};

View File

@ -0,0 +1,49 @@
#include "tft_handler.h"
void TFT_Handler::init(DISPLAY_STATE* display_state) {
_display_state = display_state;
tft.setColorDepth(16);
tft.init();
tft.fillScreen(0x0000);
}
void TFT_Handler::draw_box(int x, int y, int size_x, int size_y, int color) {
tft.startWrite();
tft.setRotation(3);
tft.fillRect(x, y, size_x, size_y, color);
tft.endWrite();
}
void TFT_Handler::draw_rect(int x, int y, int size_x, int size_y, int thickness, int color) {
tft.startWrite();
tft.setRotation(3);
tft.drawRect(x, y, size_x, size_y, color);
tft.endWrite();
}
void TFT_Handler::draw_line(int x1, int y1, int x2, int y2, int color) {
tft.drawLine(x1, y1, x2, y2, color);
}
void TFT_Handler::draw_text(int x, int y, std::string str) {
tft.startWrite();
tft.setTextColor(TFT_WHITE);
tft.setTextSize(2);
tft.drawString(str.c_str(), x, y);
tft.endWrite();
}
void TFT_Handler::fill_screen(int color) {
tft.startWrite();
tft.fillScreen(color);
tft.endWrite();
}

View File

@ -0,0 +1,63 @@
#pragma once
#include <LovyanGFX.hpp>
#include "GLOBALS.h"
#include <string>
#define MAX_X 480
#define MAX_Y 320
// https://github.com/lovyan03/LovyanGFX/issues/714#issuecomment-2968349655
// https://github.com/lovyan03/LovyanGFX/issues/734
class LGFX : public lgfx::LGFX_Device {
public:
lgfx::Panel_ILI9488 panel;
lgfx::Bus_SPI spi;
LGFX() {
// ---- SPI BUS CONFIG ----
{
auto cfg = spi.config(); // *** GET STRUCT ***
cfg.spi_host = SPI2_HOST;
cfg.pin_sclk = 12;
cfg.pin_mosi = 11;
cfg.pin_dc = 21;
cfg.pin_miso = -1;
cfg.freq_write = 40000000;
cfg.freq_read = 16000000;
spi.config(cfg); // *** APPLY CONFIG ***
}
// ---- PANEL CONFIG ----
{
auto cfg = panel.config(); // *** GET STRUCT ***
cfg.pin_cs = 10;
cfg.pin_rst = 15;
cfg.memory_width = 320;
cfg.memory_height = 480;
cfg.panel_width = 320;
cfg.panel_height = 480;
cfg.offset_x = 0;
cfg.offset_y = 0;
panel.config(cfg); // *** APPLY CONFIG ***
}
panel.setBus(&spi);
setPanel(&panel);
}
};
class TFT_Handler {
public:
LGFX tft;
DISPLAY_STATE* _display_state;
void init(DISPLAY_STATE* display_state);
void draw_box(int x, int y, int size_x, int size_y, int color);
void draw_rect(int x, int y, int size_x, int size_y, int thickness, int color);
void draw_line(int x1, int y1, int x2, int y2, int color);
void draw_text(int x, int y, std::string str);
void fill_screen(int color);
};

22
Desktop_Test/window.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <string>
#include <vector>
enum class WindowAction {
EXIT = 1,
FILE = 2,
HELP = 3,
};
struct Window {
int id;
int x;
int y;
int width;
int height;
int background_color;
int foreground_color;
bool focused;
std::string title;
std::vector<WindowAction> window_actions;
};

View File

@ -0,0 +1,99 @@
#include "HardwareSerial.h"
#include "GLOBALS.h"
#include "window_manager.h"
#include <algorithm>
void WindowManager::init(TFT_Handler* th) {
tft = th;
}
void WindowManager::create_window(Window window) {
windows.push_back(window);
draw_window(window);
}
void WindowManager::close_window(int window_id) {
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (it->id == window_id) {
windows.erase(it);
break;
}
}
draw_all();
}
void WindowManager::draw_all() {
for (const auto& win : windows) {
draw_window(win);
}
}
void WindowManager::draw_window(Window window) {
// Outer shadow
//tft->draw_box(window.x, window.y, window.width + 2, window.height + 2, 0x0000);
tft->draw_rect(window.x, window.y, window.width + 2, window.height + 2, 1, 0x0000);
// Inner shadow
tft->draw_rect(window.x, window.y, window.width + 1, window.height + 1, 1, 0x8410);
// Highlight
tft->draw_rect(window.x - 1, window.y - 1, window.width + 1, window.height + 1, 1, 0xffff);
// Window
tft->draw_box(window.x, window.y, window.width, window.height, window.background_color);
// Decorations
tft->draw_box(window.x + 3, window.y + 3, window.width - 6, 30, window.foreground_color);
// Close button
tft->draw_box(window.x + 6, window.y + 5, 25, 25, COL_RED);
// Minimize button
tft->draw_box(window.x + 6 + 30, window.y + 5, 25, 25, COL_LIGHT_GREY);
// Window title
tft->draw_text(window.x + 6 + 65, window.y + 10, window.title.c_str());
// Window content
tft->draw_box(window.x + 3, window.y + 30 + 5, window.width - 6, window.height - 7 - 30, COL_WHITE);
}
int WindowManager::click_event(CLICK_EVENT event) {
int id = -1;
if (event.event == CLICK_EVENTS::NONE) {
return -1;
}
// Iterate BACKWARDS - last window is on top
for (int i = windows.size() - 1; i >= 0; i--) {
Window& window = windows[i];
if (event.event != CLICK_EVENTS::LEFT_CLICK)
continue;
if (event.x >= window.x && event.x <= (window.x + window.width)) {
if (event.y >= window.y && event.y <= (window.y + window.height)) {
if (window.focused)
break;
// Unfocus all windows
for (size_t j = 0; j < windows.size(); j++) {
windows[j].focused = false;
}
// Focus this window
window.focused = true;
id = window.id;
// Move window to end (top of z-order)
auto it = windows.begin() + i;
std::rotate(it, it + 1, windows.end());
}
}
}
return id;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "window.h"
#include "tft_handler.h"
#include <vector>
#include "GLOBALS.h"
#define COL_WHITE 0xffff
#define COL_BLACK 0x0000
#define COL_GREY 0xbdf7
#define COL_LIGHT_GREY 0x8410
#define COL_DARK_BLUE 0x0014
#define COL_RED 0xf800
class WindowManager {
private:
TFT_Handler* tft; // Pointer to shared TFT handler
std::vector<Window> windows; // Track all windows
public:
void init(TFT_Handler* th);
void create_window(Window window);
void close_window(int window_id);
void draw_all(); // Redraw all windows
void draw_window(Window window);
int click_event(CLICK_EVENT event);
};

View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/.idea.PocketPutrMessageService.iml
/modules.xml
/projectSettingsUpdater.xml
/contentModel.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PocketPutrMessageService", "PocketPutrMessageService\PocketPutrMessageService.csproj", "{265B3E5A-541D-4B84-97B7-18A0F3D49272}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{265B3E5A-541D-4B84-97B7-18A0F3D49272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{265B3E5A-541D-4B84-97B7-18A0F3D49272}.Debug|Any CPU.Build.0 = Debug|Any CPU
{265B3E5A-541D-4B84-97B7-18A0F3D49272}.Release|Any CPU.ActiveCfg = Release|Any CPU
{265B3E5A-541D-4B84-97B7-18A0F3D49272}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,35 @@
using System.Net;
using System.Text;
namespace PocketPutrMessageService;
public class ClientService
{
public static void Run()
{
using HttpListener listener = new();
listener.Prefixes.Add("http://localhost:8001/");
listener.Start();
Console.WriteLine("Listening on port 8001...");
while (true)
{
HttpListenerContext context = listener.GetContext();
HttpListenerRequest req = context.Request;
Console.WriteLine($"Received request for {req.Url}");
using HttpListenerResponse resp = context.Response;
resp.Headers.Set("Content-Type", "text/plain");
const string data = "Hello there!";
byte[] buffer = Encoding.UTF8.GetBytes(data);
resp.ContentLength64 = buffer.Length;
using Stream ros = resp.OutputStream;
ros.Write(buffer, 0, buffer.Length);
}
}
}

View File

@ -0,0 +1,6 @@
namespace PocketPutrMessageService;
public class DatabaseService
{
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>false</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<RetainVMGarbageCollection>false</RetainVMGarbageCollection>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
// See https://aka.ms/new-console-template for more information
using PocketPutrMessageService;
Console.WriteLine("Hello, World!");
ClientService clientService = new ClientService();
ClientService.Run();

View File

@ -0,0 +1,7 @@
{
"sdk": {
"version": "9.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}