├── images └── OpenMenuOS_GIF.gif ├── library.properties ├── examples └── OpenMenuOS_Simple │ ├── images.h │ └── OpenMenuOS_Simple.ino ├── docs ├── index.md ├── README.md ├── examples.md └── api.md ├── src ├── OpenMenuOS_images.h └── OpenMenuOS.h ├── README.MD └── LICENSE.txt /images/OpenMenuOS_GIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Young-Maker/OpenMenuOS/HEAD/images/OpenMenuOS_GIF.gif -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=OpenMenuOS 2 | version=3.1.0 3 | author=Loic Daigle 4 | maintainer=Loic Daigle 5 | sentence=Library for easily creating menus on TFT displays for ESP32 and ESP8266. 6 | paragraph=This library simplifies creating and displaying menus on TFT_eSPI supported displays for ESP32 and ESP8266 boards. 7 | category=Display 8 | url=https://github.com/The-Young-Maker/OpenMenuOS 9 | architectures=* 10 | includes=OpenMenuOS.h 11 | depends=TFT_eSPI (>=2.5.0) -------------------------------------------------------------------------------- /examples/OpenMenuOS_Simple/images.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGES_H 2 | #define IMAGES_H 3 | 4 | const uint8_t Menu_icon_1[] = { 5 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 6 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 7 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xff, 0x59, 0xce, 0x18, 0xc6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 8 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8a, 0x52, 0x20, 0x00, 0x00, 0x00, 0x41, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 9 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x34, 0xa5, 0x39, 0xce, 0xd3, 0x9c, 0x41, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 10 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0xa4, 0x21, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 11 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0xa4, 0x21, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 12 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0xa4, 0x21, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 13 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0xa4, 0x21, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 14 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0xa4, 0x21, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 15 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0xa4, 0x21, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 16 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xde, 0x18, 0xc6, 0xcf, 0x7b, 0x21, 0x08, 0x18, 0xc6, 0x18, 0xc6, 0x9e, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 17 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0xc6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 18 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 19 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 20 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 21 | }; 22 | const size_t Menu_icon_1_size {sizeof(Menu_icon_1) / sizeof(Menu_icon_1[0])}; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # OpenMenuOS Documentation Index 2 | 3 | Welcome to the OpenMenuOS documentation! Choose the resource that best fits your needs: 4 | 5 | ## 📚 Documentation Files 6 | 7 | ### 🚀 [Getting Started (README.md)](README.md) 8 | **Perfect for:** Arduino IDE users, quick setup 9 | **Contains:** Installation guide, basic usage, hardware setup, key features overview 10 | 11 | ### 🔧 [Complete API Reference (api.md)](api.md) 12 | **Perfect for:** Developers, detailed implementation 13 | **Contains:** All classes, methods, parameters, return values, and advanced configurations 14 | 15 | ### 💡 [Code Examples (examples.md)](examples.md) 16 | **Perfect for:** Learning by example, common patterns 17 | **Contains:** Ready-to-use code snippets, real-world implementations, best practices 18 | 19 | ## 🎯 Quick Navigation 20 | 21 | ### New to OpenMenuOS? 22 | 1. Start with [README.md](README.md) for basic setup 23 | 2. Try the examples in [examples.md](examples.md) 24 | 3. Reference [api.md](api.md) for advanced features 25 | 26 | ### Looking for specific features? 27 | - **Popup System**: [README.md](README.md#popupmanager-system) → [api.md](api.md#popupmanager-class) 28 | - **Menu Creation**: [examples.md](examples.md#menu-examples) → [api.md](api.md#screen-classes) 29 | - **Settings Management**: [examples.md](examples.md#settings-examples) → [api.md](api.md#settings-system) 30 | - **Input Handling**: [examples.md](examples.md#input-handling-examples) → [api.md](api.md#input-configuration) 31 | - **Custom Screens**: [examples.md](examples.md#custom-screen-examples) → [api.md](api.md#customscreen) 32 | 33 | ### Common Tasks 34 | - **First Setup**: [README.md](README.md#quick-start) 35 | - **Add Popups**: [examples.md](examples.md#popup-examples) 36 | - **Create Menus**: [examples.md](examples.md#menu-examples) 37 | - **Handle Settings**: [examples.md](examples.md#settings-examples) 38 | - **Troubleshooting**: [README.md](README.md#troubleshooting) 39 | 40 | ## 🔍 Search Tips 41 | 42 | ### In Arduino IDE: 43 | - Use `Ctrl+F` to search within documentation files 44 | - Look for `###` headings for specific topics 45 | - Code examples are in \`\`\`cpp blocks 46 | 47 | ### Key Search Terms: 48 | - `PopupManager` - For popup/dialog functionality 49 | - `MenuScreen` - For menu creation 50 | - `SettingsScreen` - For settings management 51 | - `CustomScreen` - For custom drawing 52 | - `setupButtons` - For input configuration 53 | - `begin()` - For initialization 54 | 55 | ## 📋 File Descriptions 56 | 57 | | File | Purpose | Best For | 58 | |------|---------|----------| 59 | | `README.md` | Quick start guide with Arduino IDE focus | Beginners, quick reference | 60 | | `api.md` | Complete API documentation | Developers, detailed implementation | 61 | | `examples.md` | Practical code examples and patterns | Learning, common use cases | 62 | | `index.md` | This navigation guide | Finding the right documentation | 63 | 64 | ## 🆘 Need Help? 65 | 66 | 1. **Check Troubleshooting**: [README.md](README.md#troubleshooting) 67 | 2. **Browse Examples**: [examples.md](examples.md) 68 | 3. **Search API Docs**: [api.md](api.md) 69 | 4. **GitHub Issues**: [Report bugs or ask questions](https://github.com/The-Young-Maker/OpenMenuOS/issues) 70 | 71 | ## 📄 License & Credits 72 | 73 | **License**: MIT License - Free for personal and commercial use 74 | **Author**: The Young Maker 75 | **Version**: 3.1.0 76 | **GitHub**: https://github.com/The-Young-Maker/OpenMenuOS 77 | 78 | --- 79 | 80 | **Happy coding with OpenMenuOS! 🎉** 81 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # OpenMenuOS Documentation 2 | 3 | Welcome to the OpenMenuOS documentation! This library provides a professional menu system for Arduino projects using TFT displays. 4 | 5 | ## Quick Links 6 | 7 | - 📚 **[Complete API Reference](api.md)** - Detailed API documentation 8 | - 🚀 **[Getting Started](#getting-started)** - Quick setup guide 9 | - 💡 **[Examples](#examples)** - Code examples and tutorials 10 | - 🛠️ **[Hardware Setup](#hardware-setup)** - Wiring and hardware requirements 11 | 12 | ## Getting Started 13 | 14 | ### Installation 15 | Install OpenMenuOS through the Arduino Library Manager: 16 | 1. Open Arduino IDE 17 | 2. Go to `Tools` → `Manage Libraries...` 18 | 3. Search for "OpenMenuOS" 19 | 4. Click `Install` 20 | 21 | ### Basic Usage 22 | ```cpp 23 | #include "OpenMenuOS.h" 24 | 25 | OpenMenuOS menu(19, -1, 2); // up, down, select pins 26 | MenuScreen mainMenu("Main Menu"); 27 | 28 | void setup() { 29 | mainMenu.addItem("Settings", nullptr, showSettings); 30 | menu.begin(&mainMenu); 31 | } 32 | 33 | void loop() { 34 | menu.loop(); 35 | } 36 | 37 | void showSettings() { 38 | PopupManager::showInfo("Settings would open here", "Settings"); 39 | } 40 | ``` 41 | 42 | ## Key Features 43 | 44 | ### 🎛️ Professional Menu System 45 | - Multi-level menus with unlimited depth 46 | - Responsive design for any display size 47 | - Customizable themes and styling 48 | - Smooth animations and transitions 49 | 50 | ### 💬 Advanced PopupManager 51 | - 5 popup types: Info, Success, Warning, Error, Question 52 | - Auto-close functionality 53 | - Encoder and button support 54 | - Visual feedback and animations 55 | 56 | ### ⚙️ Comprehensive Settings 57 | - Boolean, Range, Option, and Subscreen settings 58 | - Automatic EEPROM/Preferences storage 59 | - Real-time value updates 60 | - Animated controls 61 | 62 | ### 🎮 Multiple Input Methods 63 | - Button support with debouncing 64 | - Rotary encoder with interrupts 65 | - Configurable pin assignments 66 | - Long-press detection 67 | 68 | ## Hardware Setup 69 | 70 | ### Minimum Requirements 71 | - **ESP32** or **ESP8266** microcontroller 72 | - **TFT display** supported by TFT_eSPI 73 | - **3 GPIO pins** for buttons (optional encoder) 74 | 75 | ### Recommended Setup 76 | ``` 77 | ESP32/ESP8266 TFT Display 78 | GND ←→ GND 79 | 3.3V ←→ VCC 80 | GPIO_X ←→ CS 81 | GPIO_Y ←→ DC 82 | GPIO_Z ←→ MOSI 83 | ...etc (see TFT_eSPI docs) 84 | 85 | Buttons: 86 | GPIO_19 ←→ UP button 87 | GPIO_2 ←→ SELECT button 88 | 89 | Encoder (optional): 90 | GPIO_5 ←→ CLK 91 | GPIO_2 ←→ DT 92 | GPIO_19 ←→ SW (button) 93 | ``` 94 | 95 | ## Examples 96 | 97 | ### Simple Menu 98 | ```cpp 99 | MenuScreen mainMenu("Device"); 100 | mainMenu.addItem("LED Control", nullptr, toggleLED); 101 | mainMenu.addItem("Settings", &settingsScreen); 102 | ``` 103 | 104 | ### Popup Notifications 105 | ```cpp 106 | // Success notification (auto-closes) 107 | PopupManager::showSuccess("Data saved successfully!"); 108 | 109 | // Question dialog 110 | PopupResult result = PopupManager::showQuestion("Delete all data?", "Confirm"); 111 | if (result == PopupResult::OK) { 112 | // User confirmed 113 | } 114 | ``` 115 | 116 | ### Settings Configuration 117 | ```cpp 118 | SettingsScreen settings("Device Settings"); 119 | settings.addBooleanSetting("WiFi", true); 120 | settings.addRangeSetting("Brightness", 0, 100, 75, "%"); 121 | 122 | const char* modes[] = {"Auto", "Manual", "Off"}; 123 | settings.addOptionSetting("Mode", modes, 3, 0); 124 | ``` 125 | 126 | ### Custom Screens 127 | ```cpp 128 | CustomScreen aboutScreen("About"); 129 | aboutScreen.customDraw = []() { 130 | canvas.fillScreen(TFT_BLACK); 131 | canvas.setTextColor(TFT_WHITE); 132 | canvas.drawString("Custom Content", 10, 50); 133 | canvas.fillCircle(100, 100, 20, TFT_BLUE); 134 | }; 135 | ``` 136 | 137 | ## Advanced Features 138 | 139 | ### Performance Optimization 140 | ```cpp 141 | menu.setOptimizeDisplayUpdates(true); // Enable frame comparison 142 | menu.setAnimation(false); // Disable for better performance 143 | ``` 144 | 145 | ### Visual Customization 146 | ```cpp 147 | menu.useStylePreset("Rabbit_R1"); // Built-in theme 148 | menu.setSelectionBorderColor(TFT_CYAN); // Custom colors 149 | menu.setScrollbar(true); // Enable scrollbar 150 | ``` 151 | 152 | ### Input Configuration 153 | ```cpp 154 | menu.setEncoderPin(5, 2); // Enable encoder support 155 | menu.setButtonsMode("High"); // Button logic level 156 | ``` 157 | 158 | ## Support 159 | 160 | - 📖 **Documentation**: Complete API reference in [api.md](api.md) 161 | - 🐛 **Issues**: Report bugs on [GitHub Issues](https://github.com/The-Young-Maker/OpenMenuOS/issues) 162 | - 💡 **Feature Requests**: Suggest improvements on GitHub 163 | - ⭐ **Star** the repository if you find it useful! 164 | 165 | ## License 166 | 167 | MIT License - free for personal and commercial use. 168 | 169 | --- 170 | 171 | **OpenMenuOS v3.1.0** - Professional Menu System for Arduino 172 | Created by The Young Maker 173 | -------------------------------------------------------------------------------- /examples/OpenMenuOS_Simple/OpenMenuOS_Simple.ino: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // INCLUDES 3 | //============================================================================== 4 | #include "OpenMenuOS.h" // Include the OpenMenuOS library 5 | #include "images.h" // Include custom images 6 | 7 | //============================================================================== 8 | // MENU SCREEN DEFINITIONS 9 | //============================================================================== 10 | // Create an instance of the OpenMenuOS class with button pins 11 | // Use -1 for buttons you don't have 12 | // OpenMenuOS menu(2, 5, 19); // btn_up, btn_down, btn_sel 13 | OpenMenuOS menu; // btn_up, btn_down, btn_sel 14 | 15 | // Create menu screens 16 | MenuScreen mainMenu("Main Menu"); 17 | MenuScreen testScreen("Test Menu"); 18 | CustomScreen customScreen("Custom Screen"); 19 | SettingsScreen settingsScreen("Settings"); 20 | SettingsScreen speakerSettingsScreen("Advanced Settings"); 21 | 22 | //============================================================================== 23 | // CALLBACK FUNCTIONS 24 | //============================================================================== 25 | // Example action callback for redirection 26 | void redirectToCustomScreen() 27 | { 28 | menu.redirectToScreen(&customScreen); 29 | } 30 | 31 | // Popup demonstration callbacks 32 | void showInfoPopup() 33 | { 34 | PopupManager::showInfo("This is an information message!", "Info Demo"); 35 | } 36 | 37 | void showSuccessPopup() 38 | { 39 | PopupManager::showSuccess("Operation completed successfully!"); 40 | } 41 | 42 | void showWarningPopup() 43 | { 44 | PopupManager::showWarning("This action cannot be undone", "Warning!"); 45 | } 46 | 47 | void showErrorPopup() 48 | { 49 | PopupManager::showError("Failed to save settings", "Error"); 50 | } 51 | 52 | void showQuestionPopup() 53 | { 54 | PopupResult result = PopupManager::showQuestion("Are you sure you want to delete all data?", "Confirm Delete"); 55 | // Note: Result will be handled in the main loop via PopupManager::update() 56 | } 57 | 58 | void showCustomPopup() 59 | { 60 | PopupConfig config; 61 | config.title = "Custom Popup"; 62 | config.message = "This is a custom popup with auto-close feature enabled. It will close automatically after 5 seconds."; 63 | config.type = PopupType::INFO; 64 | config.autoClose = true; 65 | config.autoCloseDelay = 5000; // 5 seconds 66 | config.customColor = 0x7E0; // Custom green color 67 | PopupManager::show(config); 68 | } 69 | 70 | //============================================================================== 71 | // SETUP 72 | //============================================================================== 73 | void setup() 74 | { 75 | // Initialize serial communication 76 | Serial.begin(921600); 77 | while (!Serial) 78 | { 79 | } 80 | Serial.println("Menu system started."); 81 | // Configure custom screen drawing 82 | customScreen.customDraw = []() 83 | { 84 | canvas.drawSmoothRoundRect(-15, 50, 40, 0, 50, 50, TFT_BLUE, TFT_BLACK); 85 | canvas.drawSmoothRoundRect(10, 10, 200, 100, 20, 5, TFT_ORANGE, TFT_BLACK); 86 | canvas.drawSmoothRoundRect(120, -25, 40, 0, 40, 40, TFT_DARKGREEN, TFT_BLACK); 87 | canvas.drawString("V" + String(menu.getLibraryVersion()), 10, 10); 88 | canvas.setTextColor(TFT_WHITE, TFT_BLACK); 89 | canvas.drawString("Press UP for popup demo", 10, 30); 90 | }; 91 | 92 | // Configure sound settings 93 | speakerSettingsScreen.addRangeSetting("Speaker Power", 1, 30, 5, "dB"); 94 | speakerSettingsScreen.addBooleanSetting("Sound", false); 95 | 96 | // Configure main settings 97 | settingsScreen.addBooleanSetting("Animations", true); // Toggle setting 98 | settingsScreen.addBooleanSetting("Animations", true); // Toggle setting 99 | settingsScreen.addBooleanSetting("Optimize Display Updates", true); // Toggle setting 100 | settingsScreen.addRangeSetting("Brightness", 0, 100, 75, "%"); // Range setting 101 | settingsScreen.addSubscreenSetting("Speaker", &speakerSettingsScreen); // Subscreen link 102 | 103 | // Add style options 104 | const char *styleOptions[] = {"Default", "Modern"}; 105 | settingsScreen.addOptionSetting("Style", styleOptions, 2, 1); // Option setting 106 | // Configure main menu 107 | mainMenu.addItem("Settings", &settingsScreen, nullptr, (const uint16_t *)Menu_icon_1); 108 | mainMenu.addItem(&testScreen); 109 | mainMenu.addItem("Redirect To Screen", nullptr, redirectToCustomScreen); 110 | mainMenu.addItem("Info Popup", nullptr, showInfoPopup); 111 | mainMenu.addItem("Success Popup", nullptr, showSuccessPopup); 112 | mainMenu.addItem("Warning Popup", nullptr, showWarningPopup); 113 | mainMenu.addItem("Error Popup", nullptr, showErrorPopup); 114 | mainMenu.addItem("Question Popup", nullptr, showQuestionPopup); // With encoder: rotate to select Yes/No, press to confirm 115 | mainMenu.addItem("Custom Popup", nullptr, showCustomPopup); 116 | // Configure test menu 117 | testScreen.addItem("First Test Page"); 118 | testScreen.addItem("Second Test Page"); 119 | testScreen.addItem("Third Test Page"); 120 | testScreen.addItem("Quick Info Popup", nullptr, showInfoPopup); // Popup from submenu 121 | testScreen.addItem(&customScreen); 122 | 123 | // Configure menu appearance and behavior 124 | menu.useStylePreset("Rabbit_R1"); // Available presets: "Default", "Rabbit_R1" OR the number of the preset (0, 1, 2, etc.) 125 | menu.setScrollbar(true); // Enable scrollbar 126 | menu.setScrollbarStyle(1); // Styles: 0 (Default), 1 (Modern) 127 | menu.setButtonsMode("low"); // Button active voltage: "High" or "Low" 128 | 129 | // Optional configurations 130 | // menu.setMenuStyle(1); // Styles: 0 (Default), 1 (Modern) // menu.setSelectionBorderColor(0xFFFF); // Set selection border color 131 | // menu.setSelectionFillColor(0x0000); // Set selection fill color 132 | menu.setEncoderPin(5, 2); // Configure encoder pins (also enables encoder support for popups) 133 | menu.setSelectPin(19); 134 | 135 | menu.setDisplayRotation(0); // Set display rotation 136 | 137 | // Initialize the menu system 138 | menu.begin(&mainMenu); // Parameter: Main menu object 139 | } 140 | 141 | //============================================================================== 142 | // MAIN LOOP 143 | //============================================================================== 144 | void loop() 145 | { 146 | // Handle popup interactions first 147 | PopupResult popupResult = PopupManager::update(); 148 | 149 | menu.loop(); // Handle menu logic 150 | 151 | // Handle popup results when user interacts with popup 152 | if (popupResult != PopupResult::NONE) 153 | { 154 | // Handle popup results 155 | switch (popupResult) 156 | { 157 | case PopupResult::OK: 158 | Serial.println("User clicked OK/Yes"); 159 | // Add your OK/Yes handling logic here 160 | break; 161 | case PopupResult::CANCEL: 162 | Serial.println("User clicked Cancel/No"); 163 | // Add your Cancel/No handling logic here 164 | break; 165 | default: 166 | break; 167 | } 168 | } 169 | 170 | // Update settings-based configurations 171 | menu.setAnimation(settingsScreen.getSettingValue("Animations")); 172 | menu.setOptimizeDisplayUpdates(settingsScreen.getSettingValue("Optimize Display Updates")); 173 | // menu.setMenuStyle(settingsScreen.getSettingValue("Style")); 174 | } -------------------------------------------------------------------------------- /src/OpenMenuOS_images.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENMENUOS_IMAGES_H 2 | #define OPENMENUOS_IMAGES_H 3 | 4 | const uint8_t Warning_icon[] = { 5 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x14, 0xf5, 0xf8, 0xfd, 0x72, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 6 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xf7, 0xf5, 0x7d, 0xff, 0xdb, 0xf6, 0x7d, 0xff, 0x51, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 7 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x92, 0xf4, 0x7d, 0xff, 0x34, 0xf5, 0x4e, 0xf3, 0xf7, 0xf5, 0x1c, 0xff, 0x6e, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 8 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x4d, 0xf3, 0xdb, 0xfe, 0x59, 0xfe, 0x4d, 0xeb, 0xea, 0xf2, 0xaf, 0xeb, 0x3d, 0xff, 0x75, 0xf5, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 9 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xcb, 0xfa, 0xf8, 0xf5, 0x5d, 0xff, 0xcf, 0xeb, 0xec, 0xf2, 0x6e, 0xeb, 0xeb, 0xf2, 0xf4, 0xf4, 0x7d, 0xff, 0x51, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 10 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xcf, 0xf3, 0x7d, 0xff, 0x95, 0xf5, 0xab, 0xfa, 0xf3, 0xf4, 0xdc, 0xfe, 0x0f, 0xec, 0xab, 0xfa, 0x7a, 0xfe, 0xba, 0xf6, 0x0c, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 11 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x0c, 0xf3, 0xba, 0xfe, 0x7a, 0xfe, 0xec, 0xf2, 0xab, 0xfa, 0xd7, 0xfd, 0xdf, 0xff, 0xb2, 0xf4, 0xab, 0xfa, 0x10, 0xec, 0x5d, 0xff, 0x95, 0xfd, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 12 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x34, 0xf5, 0x7d, 0xff, 0x51, 0xec, 0xab, 0xfa, 0xab, 0xfa, 0xb6, 0xfd, 0xdf, 0xff, 0x71, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0x76, 0xf5, 0x5c, 0xff, 0xaf, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 13 | 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x8e, 0xf3, 0x5d, 0xff, 0xd7, 0xf5, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x55, 0xf5, 0x9e, 0xff, 0x10, 0xec, 0xab, 0xfa, 0xab, 0xfa, 0x0c, 0xf3, 0x9a, 0xfe, 0x9a, 0xfe, 0x0c, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 14 | 0xab, 0xfa, 0xab, 0xfa, 0xcb, 0xfa, 0xf8, 0xf5, 0xfb, 0xfe, 0x6d, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x14, 0xed, 0x7e, 0xff, 0x6e, 0xeb, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x92, 0xec, 0x7e, 0xff, 0x92, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 15 | 0xab, 0xfa, 0xab, 0xfa, 0x92, 0xf4, 0x9d, 0xff, 0x92, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x72, 0xec, 0x3d, 0xff, 0x0c, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xd7, 0xf5, 0x3c, 0xff, 0x8e, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 16 | 0xab, 0xfa, 0x0c, 0xfb, 0x1c, 0xff, 0x79, 0xf6, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x2d, 0xf3, 0x75, 0xfd, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x6d, 0xf3, 0x1c, 0xff, 0xf7, 0xf5, 0xcb, 0xfa, 0xab, 0xfa, 17 | 0xab, 0xfa, 0xb6, 0xf5, 0x1c, 0xff, 0xcf, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xd0, 0xf3, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xf3, 0xf4, 0x7d, 0xff, 0x71, 0xf4, 0xab, 0xfa, 18 | 0x51, 0xf4, 0x5d, 0xff, 0xd3, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x14, 0xf5, 0x3c, 0xf7, 0x31, 0xf4, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x38, 0xf6, 0xfb, 0xfe, 0x2c, 0xf3, 19 | 0xf7, 0xfd, 0xdb, 0xf6, 0xcb, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x31, 0xf4, 0xf7, 0xf5, 0x8f, 0xeb, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0xab, 0xfa, 0x10, 0xec, 0x5d, 0xff, 0xb3, 0xfc, 20 | 0xd7, 0xfd, 0xfb, 0xf6, 0x10, 0xec, 0xaf, 0xf3, 0xaf, 0xf3, 0x6e, 0xf3, 0x6e, 0xf3, 0x6e, 0xf3, 0x8e, 0xf3, 0xce, 0xf3, 0xcf, 0xf3, 0xaf, 0xf3, 0x6d, 0xf3, 0x6d, 0xf3, 0x6d, 0xf3, 0x6d, 0xf3, 0x8f, 0xf3, 0x6e, 0xfb, 0x92, 0xf4, 0x5d, 0xff, 0x92, 0xfc, 21 | 0xef, 0xf3, 0x1c, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x3d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5d, 0xff, 0x5c, 0xff, 0x7d, 0xff, 0x59, 0xfe, 0xec, 0xfa, 22 | 0xab, 0xfa, 0x0c, 0xf3, 0x51, 0xfc, 0x92, 0xf4, 0x92, 0xf4, 0x92, 0xfc, 0x92, 0xfc, 0x92, 0xfc, 0x92, 0xfc, 0x92, 0xfc, 0x92, 0xf4, 0x72, 0xfc, 0x92, 0xfc, 0x92, 0xfc, 0x92, 0xfc, 0x92, 0xfc, 0x72, 0xfc, 0x92, 0xf4, 0x11, 0xf4, 0x0c, 0xfb, 0xab, 0xfa, 23 | }; 24 | const size_t Warning_icon_size {sizeof(Warning_icon) / sizeof(Warning_icon[0])}; 25 | 26 | const uint8_t Success_icon[] = { 27 | 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x50, 0x36, 0x72, 0x4e, 0x30, 0x36, 28 | 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x50, 0x36, 0xf6, 0x86, 0xff, 0xff, 0x72, 0x46, 29 | 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x50, 0x36, 0xf6, 0x86, 0xff, 0xff, 0xf7, 0x8e, 0x51, 0x36, 30 | 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x51, 0x36, 0xf6, 0x86, 0xff, 0xff, 0xf7, 0x86, 0x51, 0x3e, 0x0f, 0x26, 31 | 0x51, 0x3e, 0x94, 0x66, 0x51, 0x36, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x51, 0x3e, 0xf6, 0x86, 0xff, 0xff, 0xf6, 0x86, 0x51, 0x3e, 0x0f, 0x26, 0x0f, 0x26, 32 | 0x72, 0x4e, 0xff, 0xff, 0xf7, 0x8e, 0x51, 0x3e, 0x0f, 0x26, 0x51, 0x3e, 0xf7, 0x8e, 0xff, 0xff, 0xf6, 0x86, 0x51, 0x3e, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 33 | 0x30, 0x36, 0xf6, 0x7e, 0xff, 0xff, 0x17, 0x97, 0x93, 0x56, 0xf7, 0x8e, 0xff, 0xff, 0xf6, 0x86, 0x51, 0x36, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 34 | 0x0f, 0x26, 0x30, 0x36, 0xd6, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf6, 0x7e, 0x50, 0x36, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 35 | 0x0f, 0x26, 0x0f, 0x26, 0x30, 0x36, 0xd5, 0x76, 0xff, 0xff, 0xd6, 0x7e, 0x50, 0x36, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 36 | 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x30, 0x36, 0x51, 0x46, 0x30, 0x36, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 0x0f, 0x26, 37 | }; 38 | const size_t Success_icon_size {sizeof(Success_icon) / sizeof(Success_icon[0])}; 39 | 40 | const uint8_t Info_icon[] = { 41 | 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 42 | 0x3f, 0x45, 0xbf, 0x6d, 0xff, 0xbe, 0xff, 0xbe, 0x7f, 0x55, 0x3f, 0x45, 43 | 0x3f, 0x45, 0x5f, 0x8e, 0xbf, 0xe7, 0x9f, 0xe7, 0xbf, 0x6d, 0x3f, 0x45, 44 | 0x3f, 0x45, 0x9f, 0x5d, 0x7f, 0x9e, 0x7f, 0x9e, 0x7f, 0x55, 0x3f, 0x45, 45 | 0x5f, 0x4d, 0xdf, 0x6d, 0xff, 0x7d, 0xbf, 0x65, 0x3f, 0x45, 0x3f, 0x45, 46 | 0x9f, 0x5d, 0x3f, 0xcf, 0xbf, 0xef, 0xdf, 0xb6, 0x5f, 0x4d, 0x3f, 0x45, 47 | 0x9f, 0x5d, 0x5f, 0xcf, 0xff, 0xff, 0x7f, 0xdf, 0x9f, 0x5d, 0x3f, 0x45, 48 | 0x3f, 0x45, 0xdf, 0x75, 0x7f, 0xdf, 0x7f, 0xdf, 0x9f, 0x5d, 0x3f, 0x45, 49 | 0x3f, 0x45, 0xdf, 0x75, 0x7f, 0xdf, 0x7f, 0xdf, 0x9f, 0x5d, 0x3f, 0x45, 50 | 0x3f, 0x45, 0xdf, 0x75, 0x7f, 0xdf, 0x7f, 0xdf, 0x9f, 0x5d, 0x3f, 0x45, 51 | 0x5f, 0x4d, 0x5f, 0x8e, 0x9f, 0xe7, 0x9f, 0xe7, 0xdf, 0x6d, 0x5f, 0x4d, 52 | 0x9f, 0x5d, 0x5f, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xae, 0x9f, 0x5d, 53 | 0x9f, 0x5d, 0x3f, 0xcf, 0xdf, 0xf7, 0xdf, 0xf7, 0xbf, 0xae, 0x9f, 0x5d, 54 | 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 0x3f, 0x45, 55 | }; 56 | const size_t Info_icon_size {sizeof(Info_icon) / sizeof(Info_icon[0])}; 57 | 58 | #endif -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # OpenMenuOS Examples 2 | 3 | Quick reference guide with practical examples for common use cases. 4 | 5 | ## Basic Setup 6 | 7 | ### Minimal Setup 8 | ```cpp 9 | #include "OpenMenuOS.h" 10 | 11 | OpenMenuOS menu; 12 | 13 | void setup() { 14 | menu.begin(); 15 | menu.setupButtons(19, -1, 2, INPUT_PULLUP, LOW); 16 | 17 | menu.setMainMenuItems({"Option 1", "Option 2", "Settings"}); 18 | menu.createMainMenu(); 19 | } 20 | 21 | void loop() { 22 | menu.loop(); 23 | } 24 | ``` 25 | 26 | ### With Encoder Support 27 | ```cpp 28 | #include "OpenMenuOS.h" 29 | 30 | OpenMenuOS menu; 31 | 32 | void setup() { 33 | menu.begin(); 34 | menu.setupButtons(19, -1, 2, INPUT_PULLUP, LOW); 35 | menu.setEncoderPin(5, 4); // CLK, DT pins 36 | 37 | menu.setMainMenuItems({"LED Control", "Settings", "About"}); 38 | menu.createMainMenu(); 39 | } 40 | 41 | void loop() { 42 | menu.loop(); 43 | } 44 | ``` 45 | 46 | ## Menu Examples 47 | 48 | ### Simple Menu with Callbacks 49 | ```cpp 50 | MenuScreen mainMenu("Device Control"); 51 | 52 | void setup() { 53 | mainMenu.addItem("Toggle LED", nullptr, toggleLED); 54 | mainMenu.addItem("Show Info", nullptr, showDeviceInfo); 55 | mainMenu.addItem("Reset", nullptr, confirmReset); 56 | 57 | menu.addScreen(&mainMenu); 58 | menu.navigateToScreen(0); 59 | } 60 | 61 | void toggleLED() { 62 | static bool ledState = false; 63 | ledState = !ledState; 64 | digitalWrite(LED_BUILTIN, ledState); 65 | PopupManager::showSuccess(ledState ? "LED ON" : "LED OFF"); 66 | } 67 | 68 | void showDeviceInfo() { 69 | String info = "Device: ESP32\\n"; 70 | info += "Firmware: v1.0\\n"; 71 | info += "Free RAM: " + String(ESP.getFreeHeap()) + " bytes"; 72 | PopupManager::showInfo(info, "Device Information"); 73 | } 74 | 75 | void confirmReset() { 76 | PopupResult result = PopupManager::showQuestion( 77 | "This will reset all settings.\\nAre you sure?", 78 | "Confirm Reset" 79 | ); 80 | 81 | if (result.buttonIndex == 0) { // OK button 82 | // Perform reset 83 | PopupManager::showSuccess("System reset complete!"); 84 | } 85 | } 86 | ``` 87 | 88 | ### Nested Menu Structure 89 | ```cpp 90 | MenuScreen mainMenu("Main Menu"); 91 | MenuScreen wifiMenu("WiFi Settings"); 92 | MenuScreen deviceMenu("Device Settings"); 93 | 94 | void setup() { 95 | // Main menu 96 | mainMenu.addItem("WiFi", &wifiMenu); 97 | mainMenu.addItem("Device", &deviceMenu); 98 | mainMenu.addItem("About", nullptr, showAbout); 99 | 100 | // WiFi submenu 101 | wifiMenu.addItem("Connect", nullptr, connectWiFi); 102 | wifiMenu.addItem("Scan Networks", nullptr, scanNetworks); 103 | wifiMenu.addItem("Disconnect", nullptr, disconnectWiFi); 104 | 105 | // Device submenu 106 | deviceMenu.addItem("LED Control", nullptr, toggleLED); 107 | deviceMenu.addItem("Restart", nullptr, restartDevice); 108 | 109 | menu.addScreen(&mainMenu); 110 | menu.addScreen(&wifiMenu); 111 | menu.addScreen(&deviceMenu); 112 | menu.navigateToScreen(0); 113 | } 114 | ``` 115 | 116 | ## Settings Examples 117 | 118 | ### Basic Settings Screen 119 | ```cpp 120 | SettingsScreen settings("System Settings"); 121 | 122 | void setup() { 123 | settings.addBooleanSetting("WiFi Enabled", true); 124 | settings.addBooleanSetting("Auto Sleep", false); 125 | settings.addRangeSetting("Brightness", 0, 100, 75, "%"); 126 | settings.addRangeSetting("Volume", 0, 10, 5); 127 | 128 | const char* themes[] = {"Dark", "Light", "Auto"}; 129 | settings.addOptionSetting("Theme", themes, 3, 0); 130 | 131 | menu.addScreen(&settings); 132 | } 133 | 134 | // Access setting values 135 | void checkSettings() { 136 | bool wifiEnabled = settings.getBooleanValue(0); 137 | int brightness = settings.getRangeValue(2); 138 | int themeIndex = settings.getOptionValue(4); 139 | 140 | if (wifiEnabled) { 141 | // Enable WiFi 142 | } 143 | 144 | // Set display brightness 145 | setBrightness(brightness); 146 | } 147 | ``` 148 | 149 | ### Advanced Settings with Callbacks 150 | ```cpp 151 | SettingsScreen settings("Advanced Settings"); 152 | 153 | void setup() { 154 | settings.addBooleanSetting("Debug Mode", false); 155 | settings.addRangeSetting("Timeout", 5, 60, 30, "s"); 156 | 157 | // Add callback for when settings change 158 | settings.onSettingChanged = [](int index, SettingType type) { 159 | switch (index) { 160 | case 0: // Debug mode changed 161 | bool debugMode = settings.getBooleanValue(0); 162 | Serial.println("Debug mode: " + String(debugMode ? "ON" : "OFF")); 163 | break; 164 | 165 | case 1: // Timeout changed 166 | int timeout = settings.getRangeValue(1); 167 | setSystemTimeout(timeout * 1000); // Convert to milliseconds 168 | break; 169 | } 170 | }; 171 | } 172 | ``` 173 | 174 | ## Popup Examples 175 | 176 | ### Information Popups 177 | ```cpp 178 | void showStatus() { 179 | // Auto-closing info popup 180 | PopupManager::showInfo("System is running normally", "Status", 2000); 181 | } 182 | 183 | void showDetailedInfo() { 184 | String details = "System Status:\\n"; 185 | details += "• CPU: Normal\\n"; 186 | details += "• Memory: 78% free\\n"; 187 | details += "• Network: Connected\\n"; 188 | details += "• Sensors: All OK"; 189 | 190 | PopupManager::showInfo(details, "Detailed Status"); 191 | } 192 | ``` 193 | 194 | ### User Confirmation 195 | ```cpp 196 | void deleteAllData() { 197 | PopupResult result = PopupManager::showQuestion( 198 | "This will permanently delete all stored data.\\n\\nThis action cannot be undone.", 199 | "Delete All Data?", 200 | {"Delete", "Cancel"} 201 | ); 202 | 203 | if (result.buttonIndex == 0) { 204 | // User confirmed deletion 205 | eraseAllData(); 206 | PopupManager::showSuccess("All data deleted successfully"); 207 | } 208 | } 209 | 210 | void saveConfiguration() { 211 | std::vector options = {"Save", "Save & Exit", "Cancel"}; 212 | PopupResult result = PopupManager::showQuestion( 213 | "Save current configuration?", 214 | "Save Changes", 215 | options 216 | ); 217 | 218 | switch (result.buttonIndex) { 219 | case 0: // Save 220 | saveConfig(); 221 | PopupManager::showSuccess("Configuration saved"); 222 | break; 223 | 224 | case 1: // Save & Exit 225 | saveConfig(); 226 | menu.goToMainMenu(); 227 | break; 228 | 229 | case 2: // Cancel 230 | // Do nothing 231 | break; 232 | } 233 | } 234 | ``` 235 | 236 | ### Status Notifications 237 | ```cpp 238 | void performLongOperation() { 239 | PopupManager::showInfo("Processing, please wait...", "Working", 0); // No auto-close 240 | 241 | // Simulate long operation 242 | delay(3000); 243 | 244 | // Close working popup and show result 245 | PopupManager::close(); 246 | PopupManager::showSuccess("Operation completed successfully!"); 247 | } 248 | 249 | void handleErrors() { 250 | if (!wifiConnected) { 251 | PopupManager::showError("WiFi connection failed", "Network Error"); 252 | return; 253 | } 254 | 255 | if (batteryLow) { 256 | PopupManager::showWarning("Battery level is low (15%)", "Low Battery"); 257 | } 258 | 259 | // Continue with operation... 260 | } 261 | ``` 262 | 263 | ## Custom Screen Examples 264 | 265 | ### Information Display Screen 266 | ```cpp 267 | class InfoScreen : public CustomScreen { 268 | private: 269 | String deviceName; 270 | String firmwareVersion; 271 | String ipAddress; 272 | 273 | public: 274 | InfoScreen(const String& title) : CustomScreen(title) { 275 | customDraw = [this]() { this->drawInfo(); }; 276 | } 277 | 278 | void updateInfo(const String& name, const String& version, const String& ip) { 279 | deviceName = name; 280 | firmwareVersion = version; 281 | ipAddress = ip; 282 | } 283 | 284 | private: 285 | void drawInfo() { 286 | canvas.fillScreen(TFT_BLACK); 287 | canvas.setTextColor(TFT_WHITE); 288 | canvas.setTextDatum(MC_DATUM); 289 | 290 | int centerX = canvas.width() / 2; 291 | int y = 40; 292 | 293 | canvas.drawString("Device Information", centerX, y); 294 | y += 30; 295 | 296 | canvas.setTextColor(TFT_CYAN); 297 | canvas.drawString("Name: " + deviceName, centerX, y); 298 | y += 20; 299 | 300 | canvas.drawString("Firmware: " + firmwareVersion, centerX, y); 301 | y += 20; 302 | 303 | canvas.drawString("IP: " + ipAddress, centerX, y); 304 | y += 30; 305 | 306 | canvas.setTextColor(TFT_YELLOW); 307 | canvas.drawString("Press SELECT to return", centerX, y); 308 | } 309 | }; 310 | 311 | // Usage 312 | InfoScreen infoScreen("About Device"); 313 | infoScreen.updateInfo("My Device", "v1.2.3", "192.168.1.100"); 314 | menu.addScreen(&infoScreen); 315 | ``` 316 | 317 | ### Real-time Data Display 318 | ```cpp 319 | class SensorScreen : public CustomScreen { 320 | private: 321 | float temperature; 322 | float humidity; 323 | int lightLevel; 324 | unsigned long lastUpdate; 325 | 326 | public: 327 | SensorScreen(const String& title) : CustomScreen(title), lastUpdate(0) { 328 | customDraw = [this]() { this->drawSensors(); }; 329 | } 330 | 331 | void updateSensors(float temp, float hum, int light) { 332 | temperature = temp; 333 | humidity = hum; 334 | lightLevel = light; 335 | lastUpdate = millis(); 336 | } 337 | 338 | private: 339 | void drawSensors() { 340 | canvas.fillScreen(TFT_BLACK); 341 | canvas.setTextColor(TFT_WHITE); 342 | canvas.setTextDatum(TL_DATUM); 343 | 344 | // Title 345 | canvas.drawString("Sensor Readings", 10, 10); 346 | 347 | // Temperature 348 | canvas.setTextColor(TFT_RED); 349 | canvas.drawString("Temperature:", 10, 40); 350 | canvas.drawString(String(temperature, 1) + "°C", 120, 40); 351 | 352 | // Humidity 353 | canvas.setTextColor(TFT_BLUE); 354 | canvas.drawString("Humidity:", 10, 60); 355 | canvas.drawString(String(humidity, 1) + "%", 120, 60); 356 | 357 | // Light level 358 | canvas.setTextColor(TFT_YELLOW); 359 | canvas.drawString("Light:", 10, 80); 360 | canvas.drawString(String(lightLevel) + " lux", 120, 80); 361 | 362 | // Last update time 363 | canvas.setTextColor(TFT_GRAY); 364 | canvas.drawString("Updated: " + String((millis() - lastUpdate) / 1000) + "s ago", 10, 110); 365 | 366 | // Draw simple bar graphs 367 | drawBarGraph(10, 130, 100, 10, lightLevel, 1000, TFT_YELLOW); 368 | drawBarGraph(10, 150, 100, 10, (int)temperature, 50, TFT_RED); 369 | drawBarGraph(10, 170, 100, 10, (int)humidity, 100, TFT_BLUE); 370 | } 371 | 372 | void drawBarGraph(int x, int y, int width, int height, int value, int maxValue, uint16_t color) { 373 | // Background 374 | canvas.drawRect(x, y, width, height, TFT_WHITE); 375 | 376 | // Fill 377 | int fillWidth = map(value, 0, maxValue, 0, width - 2); 378 | canvas.fillRect(x + 1, y + 1, fillWidth, height - 2, color); 379 | } 380 | 381 | void readSensors() { 382 | // Simulate sensor readings 383 | temperature = 20 + (random(100) / 10.0); 384 | humidity = 40 + random(40); 385 | lightLevel = random(1000); 386 | lastUpdate = millis(); 387 | } 388 | }; 389 | 390 | // Usage 391 | SensorScreen sensorScreen("Live Sensors"); 392 | menu.addScreen(&sensorScreen); 393 | ``` 394 | 395 | ## Theme and Styling Examples 396 | 397 | ### Custom Theme 398 | ```cpp 399 | void setupCustomTheme() { 400 | // Use built-in theme as base 401 | menu.useStylePreset("Rabbit_R1"); 402 | 403 | // Customize colors 404 | menu.setSelectionBorderColor(TFT_CYAN); 405 | menu.setBackgroundColor(TFT_NAVY); 406 | 407 | // Configure scrollbar 408 | menu.setScrollbar(true); 409 | menu.setScrollbarColor(TFT_DARKGREY); 410 | 411 | // Configure animations 412 | menu.setAnimation(true); 413 | menu.setTextScroll(true); 414 | } 415 | ``` 416 | 417 | ### Performance-Optimized Setup 418 | ```cpp 419 | void setupForPerformance() { 420 | // Disable visual effects for better performance 421 | menu.setAnimation(false); 422 | menu.setTextScroll(false); 423 | menu.setScrollbar(false); 424 | 425 | // Enable frame comparison for ESP32 426 | #ifdef ESP32 427 | menu.setOptimizeDisplayUpdates(true); 428 | #else 429 | menu.setOptimizeDisplayUpdates(false); // ESP8266 with large displays 430 | #endif 431 | } 432 | ``` 433 | 434 | ## Input Handling Examples 435 | 436 | ### Button-Only Setup 437 | ```cpp 438 | void setup() { 439 | // Configure for 3-button operation 440 | menu.setupButtons( 441 | 19, // UP button pin 442 | 18, // DOWN button pin 443 | 2, // SELECT button pin 444 | INPUT_PULLUP, // Use internal pullup resistors 445 | LOW // Button pressed = LOW 446 | ); 447 | } 448 | ``` 449 | 450 | ### Encoder-Only Setup 451 | ```cpp 452 | void setup() { 453 | // Configure for encoder + button operation 454 | menu.setupButtons( 455 | -1, // No UP button 456 | -1, // No DOWN button 457 | 2, // SELECT button (encoder button) 458 | INPUT_PULLUP, 459 | LOW 460 | ); 461 | 462 | menu.setEncoderPin(5, 4); // CLK, DT pins 463 | } 464 | ``` 465 | 466 | ### Hybrid Input Setup 467 | ```cpp 468 | void setup() { 469 | // Both buttons and encoder 470 | menu.setupButtons(19, 18, 2, INPUT_PULLUP, LOW); 471 | menu.setEncoderPin(5, 4); 472 | 473 | // Users can navigate with either input method 474 | } 475 | ``` 476 | 477 | ## Advanced Features 478 | 479 | ### Auto-Save Settings 480 | ```cpp 481 | class AutoSaveSettings : public SettingsScreen { 482 | public: 483 | AutoSaveSettings(const String& title) : SettingsScreen(title) { 484 | onSettingChanged = [this](int index, SettingType type) { 485 | // Auto-save when any setting changes 486 | this->saveToEEPROM(); 487 | PopupManager::showSuccess("Settings saved", "Auto-Save", 1000); 488 | }; 489 | } 490 | }; 491 | ``` 492 | 493 | ### Conditional Menu Items 494 | ```cpp 495 | void updateMenuItems() { 496 | mainMenu.clearItems(); 497 | 498 | mainMenu.addItem("Status", nullptr, showStatus); 499 | 500 | if (wifiConnected) { 501 | mainMenu.addItem("Cloud Sync", nullptr, syncToCloud); 502 | mainMenu.addItem("Remote Control", &remoteMenu); 503 | } else { 504 | mainMenu.addItem("Connect WiFi", nullptr, connectWiFi); 505 | } 506 | 507 | if (isAdminMode) { 508 | mainMenu.addItem("Admin Panel", &adminMenu); 509 | } 510 | 511 | mainMenu.addItem("Settings", &settingsMenu); 512 | } 513 | ``` 514 | 515 | ### Progress Indication 516 | ```cpp 517 | void showProgress() { 518 | for (int i = 0; i <= 100; i += 10) { 519 | String message = "Processing... " + String(i) + "%"; 520 | PopupManager::showInfo(message, "Please Wait", 500); 521 | 522 | // Do work 523 | delay(200); 524 | } 525 | 526 | PopupManager::showSuccess("Process completed!"); 527 | } 528 | ``` 529 | 530 | --- 531 | 532 | For more examples, check the `examples/` folder in the library directory. 533 | 534 | **OpenMenuOS Examples Guide v3.1.0** 535 | Documentation for practical implementation patterns 536 | -------------------------------------------------------------------------------- /src/OpenMenuOS.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file OpenMenuOS.h 3 | * @brief Library to create menu system on color displays 4 | * @author Loic Daigle (The Young Maker) 5 | * @version 3.1.0 6 | * @date 2025 7 | * @license Public Domain 8 | * 9 | * OpenMenuOS is a comprehensive library for creating intuitive menu systems 10 | * on color TFT displays using the TFT_eSPI library. It supports multiple 11 | * screen types, settings management, and various input methods including 12 | * buttons and rotary encoders. 13 | * 14 | * Features: 15 | * - Multiple screen types (Menu, Settings, Custom) 16 | * - Button and rotary encoder input support 17 | * - Persistent settings storage (EEPROM/Preferences) 18 | * - Smooth animations and transitions 19 | * - Customizable themes and styles 20 | * - Scrollable content support 21 | * - Memory optimization options 22 | */ 23 | 24 | #ifndef OPENMENUOS_H 25 | #define OPENMENUOS_H 26 | 27 | //-------------------------------------------------------------------------- 28 | // Library Information 29 | //-------------------------------------------------------------------------- 30 | #define OPENMENUOS_VERSION_MAJOR 3 31 | #define OPENMENUOS_VERSION_MINOR 1 32 | #define OPENMENUOS_VERSION_PATCH 0 33 | #define OPENMENUOS_VERSION "3.1.0" 34 | 35 | // Legacy compatibility 36 | #define LIBRARY_VERSION OPENMENUOS_VERSION 37 | 38 | //-------------------------------------------------------------------------- 39 | // Includes 40 | //-------------------------------------------------------------------------- 41 | #include "Arduino.h" 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | // Platform specific includes 49 | #if defined(ESP32) 50 | #include 51 | #else 52 | #include 53 | #endif 54 | 55 | #include "OpenMenuOS_images.h" 56 | 57 | //-------------------------------------------------------------------------- 58 | // Forward declarations 59 | //-------------------------------------------------------------------------- 60 | class Screen; 61 | class SettingsScreen; 62 | class MenuScreen; 63 | class CustomScreen; 64 | class ScreenManager; 65 | class OpenMenuOS; 66 | class Setting; 67 | class PopupManager; 68 | 69 | // Callback type for menu actions 70 | typedef void (*ActionCallback)(); 71 | 72 | //-------------------------------------------------------------------------- 73 | // Popup System Enums and Structures 74 | //-------------------------------------------------------------------------- 75 | 76 | /** 77 | * @brief Enumeration of available popup types 78 | * 79 | * Each type has its own color scheme and default styling to provide 80 | * consistent visual feedback for different message categories. 81 | */ 82 | enum class PopupType 83 | { 84 | INFO, ///< Informational message (blue theme) 85 | SUCCESS, ///< Success confirmation (green theme) 86 | WARNING, ///< Warning message (orange theme) 87 | ERROR, ///< Error message (red theme) 88 | QUESTION ///< Question requiring user response (cyan theme) 89 | }; 90 | 91 | /** 92 | * @brief Enumeration of popup interaction results 93 | * 94 | * Returned by PopupManager methods to indicate user actions. 95 | */ 96 | enum class PopupResult 97 | { 98 | NONE, ///< No user interaction yet 99 | OK, ///< User clicked OK/Yes button 100 | CANCEL, ///< User clicked Cancel/No button 101 | YES, ///< Alias for OK in question dialogs 102 | NO ///< Alias for CANCEL in question dialogs 103 | }; 104 | 105 | /** 106 | * @brief Configuration structure for popup appearance and behavior 107 | * 108 | * Allows complete customization of popup content, styling, and interaction. 109 | * Default values provide sensible behavior for most use cases. 110 | */ 111 | struct PopupConfig 112 | { 113 | const char *title = nullptr; ///< Custom title (uses type default if null) 114 | const char *message = nullptr; ///< Main message text (required) 115 | PopupType type = PopupType::INFO; ///< Popup type affecting colors and icons 116 | bool showButtons = true; ///< Show interactive buttons 117 | bool showCancelButton = false; ///< Show cancel/no button for confirmation 118 | bool autoClose = false; ///< Automatically close after delay 119 | uint32_t autoCloseDelay = 3000; ///< Auto-close delay in milliseconds 120 | uint16_t customColor = 0; ///< Custom header color (0 = use type default) 121 | const uint16_t *customIcon = nullptr; ///< Custom icon image data 122 | uint16_t customIconWidth = 0; ///< Custom icon width in pixels 123 | uint16_t customIconHeight = 0; ///< Custom icon height in pixels 124 | }; 125 | 126 | /** 127 | * @brief Professional popup management system 128 | * 129 | * PopupManager provides a modern, easy-to-use interface for displaying 130 | * various types of popup messages. It supports both simple one-line calls 131 | * for common scenarios and detailed configuration for advanced use cases. 132 | * 133 | * Features: 134 | * - Type-safe popup types with consistent styling 135 | * - Non-blocking operation via update() method 136 | * - Automatic word wrapping for long messages 137 | * - Configurable auto-close behavior 138 | * - Professional visual design with shadows and animations 139 | * - Button debouncing and input validation 140 | * - Memory efficient static implementation 141 | * 142 | * Usage: 143 | * @code 144 | * // Simple usage 145 | * PopupManager::showInfo("Operation completed successfully"); 146 | * 147 | * // Question dialog 148 | * PopupResult result = PopupManager::showQuestion("Delete file?", "Confirm"); 149 | * if (result == PopupResult::OK) { } 150 | * 151 | * // Custom configuration 152 | * PopupConfig config; 153 | * config.message = "Custom popup"; 154 | * config.type = PopupType::WARNING; 155 | * config.autoClose = true; 156 | * PopupManager::show(config); 157 | * 158 | * // In main loop 159 | * PopupResult result = PopupManager::update(); 160 | * @endcode 161 | */ 162 | class PopupManager 163 | { 164 | public: 165 | /** 166 | * @brief Display a popup with custom configuration 167 | * @param config Complete popup configuration 168 | * @return PopupResult::NONE initially, check with update() 169 | */ 170 | static PopupResult show(const PopupConfig &config); 171 | 172 | /** 173 | * @brief Show an informational popup 174 | * @param message Main message text 175 | * @param title Optional custom title (uses "Information" if null) 176 | * @return PopupResult::NONE initially, check with update() 177 | */ 178 | static PopupResult showInfo(const char *message, const char *title = nullptr); 179 | 180 | /** 181 | * @brief Show a success popup (auto-closes by default) 182 | * @param message Main message text 183 | * @param title Optional custom title (uses "Success" if null) 184 | * @return PopupResult::NONE initially, check with update() 185 | */ 186 | static PopupResult showSuccess(const char *message, const char *title = nullptr); 187 | 188 | /** 189 | * @brief Show a warning popup 190 | * @param message Main message text 191 | * @param title Optional custom title (uses "Warning" if null) 192 | * @return PopupResult::NONE initially, check with update() 193 | */ 194 | static PopupResult showWarning(const char *message, const char *title = nullptr); 195 | 196 | /** 197 | * @brief Show an error popup 198 | * @param message Main message text 199 | * @param title Optional custom title (uses "Error" if null) 200 | * @return PopupResult::NONE initially, check with update() 201 | */ 202 | static PopupResult showError(const char *message, const char *title = nullptr); 203 | 204 | /** 205 | * @brief Show a question popup with Yes/No buttons 206 | * @param message Main message text 207 | * @param title Optional custom title (uses "Question" if null) 208 | * @return PopupResult::NONE initially, check with update() 209 | */ 210 | static PopupResult showQuestion(const char *message, const char *title = nullptr); 211 | 212 | /** 213 | * @brief Update popup display and handle input (call in main loop) 214 | * @return Current popup result (NONE if still active, OK/CANCEL when dismissed) 215 | */ 216 | static PopupResult update(); 217 | 218 | /** 219 | * @brief Manually hide the current popup 220 | */ 221 | static void hide(); 222 | 223 | /** 224 | * @brief Check if a popup is currently active 225 | * @return true if popup is visible, false otherwise 226 | */ 227 | static bool isActive(); 228 | 229 | private: 230 | // Private implementation details 231 | struct PopupColors 232 | { 233 | uint16_t headerColor; 234 | uint16_t backgroundColor; 235 | uint16_t textColor; 236 | uint16_t buttonColor; 237 | uint16_t buttonTextColor; 238 | const char *defaultTitle; 239 | const uint16_t *defaultIcon; 240 | uint16_t iconWidth; 241 | uint16_t iconHeight; 242 | }; 243 | static PopupResult currentResult; 244 | static bool isVisible; 245 | static uint32_t showTime; 246 | static PopupConfig currentConfig; 247 | static uint32_t lastButtonTime; 248 | static int selectedButton; // 0 = OK/Yes, 1 = Cancel/No (for encoder navigation) 249 | static const PopupColors colorSchemes[]; 250 | 251 | // Rendering methods 252 | static void drawBackground(uint16_t x, uint16_t y, uint16_t width, uint16_t height); 253 | static void drawHeader(uint16_t x, uint16_t y, uint16_t width, uint16_t headerHeight, const PopupColors &colors); 254 | static void drawContent(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const PopupColors &colors); 255 | static void drawButtons(uint16_t x, uint16_t y, uint16_t width, uint16_t buttonHeight, const PopupColors &colors); 256 | static void drawIcon(uint16_t x, uint16_t y, const PopupColors &colors); 257 | static void drawText(const char *text, uint16_t x, uint16_t y, uint16_t maxWidth, uint16_t textColor, bool bold = false); 258 | static void handleInput(); 259 | }; 260 | 261 | //-------------------------------------------------------------------------- 262 | // External variables 263 | //-------------------------------------------------------------------------- 264 | extern TFT_eSPI tft; 265 | extern TFT_eSprite canvas; 266 | extern Screen *currentScreen; 267 | 268 | #if defined(ESP32) 269 | extern Preferences preferences; 270 | #endif 271 | 272 | //-------------------------------------------------------------------------- 273 | // Configuration structures 274 | //-------------------------------------------------------------------------- 275 | 276 | /** 277 | * @brief Configuration structure for screen appearance and behavior 278 | * 279 | * This structure contains all the configurable options for customizing 280 | * the look and feel of the menu system. All color values use 16-bit 281 | * RGB565 format compatible with TFT_eSPI. 282 | */ 283 | struct ScreenConfig 284 | { 285 | // Color configuration (RGB565 format) 286 | uint16_t scrollbarColor = TFT_WHITE; ///< Color of the scrollbar 287 | uint16_t selectionBorderColor = TFT_WHITE; ///< Border color of selection rectangle 288 | uint16_t selectionFillColor = TFT_BLACK; ///< Fill color of selection rectangle 289 | uint16_t selectedItemColor = TFT_WHITE; ///< Text color of selected items 290 | 291 | // Feature toggles 292 | bool scrollbar = true; ///< Enable/disable scrollbar display 293 | bool buttonAnimation = true; ///< Enable/disable button press animations 294 | bool textScroll = true; ///< Enable/disable horizontal text scrolling 295 | bool showImages = false; ///< Enable/disable image display in menus 296 | bool animation = true; ///< Enable/disable general animations 297 | 298 | // Style configuration 299 | int menuStyle = 1; ///< Menu style (0=outlined, 1=filled) 300 | int scrollbarStyle = 1; ///< Scrollbar style variant 301 | int textShift = -4; ///< Vertical text offset for better centering 302 | 303 | // Layout proportions (as ratios of screen dimensions) 304 | const float itemHeightRatio = 0.30f; ///< Selection rectangle height ratio 305 | const float marginRatioX = 0.05f; ///< Horizontal text margin ratio (5%) 306 | const float marginRatioY = 0.01f; ///< Vertical cleaning margin ratio (1%) 307 | const float toggleSwitchHeightRatio = 0.26f; ///< Toggle switch height ratio 308 | const float iconSizeRatio = 0.06f; ///< Icon size ratio (6% of screen height) 309 | }; 310 | 311 | //-------------------------------------------------------------------------- 312 | // Core classes 313 | //-------------------------------------------------------------------------- 314 | 315 | /** 316 | * @brief Represents a configurable setting in the settings menu 317 | * 318 | * The Setting class encapsulates different types of configurable options 319 | * that can be displayed and modified in a SettingsScreen. It supports 320 | * boolean toggles, numeric ranges, multiple choice options, and navigation 321 | * to sub-screens. 322 | */ 323 | class Setting 324 | { 325 | public: 326 | /** 327 | * @brief Enumeration of available setting types 328 | */ 329 | enum Type 330 | { 331 | BOOLEAN, ///< On/off toggle setting 332 | RANGE, ///< Numeric value within a specified range 333 | OPTION, ///< Selection from a list of predefined options 334 | SUBSCREEN ///< Navigation to another screen 335 | }; 336 | 337 | const char *name; ///< Display name of the setting 338 | Type type; ///< Type of setting (determines behavior) 339 | uint16_t id; ///< Unique identifier for persistent storage 340 | Screen *subScreen; ///< Target screen for SUBSCREEN type 341 | 342 | // Union for storing different value types efficiently 343 | union 344 | { 345 | bool booleanValue; ///< Value for BOOLEAN type 346 | uint8_t rangeValue; ///< Value for RANGE type 347 | uint8_t optionIndex; ///< Selected index for OPTION type 348 | }; 349 | 350 | // Range-specific configuration 351 | struct 352 | { 353 | uint8_t min; ///< Minimum value for RANGE type 354 | uint8_t max; ///< Maximum value for RANGE type 355 | const char *unit; ///< Unit label for RANGE type (e.g., "°C", "%") 356 | } range; 357 | 358 | // Option-specific configuration 359 | const char **options; ///< Array of option strings for OPTION type 360 | uint8_t optionCount; ///< Number of available options 361 | 362 | /** 363 | * @brief Constructor for Setting 364 | * @param n Name of the setting 365 | * @param t Type of the setting 366 | */ 367 | Setting(const char *n, Type t) 368 | : name(n), type(t) 369 | { 370 | generateId(); 371 | } 372 | 373 | private: 374 | /** 375 | * @brief Generate a unique ID using a hash of the name and type 376 | */ 377 | void generateId(); 378 | }; 379 | 380 | /** 381 | * Screen - Base class for all screen types 382 | */ 383 | class Screen 384 | { 385 | public: 386 | static ScreenConfig &config; 387 | 388 | // Drawing methods 389 | virtual void draw() = 0; // Display the screen 390 | virtual void handleInput() = 0; // Handle user input 391 | void drawScrollbar(int selectedItem, int nextItem, int numItem); 392 | void drawSelectionRect(); 393 | 394 | // Text handling methods 395 | int calculateMaxCharacters(const char *text, uint16_t windowSize); 396 | void scrollTextHorizontal(int16_t x, int16_t y, const char *text, uint16_t textColor, 397 | uint16_t bgColor, uint8_t textSize, uint16_t delayTime, uint16_t windowSize); 398 | void scrollTextHorizontal(int16_t x, int16_t y, const String &text, uint16_t textColor, 399 | uint16_t bgColor, uint8_t textSize, uint16_t delayTime, uint16_t windowSize); 400 | 401 | virtual const char *getTitle() const 402 | { 403 | return "Untitled Screen"; 404 | } 405 | 406 | // Virtual destructor 407 | virtual ~Screen() = default; 408 | 409 | private: 410 | int lastSelectedItem = 0; 411 | float currentScrollPosition = 0; 412 | float targetScrollPosition = 0; 413 | unsigned long lastScrollTime = 0; 414 | const int SCROLL_ANIMATION_SPEED = 8; // Lower value = slower animation 415 | }; 416 | 417 | /** 418 | * @brief Screen that presents a list of configurable settings 419 | * 420 | * SettingsScreen provides an interface for displaying and modifying 421 | * various types of settings including boolean toggles, numeric ranges, 422 | * option selections, and navigation to sub-screens. Settings are 423 | * automatically persisted to EEPROM or Preferences (ESP32). 424 | */ 425 | class SettingsScreen : public Screen 426 | { 427 | public: 428 | // Constants 429 | static const int MAX_ITEMS = 10; ///< Maximum number of settings items 430 | 431 | // Constructor and destructor 432 | SettingsScreen(); 433 | SettingsScreen(const char *title); 434 | 435 | /** 436 | * @brief Destructor - properly closes preferences on ESP32 437 | */ 438 | ~SettingsScreen() override 439 | { 440 | #if defined(ESP32) 441 | preferences.end(); 442 | #endif 443 | } 444 | 445 | // Public methods 446 | void addSetting(Setting *setting); 447 | void resetSettings(); 448 | void draw() override; 449 | void handleInput() override; 450 | void drawToggleSwitch(int16_t x, int16_t y, Setting *setting, uint16_t bgColor = TFT_BLACK, bool isSelected = false); 451 | 452 | // Setting management methods 453 | void modify(int8_t direction, int index); 454 | void modify(int8_t direction, const char *name); 455 | Setting::Type getSettingType(uint8_t index); 456 | String getSettingName(int index); 457 | uint8_t getSettingValue(int index); 458 | uint8_t getSettingValue(const char *name); 459 | const char *getTitle() const override 460 | { 461 | return title; 462 | } 463 | // Setting creation helper methods 464 | void addBooleanSetting(const char *name, bool defaultValue); 465 | void addRangeSetting(const char *name, uint8_t min, uint8_t max, uint8_t defaultValue, const char *unit); 466 | void addOptionSetting(const char *name, const char **options, uint8_t count, uint8_t defaultIndex); 467 | void addSubscreenSetting(const char *name, Screen *targetScreen); 468 | 469 | // Public properties 470 | const char *title; // Title of the settings screen 471 | uint8_t totalSettings; // Number of settings items 472 | int currentSettingIndex; // Index of the currently selected setting 473 | 474 | // Navigation tracking 475 | int item_selected_settings_previous = -1; 476 | int item_selected_settings = 0; 477 | int item_selected_settings_next = 1; 478 | 479 | // Button state tracking 480 | unsigned long pressedTime = 0; 481 | unsigned long releasedTime = 0; 482 | bool isPressing = false; 483 | bool isLongDetected = false; 484 | bool previousButtonState = LOW; 485 | bool buttonPressProcessed = false; 486 | int upButtonState; 487 | int downButtonState; 488 | 489 | private: 490 | struct ToggleState 491 | { 492 | bool currentState; 493 | float currentPosition; 494 | float targetPosition; 495 | unsigned long lastToggleTime; 496 | bool animating; 497 | }; 498 | 499 | std::vector settings; 500 | std::map toggleStates; 501 | const int TOGGLE_ANIMATION_SPEED = 3; // Lower = slower, higher = faster 502 | 503 | // State variables 504 | bool settingSelectLock = false; 505 | bool flickerState = false; 506 | unsigned long previousMillis = 0; 507 | const long flickerInterval = 500; // Flicker interval in milliseconds 508 | 509 | // Private methods 510 | void ensureInitialized(); 511 | 512 | uint16_t calculateAvailableWidth(int settingIndex, uint16_t rectWidth, float textMarginX, uint16_t toggleSwitchWidth); 513 | 514 | // EEPROM/Preferences methods 515 | void saveToEEPROM(); 516 | void readFromEEPROM(); 517 | bool settingExists(uint16_t settingId); 518 | uint8_t getBooleanFromEEPROM(uint16_t settingId); 519 | uint8_t getRangeFromEEPROM(uint16_t settingId); 520 | uint8_t getOptionIndexFromEEPROM(uint16_t settingId); 521 | }; 522 | 523 | /** 524 | * MenuItem - Structure representing a single menu item 525 | */ 526 | struct MenuItem 527 | { 528 | const char *label; // Label for the menu item 529 | Screen *nextScreen; // Screen to navigate to when selected 530 | ActionCallback action; // Action to perform when selected 531 | const uint16_t *image; // Image associated with this menu item 532 | }; 533 | 534 | /** 535 | * MenuScreen - Screen that presents a list of menu options 536 | */ 537 | class MenuScreen : public Screen 538 | { 539 | public: 540 | // Constructor and destructor 541 | MenuScreen(); 542 | MenuScreen(const char *title); 543 | ~MenuScreen() override = default; 544 | const char *getTitle() const override 545 | { 546 | return title; 547 | } 548 | 549 | // Public methods 550 | void addItem(const char *label, Screen *nextScreen = nullptr, ActionCallback action = nullptr, const uint16_t *image = nullptr); 551 | void addItem(Screen *nextScreen, ActionCallback action = nullptr, const uint16_t *image = nullptr); 552 | void draw() override; 553 | void handleInput() override; 554 | int getIndex(); 555 | uint16_t alphaBlend(uint16_t fg, uint16_t bg, uint8_t alpha); 556 | 557 | // Properties 558 | const char *title; // Title of the menu screen 559 | std::vector items; // Collection of menu items 560 | int currentItemIndex; // Index of the selected menu item 561 | int itemSize; // Count of menu items 562 | 563 | // Layout configuration 564 | float itemHeightRatio; // Ratio for item height 565 | float marginRatioY; // Y margin ratio 566 | float marginRatioX; // X margin ratio 567 | 568 | private: 569 | // Private initialization method 570 | void initializeDefaults(); 571 | }; 572 | 573 | /** 574 | * CustomScreen - Screen with custom drawing behavior 575 | */ 576 | class CustomScreen : public Screen 577 | { 578 | public: 579 | // Constructor and destructor 580 | CustomScreen(); 581 | CustomScreen(const char *title); 582 | // Function pointer or lambda to override draw functionality 583 | std::function customDraw; 584 | 585 | // Implementation of virtual methods 586 | void draw() override 587 | { 588 | if (customDraw) 589 | { 590 | customDraw(); // Execute the custom draw behavior 591 | } 592 | } 593 | void handleInput() override; 594 | 595 | ~CustomScreen() override = default; 596 | 597 | const char *getTitle() const override 598 | { 599 | return title; 600 | } 601 | 602 | const char *title; // Title of the menu screen 603 | }; 604 | 605 | /** 606 | * @brief Manages navigation between screens using a stack-based approach 607 | * 608 | * ScreenManager handles the navigation flow between different screens, 609 | * maintaining a history stack for back navigation and ensuring proper 610 | * screen lifecycle management. 611 | */ 612 | class ScreenManager 613 | { 614 | public: 615 | std::vector screenHistory; ///< Stack of previous screens 616 | Screen *currentScreen = nullptr; ///< Currently active screen 617 | 618 | /** 619 | * @brief Push a new screen onto the navigation stack 620 | * @param newScreen Pointer to the screen to display 621 | */ 622 | void pushScreen(Screen *newScreen) 623 | { 624 | if (newScreen == nullptr) 625 | { 626 | return; // Cannot push null screen 627 | } 628 | 629 | if (currentScreen != nullptr) 630 | { 631 | screenHistory.push_back(currentScreen); 632 | } 633 | 634 | currentScreen = newScreen; 635 | currentScreen->draw(); 636 | 637 | // Update global reference 638 | ::currentScreen = currentScreen; 639 | } 640 | 641 | /** 642 | * @brief Pop the current screen and return to the previous one 643 | * @return true if successfully navigated back, false if already at root 644 | */ 645 | bool popScreen() 646 | { 647 | if (screenHistory.empty()) 648 | { 649 | return false; // Cannot go back further 650 | } 651 | 652 | currentScreen = screenHistory.back(); 653 | screenHistory.pop_back(); 654 | 655 | if (currentScreen != nullptr) 656 | { 657 | currentScreen->draw(); 658 | } 659 | 660 | // Update global reference 661 | ::currentScreen = currentScreen; 662 | return true; 663 | } 664 | 665 | /** 666 | * @brief Check if back navigation is possible 667 | * @return true if there are screens in the history stack 668 | */ 669 | bool canGoBack() const 670 | { 671 | return !screenHistory.empty(); 672 | } 673 | 674 | /** 675 | * @brief Get the current navigation depth 676 | * @return Number of screens in the history stack 677 | */ 678 | size_t getDepth() const 679 | { 680 | return screenHistory.size(); 681 | } 682 | }; 683 | 684 | /** 685 | * @brief Main library class for creating menu systems on color displays 686 | * 687 | * OpenMenuOS provides a complete framework for creating interactive menu 688 | * systems on TFT displays. It manages screens, handles input from buttons 689 | * or encoders, and provides various customization options for appearance 690 | * and behavior. 691 | * 692 | * Example usage: 693 | * @code 694 | * OpenMenuOS menu(2, 3, 4); // UP, DOWN, SELECT pins 695 | * MenuScreen mainMenu("Main Menu"); 696 | * 697 | * void setup() { 698 | * menu.begin(&mainMenu); 699 | * } 700 | * 701 | * void loop() { 702 | * menu.loop(); 703 | * } 704 | * @endcode 705 | */ 706 | class OpenMenuOS 707 | { 708 | public: 709 | // Constructor and core methods 710 | /** 711 | * @brief Constructor for OpenMenuOS 712 | * @param btn_up Pin number for UP button (-1 to disable) 713 | * @param btn_down Pin number for DOWN button (-1 to disable) 714 | * @param btn_sel Pin number for SELECT button (-1 to disable) 715 | */ 716 | OpenMenuOS(int btn_up = -1, int btn_down = -1, int btn_sel = -1); 717 | 718 | /** 719 | * @brief Initialize the menu system with specified rotation 720 | * @param rotation Display rotation (0-3) 721 | * @param mainMenu Pointer to the main menu screen 722 | */ 723 | void begin(int rotation, Screen *mainMenu); 724 | 725 | /** 726 | * @brief Initialize the menu system with default rotation 727 | * @param mainMenu Pointer to the main menu screen 728 | */ 729 | void begin(Screen *mainMenu); 730 | 731 | /** 732 | * @brief Main loop function - call this in Arduino's loop() 733 | */ 734 | void loop(); 735 | 736 | // Navigation 737 | void redirectToScreen(Screen *nextScreen); 738 | void navigateBack(); 739 | 740 | // Text handling 741 | void scrollTextHorizontal(int16_t x, int16_t y, const char *text, uint16_t textColor, 742 | uint16_t bgColor, uint8_t textSize, uint16_t delayTime, uint16_t windowSize); 743 | void scrollTextHorizontal(int16_t x, int16_t y, const String &text, uint16_t textColor, 744 | uint16_t bgColor, uint8_t textSize, uint16_t delayTime, uint16_t windowSize); 745 | 746 | // UI Configuration methods 747 | void setDisplayRotation(int rotation); 748 | void setTextScroll(bool x); 749 | void showBootImage(bool x); 750 | void setBootImage(uint16_t *Boot_img, uint16_t height, uint16_t width); 751 | void setButtonAnimation(bool x); 752 | void setMenuStyle(int style); 753 | void setScrollbar(bool x); 754 | void setScrollbarColor(uint16_t color); 755 | void setScrollbarStyle(int style); 756 | void setSelectionBorderColor(uint16_t color); 757 | void setSelectionFillColor(uint16_t color); 758 | void setAnimation(bool enabled); 759 | void setMenuFont(const GFXfont *font); 760 | void setMenuFontBold(const GFXfont *font); 761 | void setOptimizeDisplayUpdates(bool enabled = true); 762 | 763 | // Style presets and button configuration 764 | void useStylePreset(int preset); 765 | void useStylePreset(char *preset); 766 | void setButtonsMode(char *mode); 767 | void setEncoderPin(uint8_t clk, uint8_t dt); 768 | void setUpPin(uint8_t btn_down); 769 | void setDownPin(uint8_t btn_up); 770 | void setSelectPin(uint8_t btn_sel); 771 | 772 | // Drawing methods 773 | void drawCanvasOnTFT(); 774 | // Getters 775 | const char *getLibraryVersion(); 776 | int getTftHeight() const; 777 | int getTftWidth() const; 778 | int UpButton() const; 779 | int DownButton() const; 780 | int SelectButton() const; 781 | bool getOptimizeDisplayUpdates() const; 782 | 783 | private: 784 | // Private methods 785 | void drawPopup(char *message, bool &clicked, int type); 786 | 787 | // Boot image properties 788 | uint16_t *boot_image = nullptr; 789 | uint16_t boot_image_width = 0; 790 | uint16_t boot_image_height = 0; 791 | 792 | // Display optimization 793 | bool optimizeDisplayUpdates = false; 794 | 795 | // UI behavior flags 796 | bool bootImage = false; 797 | 798 | bool tftInitialized = false; 799 | 800 | int displayRotation = 0; 801 | }; 802 | 803 | #endif // OPENMENUOS_H -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # OpenMenuOS API Reference 2 | 3 | Complete reference for all classes, methods, and functions in the OpenMenuOS library. 4 | 5 | ## Table of Contents 6 | 7 | - [OpenMenuOS Class](#openmenuos-class) 8 | - [PopupManager Class](#popupmanager-class) 9 | - [Screen Classes](#screen-classes) 10 | - [Settings System](#settings-system) 11 | - [Enums and Structures](#enums-and-structures) 12 | - [Global Functions](#global-functions) 13 | 14 | --- 15 | 16 | ## OpenMenuOS Class 17 | 18 | The main class for managing the entire menu system. 19 | 20 | ### Constructor 21 | 22 | ```cpp 23 | OpenMenuOS(); 24 | ``` 25 | 26 | ### Core Methods 27 | 28 | #### `void begin()` 29 | Initialize the menu system and display. 30 | ```cpp 31 | menu.begin(); 32 | ``` 33 | 34 | #### `void loop()` 35 | Main update loop - call this in your Arduino loop() function. 36 | ```cpp 37 | void loop() { 38 | menu.loop(); 39 | } 40 | ``` 41 | 42 | #### `void setupButtons(int upPin, int downPin, int selectPin, int mode, int voltage)` 43 | Configure button pins and behavior. 44 | - `upPin`: GPIO pin for UP button (-1 to disable) 45 | - `downPin`: GPIO pin for DOWN button (-1 to disable) 46 | - `selectPin`: GPIO pin for SELECT button 47 | - `mode`: INPUT or INPUT_PULLUP 48 | - `voltage`: Expected voltage when pressed (HIGH or LOW) 49 | 50 | ```cpp 51 | menu.setupButtons(19, -1, 2, INPUT_PULLUP, LOW); 52 | ``` 53 | 54 | #### `void setEncoderPin(int clkPin, int dtPin)` 55 | Enable rotary encoder support. 56 | - `clkPin`: Clock pin of encoder 57 | - `dtPin`: Data pin of encoder 58 | 59 | ```cpp 60 | menu.setEncoderPin(5, 4); 61 | ``` 62 | 63 | ### Display Configuration 64 | 65 | #### `void setBootImage(const uint16_t* image, int width, int height)` 66 | Set a custom boot image displayed during initialization. 67 | ```cpp 68 | menu.setBootImage(myBootImage, 128, 160); 69 | ``` 70 | 71 | #### `void setOptimizeDisplayUpdates(bool enabled)` 72 | Enable/disable frame comparison for performance optimization. 73 | ```cpp 74 | menu.setOptimizeDisplayUpdates(true); // Better performance 75 | ``` 76 | 77 | #### `void setAnimation(bool enabled)` 78 | Enable/disable menu animations. 79 | ```cpp 80 | menu.setAnimation(false); // Disable for better performance 81 | ``` 82 | 83 | #### `void setTextScroll(bool enabled)` 84 | Enable/disable text scrolling for long menu items. 85 | ```cpp 86 | menu.setTextScroll(true); 87 | ``` 88 | 89 | #### `void setScrollbar(bool enabled)` 90 | Show/hide scrollbar in menus. 91 | ```cpp 92 | menu.setScrollbar(true); 93 | ``` 94 | 95 | ### Menu Management 96 | 97 | #### `void setMainMenuItems(const std::vector& items)` 98 | Set items for the automatically created main menu. 99 | ```cpp 100 | menu.setMainMenuItems({"Settings", "About", "WiFi", "Exit"}); 101 | ``` 102 | 103 | #### `void createMainMenu()` 104 | Create the main menu screen with the configured items. 105 | ```cpp 106 | menu.createMainMenu(); 107 | ``` 108 | 109 | #### `void addSettingsScreen(const String& title, Setting* settings, int count)` 110 | Add a settings screen to the menu system. 111 | ```cpp 112 | Setting settings[] = { 113 | {"WiFi Enabled", SETTING_BOOLEAN, true}, 114 | {"Brightness", SETTING_RANGE, 50, 0, 100} 115 | }; 116 | menu.addSettingsScreen("System Settings", settings, 2); 117 | ``` 118 | 119 | #### `void addScreen(Screen* screen)` 120 | Add a custom screen to the menu system. 121 | ```cpp 122 | MenuScreen* customMenu = new MenuScreen("Custom Menu"); 123 | menu.addScreen(customMenu); 124 | ``` 125 | 126 | ### Screen Navigation 127 | 128 | #### `void navigateToScreen(int screenIndex)` 129 | Navigate to a specific screen by index. 130 | ```cpp 131 | menu.navigateToScreen(0); // Go to first screen 132 | ``` 133 | 134 | #### `void goBack()` 135 | Return to the previous screen. 136 | ```cpp 137 | menu.goBack(); 138 | ``` 139 | 140 | #### `void goToMainMenu()` 141 | Navigate directly to the main menu. 142 | ```cpp 143 | menu.goToMainMenu(); 144 | ``` 145 | 146 | ### Styling and Themes 147 | 148 | #### `void useStylePreset(const String& preset)` 149 | Apply a predefined visual theme. 150 | - Available presets: "Rabbit_R1", "Classic", "Modern" 151 | 152 | ```cpp 153 | menu.useStylePreset("Rabbit_R1"); 154 | ``` 155 | 156 | #### `void setSelectionBorderColor(uint16_t color)` 157 | Set the color for selection borders. 158 | ```cpp 159 | menu.setSelectionBorderColor(TFT_CYAN); 160 | ``` 161 | 162 | #### `void setBackgroundColor(uint16_t color)` 163 | Set the background color for all screens. 164 | ```cpp 165 | menu.setBackgroundColor(TFT_BLACK); 166 | ``` 167 | 168 | ### Information Methods 169 | 170 | #### `String getLibraryVersion()` 171 | Get the current library version. 172 | ```cpp 173 | String version = menu.getLibraryVersion(); 174 | ``` 175 | 176 | #### `int getTftWidth()` / `int getTftHeight()` 177 | Get display dimensions. 178 | ```cpp 179 | int width = menu.getTftWidth(); 180 | int height = menu.getTftHeight(); 181 | ``` 182 | 183 | --- 184 | 185 | ## PopupManager Class 186 | 187 | Static class for managing popup dialogs. 188 | 189 | ### Popup Types 190 | 191 | #### `static void showInfo(const String& message, const String& title = "Info", int autoCloseMs = 0)` 192 | Display an informational popup. 193 | ```cpp 194 | PopupManager::showInfo("Operation completed successfully"); 195 | PopupManager::showInfo("Status update", "System", 3000); // Auto-close after 3s 196 | ``` 197 | 198 | #### `static void showSuccess(const String& message, const String& title = "Success", int autoCloseMs = 2000)` 199 | Display a success popup (auto-closes by default). 200 | ```cpp 201 | PopupManager::showSuccess("Data saved successfully!"); 202 | ``` 203 | 204 | #### `static void showWarning(const String& message, const String& title = "Warning", int autoCloseMs = 0)` 205 | Display a warning popup. 206 | ```cpp 207 | PopupManager::showWarning("Low battery detected", "Battery Warning"); 208 | ``` 209 | 210 | #### `static void showError(const String& message, const String& title = "Error", int autoCloseMs = 0)` 211 | Display an error popup. 212 | ```cpp 213 | PopupManager::showError("Failed to connect to WiFi", "Connection Error"); 214 | ``` 215 | 216 | #### `static PopupResult showQuestion(const String& message, const String& title = "Question", const std::vector& buttons = {"OK", "Cancel"})` 217 | Display a question popup with custom buttons. 218 | ```cpp 219 | PopupResult result = PopupManager::showQuestion("Delete all data?", "Confirm"); 220 | if (result.buttonIndex == 0) { 221 | // User clicked first button (OK) 222 | } 223 | 224 | // Custom buttons 225 | std::vector options = {"Save", "Don't Save", "Cancel"}; 226 | PopupResult result = PopupManager::showQuestion("Save changes?", "Unsaved Changes", options); 227 | ``` 228 | 229 | ### Configuration 230 | 231 | #### `static void setConfig(const PopupConfig& config)` 232 | Set global popup configuration. 233 | ```cpp 234 | PopupConfig config; 235 | config.overlayOpacity = 128; // Semi-transparent overlay 236 | config.cornerRadius = 8; // Rounded corners 237 | config.shadowOffset = 3; // Shadow effect 238 | config.animationSpeed = 200; // Animation duration in ms 239 | config.buttonSpacing = 10; // Space between buttons 240 | config.padding = 15; // Internal padding 241 | PopupManager::setConfig(config); 242 | ``` 243 | 244 | ### State Management 245 | 246 | #### `static bool isActive()` 247 | Check if any popup is currently active. 248 | ```cpp 249 | if (PopupManager::isActive()) { 250 | // A popup is currently shown 251 | } 252 | ``` 253 | 254 | #### `static PopupResult getResult()` 255 | Get the result of the last popup interaction. 256 | ```cpp 257 | PopupResult result = PopupManager::getResult(); 258 | if (result.buttonIndex != -1) { 259 | // User interacted with popup 260 | } 261 | ``` 262 | 263 | --- 264 | 265 | ## Screen Classes 266 | 267 | Base classes for creating different types of screens. 268 | 269 | ### Screen (Base Class) 270 | 271 | #### `Screen(const String& title)` 272 | Base constructor for all screen types. 273 | ```cpp 274 | Screen myScreen("My Screen"); 275 | ``` 276 | 277 | #### `virtual void draw()` 278 | Override to implement custom drawing logic. 279 | ```cpp 280 | class CustomScreen : public Screen { 281 | public: 282 | CustomScreen(const String& title) : Screen(title) {} 283 | 284 | void draw() override { 285 | canvas.fillScreen(TFT_BLACK); 286 | canvas.setTextColor(TFT_WHITE); 287 | canvas.drawString("Custom content", 10, 50); 288 | } 289 | }; 290 | ``` 291 | 292 | ### MenuScreen 293 | 294 | A screen that displays a list of menu items. 295 | 296 | #### `MenuScreen(const String& title)` 297 | Create a new menu screen. 298 | ```cpp 299 | MenuScreen mainMenu("Main Menu"); 300 | ``` 301 | 302 | #### `void addItem(const String& name, Screen* targetScreen = nullptr, void (*callback)() = nullptr)` 303 | Add an item to the menu. 304 | ```cpp 305 | // Link to another screen 306 | mainMenu.addItem("Settings", &settingsScreen); 307 | 308 | // Callback function 309 | mainMenu.addItem("Toggle LED", nullptr, toggleLED); 310 | 311 | // Both (callback executes first) 312 | mainMenu.addItem("Save & Exit", &mainMenu, saveSettings); 313 | ``` 314 | 315 | #### `void removeItem(int index)` 316 | Remove an item by index. 317 | ```cpp 318 | mainMenu.removeItem(0); // Remove first item 319 | ``` 320 | 321 | #### `void clearItems()` 322 | Remove all items from the menu. 323 | ```cpp 324 | mainMenu.clearItems(); 325 | ``` 326 | 327 | #### `int getItemCount()` 328 | Get the number of items in the menu. 329 | ```cpp 330 | int count = mainMenu.getItemCount(); 331 | ``` 332 | 333 | ### SettingsScreen 334 | 335 | A specialized screen for managing settings. 336 | 337 | #### `SettingsScreen(const String& title)` 338 | Create a new settings screen. 339 | ```cpp 340 | SettingsScreen settings("Device Settings"); 341 | ``` 342 | 343 | #### `void addBooleanSetting(const String& name, bool defaultValue)` 344 | Add a boolean (on/off) setting. 345 | ```cpp 346 | settings.addBooleanSetting("WiFi Enabled", true); 347 | ``` 348 | 349 | #### `void addRangeSetting(const String& name, int min, int max, int defaultValue, const String& unit = "")` 350 | Add a numeric range setting. 351 | ```cpp 352 | settings.addRangeSetting("Brightness", 0, 100, 75, "%"); 353 | settings.addRangeSetting("Timeout", 1, 60, 30, "s"); 354 | ``` 355 | 356 | #### `void addOptionSetting(const String& name, const char* options[], int optionCount, int defaultIndex)` 357 | Add a multiple choice setting. 358 | ```cpp 359 | const char* themes[] = {"Dark", "Light", "Auto"}; 360 | settings.addOptionSetting("Theme", themes, 3, 0); 361 | ``` 362 | 363 | #### `void addSubscreenSetting(const String& name, Screen* targetScreen)` 364 | Add a setting that opens another screen. 365 | ```cpp 366 | settings.addSubscreenSetting("Advanced", &advancedSettings); 367 | ``` 368 | 369 | #### Setting Value Access 370 | 371 | ```cpp 372 | // Get setting values 373 | bool wifiEnabled = settings.getBooleanValue(0); 374 | int brightness = settings.getRangeValue(1); 375 | int themeIndex = settings.getOptionValue(2); 376 | 377 | // Set setting values 378 | settings.setBooleanValue(0, false); 379 | settings.setRangeValue(1, 80); 380 | settings.setOptionValue(2, 1); 381 | ``` 382 | 383 | ### CustomScreen 384 | 385 | A screen with full control over drawing and input. 386 | 387 | #### `CustomScreen(const String& title)` 388 | Create a custom screen. 389 | ```cpp 390 | CustomScreen customScreen("Custom Screen"); 391 | ``` 392 | 393 | #### `std::function customDraw` 394 | Lambda function for custom drawing logic. Set this in your `setup()` function. 395 | ```cpp 396 | // Configure custom screen drawing in setup() 397 | customScreen.customDraw = []() { 398 | canvas.drawSmoothRoundRect(-15, 50, 40, 0, 50, 50, TFT_BLUE, TFT_BLACK); 399 | canvas.drawSmoothRoundRect(10, 10, 200, 100, 20, 5, TFT_ORANGE, TFT_BLACK); 400 | canvas.drawSmoothRoundRect(120, -25, 40, 0, 40, 40, TFT_DARKGREEN, TFT_BLACK); 401 | canvas.drawString("V" + String(menu.getLibraryVersion()), 10, 10); 402 | canvas.setTextColor(TFT_WHITE, TFT_BLACK); 403 | canvas.drawString("Press UP for popup demo", 10, 30); 404 | }; 405 | ``` 406 | 407 | 408 | 409 | #### Complete Example 410 | ```cpp 411 | // Define the custom screen globally 412 | CustomScreen customScreen("Custom Screen"); 413 | 414 | void setup() { 415 | // Configure the custom drawing in setup() 416 | customScreen.customDraw = []() { 417 | canvas.fillScreen(TFT_BLACK); 418 | canvas.setTextColor(TFT_WHITE); 419 | canvas.drawString("OpenMenuOS v" + String(menu.getLibraryVersion()), 10, 10); 420 | canvas.drawString("Custom Screen Content", 10, 30); 421 | 422 | // Draw some graphics 423 | canvas.fillCircle(50, 80, 20, TFT_BLUE); 424 | canvas.drawRect(10, 110, 100, 30, TFT_GREEN); 425 | canvas.fillRect(12, 112, 96, 26, TFT_DARKGREEN); 426 | canvas.setTextColor(TFT_WHITE, TFT_DARKGREEN); 427 | canvas.drawString("Press SELECT", 15, 120); 428 | }; 429 | 430 | // Add to menu system 431 | menu.addScreen(&customScreen); 432 | } 433 | ``` 434 | 435 | --- 436 | 437 | ## Settings System 438 | 439 | ### Setting Structure 440 | 441 | ```cpp 442 | struct Setting { 443 | String name; // Display name 444 | SettingType type; // Type of setting 445 | union { 446 | bool boolValue; // For SETTING_BOOLEAN 447 | struct { // For SETTING_RANGE 448 | int value; 449 | int min; 450 | int max; 451 | } range; 452 | struct { // For SETTING_OPTION 453 | int selectedIndex; 454 | const char** options; 455 | int optionCount; 456 | } option; 457 | Screen* subscreenTarget; // For SETTING_SUBSCREEN 458 | }; 459 | String unit; // Unit for range settings (e.g., "%", "ms") 460 | bool hasBeenSet; // Internal flag for EEPROM storage 461 | }; 462 | ``` 463 | 464 | ### Setting Types 465 | 466 | ```cpp 467 | enum SettingType { 468 | SETTING_BOOLEAN, // True/false toggle 469 | SETTING_RANGE, // Numeric value within range 470 | SETTING_OPTION, // Multiple choice selection 471 | SETTING_SUBSCREEN // Link to another screen 472 | }; 473 | ``` 474 | 475 | ### Usage Examples 476 | 477 | ```cpp 478 | // Boolean setting 479 | Setting wifiSetting = {"WiFi", SETTING_BOOLEAN, {.boolValue = true}}; 480 | 481 | // Range setting 482 | Setting brightnessSetting = {"Brightness", SETTING_RANGE, {.range = {75, 0, 100}}, "%"}; 483 | 484 | // Option setting 485 | const char* modes[] = {"Auto", "Manual", "Off"}; 486 | Setting modeSetting = {"Mode", SETTING_OPTION, {.option = {0, modes, 3}}}; 487 | 488 | // Subscreen setting 489 | Setting advancedSetting = {"Advanced", SETTING_SUBSCREEN, {.subscreenTarget = &advancedScreen}}; 490 | ``` 491 | 492 | --- 493 | 494 | ## Enums and Structures 495 | 496 | ### PopupType 497 | 498 | ```cpp 499 | enum PopupType { 500 | POPUP_INFO, // Blue information popup 501 | POPUP_SUCCESS, // Green success popup 502 | POPUP_WARNING, // Orange warning popup 503 | POPUP_ERROR, // Red error popup 504 | POPUP_QUESTION // Blue question popup with buttons 505 | }; 506 | ``` 507 | 508 | ### PopupResult 509 | 510 | ```cpp 511 | struct PopupResult { 512 | int buttonIndex; // Index of clicked button (-1 if none) 513 | String buttonText; // Text of clicked button 514 | bool timedOut; // True if popup auto-closed 515 | unsigned long timestamp; // When interaction occurred 516 | 517 | // Comparison operators 518 | bool operator==(const PopupResult& other) const; 519 | bool operator!=(const PopupResult& other) const; 520 | }; 521 | 522 | // Predefined results 523 | static const PopupResult OK; // {0, "OK", false, 0} 524 | static const PopupResult CANCEL; // {1, "Cancel", false, 0} 525 | static const PopupResult TIMEOUT; // {-1, "", true, 0} 526 | ``` 527 | 528 | ### PopupConfig 529 | 530 | ```cpp 531 | struct PopupConfig { 532 | uint8_t overlayOpacity; // Background overlay opacity (0-255) 533 | uint8_t cornerRadius; // Popup corner radius in pixels 534 | uint8_t shadowOffset; // Shadow offset in pixels 535 | uint16_t animationSpeed; // Animation duration in milliseconds 536 | uint8_t buttonSpacing; // Space between buttons in pixels 537 | uint8_t padding; // Internal popup padding in pixels 538 | uint16_t maxWidth; // Maximum popup width (0 = auto) 539 | uint16_t maxHeight; // Maximum popup height (0 = auto) 540 | bool wordWrap; // Enable word wrapping for long text 541 | bool showIcons; // Show type-specific icons 542 | }; 543 | ``` 544 | 545 | ### ButtonState 546 | 547 | ```cpp 548 | enum ButtonState { 549 | BUTTON_IDLE, // No interaction 550 | BUTTON_PRESSED, // Button currently pressed 551 | BUTTON_RELEASED, // Button just released 552 | BUTTON_HELD // Button held for long time 553 | }; 554 | ``` 555 | 556 | --- 557 | 558 | ## Global Functions 559 | 560 | ### Utility Functions 561 | 562 | #### `uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b)` 563 | Convert RGB values to 16-bit color format. 564 | ```cpp 565 | uint16_t myColor = rgb565(255, 128, 0); // Orange color 566 | ``` 567 | 568 | #### `void drawRoundedRect(int x, int y, int w, int h, int radius, uint16_t color)` 569 | Draw a rounded rectangle on the canvas. 570 | ```cpp 571 | drawRoundedRect(10, 10, 100, 50, 8, TFT_BLUE); 572 | ``` 573 | 574 | #### `void drawShadow(int x, int y, int w, int h, int offset, uint8_t opacity)` 575 | Draw a shadow effect for UI elements. 576 | ```cpp 577 | drawShadow(10, 10, 100, 50, 3, 128); 578 | ``` 579 | 580 | ### Display Functions 581 | 582 | #### `void drawCanvasOnTFT()` 583 | Transfer the canvas buffer to the physical display. 584 | ```cpp 585 | drawCanvasOnTFT(); // Usually called automatically 586 | ``` 587 | 588 | #### `void clearCanvas()` 589 | Clear the canvas buffer. 590 | ```cpp 591 | clearCanvas(); 592 | ``` 593 | 594 | ### Input Functions 595 | 596 | #### `ButtonState getButtonState(int pin)` 597 | Get the current state of a button pin. 598 | ```cpp 599 | ButtonState state = getButtonState(SELECT_PIN); 600 | if (state == BUTTON_PRESSED) { 601 | // Handle button press 602 | } 603 | ``` 604 | 605 | #### `bool isEncoderMoved()` 606 | Check if the encoder has been rotated. 607 | ```cpp 608 | if (isEncoderMoved()) { 609 | int direction = getEncoderDirection(); 610 | // Handle encoder movement 611 | } 612 | ``` 613 | 614 | #### `int getEncoderDirection()` 615 | Get encoder rotation direction (-1 for CCW, 1 for CW, 0 for none). 616 | ```cpp 617 | int direction = getEncoderDirection(); 618 | if (direction > 0) { 619 | // Clockwise rotation 620 | } else if (direction < 0) { 621 | // Counter-clockwise rotation 622 | } 623 | ``` 624 | 625 | --- 626 | 627 | ## Usage Examples 628 | 629 | ### Complete Basic Setup 630 | 631 | ```cpp 632 | #include "OpenMenuOS.h" 633 | #include "images.h" // Include custom images if needed 634 | 635 | //============================================================================== 636 | // MENU SCREEN DEFINITIONS 637 | //============================================================================== 638 | OpenMenuOS menu; // Main menu system instance 639 | 640 | // Create menu screens 641 | MenuScreen mainMenu("Main Menu"); 642 | MenuScreen testScreen("Test Menu"); 643 | CustomScreen customScreen("Custom Screen"); 644 | SettingsScreen settingsScreen("Settings"); 645 | 646 | //============================================================================== 647 | // CALLBACK FUNCTIONS 648 | //============================================================================== 649 | void redirectToCustomScreen() { 650 | menu.redirectToScreen(&customScreen); 651 | } 652 | 653 | void showInfoPopup() { 654 | PopupManager::showInfo("This is an information message!", "Info Demo"); 655 | } 656 | 657 | void toggleLED() { 658 | static bool ledState = false; 659 | ledState = !ledState; 660 | digitalWrite(LED_BUILTIN, ledState); 661 | PopupManager::showSuccess(ledState ? "LED On" : "LED Off"); 662 | } 663 | 664 | //============================================================================== 665 | // SETUP 666 | //============================================================================== 667 | void setup() { 668 | Serial.begin(115200); 669 | 670 | // Configure custom screen drawing 671 | customScreen.customDraw = []() { 672 | canvas.drawSmoothRoundRect(-15, 50, 40, 0, 50, 50, TFT_BLUE, TFT_BLACK); 673 | canvas.drawSmoothRoundRect(10, 10, 200, 100, 20, 5, TFT_ORANGE, TFT_BLACK); 674 | canvas.drawSmoothRoundRect(120, -25, 40, 0, 40, 40, TFT_DARKGREEN, TFT_BLACK); 675 | canvas.drawString("V" + String(menu.getLibraryVersion()), 10, 10); 676 | canvas.setTextColor(TFT_WHITE, TFT_BLACK); 677 | canvas.drawString("Press UP for popup demo", 10, 30); 678 | }; 679 | 680 | // Configure main menu 681 | mainMenu.addItem("Settings", &settingsScreen, nullptr, (const uint16_t*)Menu_icon_1); 682 | mainMenu.addItem(&testScreen); 683 | mainMenu.addItem("Custom Screen", nullptr, redirectToCustomScreen); 684 | mainMenu.addItem("LED Control", nullptr, toggleLED); 685 | mainMenu.addItem("Info Popup", nullptr, showInfoPopup); 686 | 687 | // Configure test menu 688 | testScreen.addItem("First Test Page"); 689 | testScreen.addItem("Second Test Page"); 690 | testScreen.addItem(&customScreen); 691 | 692 | // Configure settings 693 | settingsScreen.addBooleanSetting("Animations", true); 694 | settingsScreen.addBooleanSetting("Optimize Display Updates", true); 695 | settingsScreen.addRangeSetting("Brightness", 0, 100, 75, "%"); 696 | 697 | const char* styleOptions[] = {"Default", "Modern"}; 698 | settingsScreen.addOptionSetting("Style", styleOptions, 2, 1); 699 | 700 | // Configure menu appearance and behavior 701 | menu.useStylePreset("Rabbit_R1"); 702 | menu.setScrollbar(true); 703 | menu.setScrollbarStyle(1); 704 | menu.setButtonsMode("low"); 705 | 706 | // Configure input 707 | menu.setEncoderPin(5, 2); // Enable encoder support 708 | menu.setSelectPin(19); 709 | 710 | menu.setDisplayRotation(0); 711 | 712 | // Initialize the menu system 713 | menu.begin(&mainMenu); 714 | } 715 | 716 | //============================================================================== 717 | // MAIN LOOP 718 | //============================================================================== 719 | void loop() { 720 | // Handle popup interactions first 721 | PopupResult popupResult = PopupManager::update(); 722 | 723 | // Handle menu logic 724 | menu.loop(); 725 | 726 | // Handle popup results when user interacts with popup 727 | if (popupResult != PopupResult::NONE) { 728 | switch (popupResult) { 729 | case PopupResult::OK: 730 | Serial.println("User clicked OK/Yes"); 731 | break; 732 | case PopupResult::CANCEL: 733 | Serial.println("User clicked Cancel/No"); 734 | break; 735 | default: 736 | break; 737 | } 738 | } 739 | 740 | // Update settings-based configurations 741 | menu.setAnimation(settingsScreen.getSettingValue("Animations")); 742 | menu.setOptimizeDisplayUpdates(settingsScreen.getSettingValue("Optimize Display Updates")); 743 | } 744 | ``` 745 | 746 | ### Advanced Popup Usage 747 | 748 | ```cpp 749 | void handleUserChoice() { 750 | std::vector options = {"Save", "Don't Save", "Cancel"}; 751 | PopupResult result = PopupManager::showQuestion( 752 | "You have unsaved changes.\\nWhat would you like to do?", 753 | "Unsaved Changes", 754 | options 755 | ); 756 | 757 | switch (result.buttonIndex) { 758 | case 0: // Save 759 | saveData(); 760 | PopupManager::showSuccess("Data saved successfully!"); 761 | break; 762 | case 1: // Don't Save 763 | PopupManager::showWarning("Changes discarded", "Warning"); 764 | break; 765 | case 2: // Cancel 766 | // Do nothing, stay on current screen 767 | break; 768 | default: 769 | // Timed out or other 770 | break; 771 | } 772 | } 773 | 774 | void saveData() { 775 | // Simulate save operation 776 | delay(1000); 777 | 778 | if (saveSuccessful) { 779 | PopupManager::showSuccess("Data saved!", "Success", 2000); 780 | } else { 781 | PopupManager::showError("Save failed!", "Error"); 782 | } 783 | } 784 | ``` 785 | 786 | ### Custom Screen Implementation 787 | 788 | ```cpp 789 | // Define custom screen globally 790 | CustomScreen graphScreen("Sensor Graph"); 791 | 792 | class GraphData { 793 | private: 794 | static float dataPoints[50]; 795 | static int dataCount; 796 | 797 | public: 798 | static void addDataPoint(float value) { 799 | if (dataCount < 50) { 800 | dataPoints[dataCount++] = value; 801 | } else { 802 | // Shift array and add new point 803 | for (int i = 0; i < 49; i++) { 804 | dataPoints[i] = dataPoints[i + 1]; 805 | } 806 | dataPoints[49] = value; 807 | } 808 | } 809 | 810 | static void drawGraph() { 811 | canvas.fillScreen(TFT_BLACK); 812 | canvas.setTextColor(TFT_WHITE); 813 | canvas.drawString("Sensor Data", 10, 10); 814 | 815 | // Draw axes 816 | canvas.drawLine(20, 30, 20, 130, TFT_WHITE); // Y-axis 817 | canvas.drawLine(20, 130, 220, 130, TFT_WHITE); // X-axis 818 | 819 | // Plot data points 820 | for (int i = 1; i < dataCount; i++) { 821 | int x1 = 20 + (i - 1) * 4; 822 | int y1 = 130 - (int)(dataPoints[i - 1] * 100); 823 | int x2 = 20 + i * 4; 824 | int y2 = 130 - (int)(dataPoints[i] * 100); 825 | canvas.drawLine(x1, y1, x2, y2, TFT_CYAN); 826 | } 827 | } 828 | 829 | 830 | }; 831 | 832 | // Initialize static members 833 | float GraphData::dataPoints[50]; 834 | int GraphData::dataCount = 0; 835 | 836 | void setup() { 837 | // Configure the custom screen with lambda functions 838 | graphScreen.customDraw = []() { 839 | GraphData::drawGraph(); 840 | }; 841 | 842 | 843 | // Add to menu 844 | menu.addScreen(&graphScreen); 845 | 846 | // Add some initial data 847 | for (int i = 0; i < 10; i++) { 848 | GraphData::addDataPoint(random(100) / 100.0); 849 | } 850 | } 851 | 852 | void loop() { 853 | menu.loop(); 854 | 855 | // Continuously add new sensor data 856 | static unsigned long lastDataUpdate = 0; 857 | if (millis() - lastDataUpdate > 1000) { // Every second 858 | float sensorValue = analogRead(A0) / 1024.0; 859 | GraphData::addDataPoint(sensorValue); 860 | lastDataUpdate = millis(); 861 | } 862 | } 863 | ``` 864 | 865 | --- 866 | 867 | ## Best Practices 868 | 869 | ### Performance Optimization 870 | 871 | 1. **Enable Frame Comparison**: Use `setOptimizeDisplayUpdates(true)` for better performance 872 | 2. **Disable Animations**: Use `setAnimation(false)` on slower devices 873 | 3. **Limit Menu Items**: Keep menus concise to reduce memory usage 874 | 4. **Use Static Callbacks**: Prefer static functions for menu callbacks 875 | 876 | ### Memory Management 877 | 878 | 1. **Initialize in setup()**: Create screens and menus in `setup()`, not in callbacks 879 | 2. **Use References**: Pass screens by reference when possible 880 | 3. **Limit Recursion**: Avoid deeply nested menu structures 881 | 4. **Clean Up**: Remove unused screens and menu items 882 | 883 | ### User Experience 884 | 885 | 1. **Provide Feedback**: Use popups to confirm actions 886 | 2. **Keep Text Short**: Long text may scroll or be truncated 887 | 3. **Use Appropriate Colors**: Follow color conventions (red for errors, green for success) 888 | 4. **Test on Hardware**: Always test on actual devices, not just emulators 889 | 890 | --- 891 | 892 | ## Error Handling 893 | 894 | ### Common Issues 895 | 896 | #### Display Not Working 897 | ```cpp 898 | // Check TFT_eSPI configuration 899 | #if !defined(TFT_eSPI_VERSION) 900 | #error "TFT_eSPI library not found or configured" 901 | #endif 902 | 903 | // Verify display initialization 904 | if (tft.width() == 0 || tft.height() == 0) { 905 | Serial.println("Display initialization failed"); 906 | } 907 | ``` 908 | 909 | #### Button Not Responding 910 | ```cpp 911 | // Verify button configuration 912 | void checkButtons() { 913 | Serial.println("UP: " + String(digitalRead(UP_PIN))); 914 | Serial.println("SELECT: " + String(digitalRead(SELECT_PIN))); 915 | 916 | // Check button mode 917 | if (buttonsMode == INPUT_PULLUP && buttonVoltage == HIGH) { 918 | Serial.println("Warning: Inconsistent button configuration"); 919 | } 920 | } 921 | ``` 922 | 923 | #### Memory Issues 924 | ```cpp 925 | // Monitor free heap 926 | void checkMemory() { 927 | #ifdef ESP32 928 | Serial.println("Free heap: " + String(ESP.getFreeHeap())); 929 | Serial.println("Min free heap: " + String(ESP.getMinFreeHeap())); 930 | #endif 931 | 932 | #ifdef ESP8266 933 | Serial.println("Free heap: " + String(ESP.getFreeHeap())); 934 | #endif 935 | } 936 | ``` 937 | 938 | ### Debugging Tips 939 | 940 | ```cpp 941 | // Enable verbose output 942 | #define OPENMENUOS_DEBUG 1 943 | 944 | // Check library version 945 | void printSystemInfo() { 946 | Serial.println("OpenMenuOS Version: " + menu.getLibraryVersion()); 947 | Serial.println("Display Size: " + String(menu.getTftWidth()) + "x" + String(menu.getTftHeight())); 948 | Serial.println("Current Screen: " + String(currentScreen ? currentScreen->getTitle() : "None")); 949 | } 950 | 951 | // Monitor popup state 952 | void checkPopupState() { 953 | if (PopupManager::isActive()) { 954 | Serial.println("Popup is active"); 955 | } else { 956 | PopupResult lastResult = PopupManager::getResult(); 957 | if (lastResult.buttonIndex != -1) { 958 | Serial.println("Last popup result: " + lastResult.buttonText); 959 | } 960 | } 961 | } 962 | ``` 963 | 964 | --- 965 | 966 | **OpenMenuOS v3.1.0 API Reference** 967 | Last Updated: June 15, 2025 968 | For the latest documentation, visit: https://github.com/The-Young-Maker/OpenMenuOS 969 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # OpenMenuOS Library Documentation 2 | 3 | ## **What's New in v3.1.0 (2025/06/15)?** 4 | 5 | ### **New PopupManager System** 6 | - **Professional PopupManager** with 5 distinct popup types: Info, Success, Warning, Error, and Question 7 | - **Advanced popup features**: Auto-close functionality for success messages, custom colors, icons, and automatic word wrapping 8 | - **Non-blocking operation** with proper button debouncing and input validation 9 | - **Encoder support** for popup navigation: rotate to select buttons, press to confirm 10 | - **Visual feedback** with button selection indicators and semi-transparent overlays 11 | - **Input blocking** ensures clicks don't reach underlying screens when popups are active 12 | 13 | ### **Enhanced User Experience** 14 | - **Improved button handling** with proper press/release detection preventing false triggers 15 | - **Optimized display performance** with frame comparison and efficient canvas management 16 | - **Better input isolation** between popups and menu systems 17 | 18 | ### **Code Quality Improvements** 19 | - **Bug fixes**: Canvas sprite creation, encoder pin validation, self-assignment fixes, null pointer checks 20 | - **Performance optimizations**: ScrollTextHorizontal functions enhanced with sprite caching, calculateMaxCharacters improved with binary search algorithm 21 | - **Memory efficiency**: Enhanced drawCanvasOnTFT with intelligent frame comparison 22 | - **Robust error handling** for edge cases and invalid inputs 23 | 24 | ### **Additional v3.1.0 Updates (condensed format due to many small changes and new features!)** 25 | 26 | - MenuScreen and SettingsScreen constructors updated to make the title optional 27 | - CustomScreen constructor supports optional title definition 28 | - Overloaded `addItem` method introduced to use title from `nextScreen` automatically 29 | - Added `navigateBack()` function to return to previous screen 30 | - Settings system improved to check for duplicate names and types before adding 31 | - Function added to reset settings to default (restart required) 32 | - Animations can be enabled/disabled via new function 33 | - EEPROM saving fixes for global setting objects 34 | - Toggle switch flickering bugs fixed 35 | - Cleaned and reorganized header (.h) and source (.cpp) files for maintainability 36 | - Fixed toggle switch positioning and display bugs 37 | - Added function to get parameter value by name 38 | - Added style presets setting by number 39 | - Added display rotation function 40 | - Improved selection rectangle drawing and removed unnecessary variables 41 | - Changed default button pins to -1 42 | - Settings now use vectors and non-random IDs for EEPROM consistency 43 | - Sub-screen support added in settings 44 | - Added smooth animations and encoder support throughout the library 45 | - Added visual indication for locked settings and unit support for ranges 46 | - Various small improvements and bug fixes related to UI and settings handling 47 | 48 | --- 49 | 50 | ## **What's New in v3.0.0?** 51 | Here are the significant updates and improvements: 52 | 53 | ### **Code Refactoring and Fixes** 54 | - Variables moved to the private section of the class for better encapsulation and maintainability. 55 | - Fixed issues with scrollbar height for submenus. 56 | - Fixed scrolling text issues with non-black background colors. 57 | 58 | ### **Enhanced Menu System** 59 | - Menus (main, submenus, and settings) now dynamically adapt to the display size. 60 | - Unlimited screens and menu levels—replacing the previous limitation of 3 levels. 61 | - Fixed main menu item scrolling issues when returning from submenus. 62 | 63 | ### **Settings Overhaul** 64 | - Replaced the old boolean-only settings system with object-based settings. 65 | - Supports a variety of data types: options, ranges, and booleans. 66 | - Includes default value support (overridden when values are changed in EEPROM). 67 | - Full functionality for all setting types with proper EEPROM storage (v3.1.0). 68 | 69 | ### **New Features** 70 | - Added a function to retrieve the library version. 71 | - Added smoother text scrolling. 72 | - Added the ability to set a boot image. 73 | - Added font customization options for menus. 74 | 75 | ### **Storage Handling** 76 | - Improved storage handling by using `preferences.h` on ESP32 and `EEPROM` on ESP8266. 77 | 78 | ### **Additional Fixes** 79 | - Resolved an issue where long-pressing the select button did not correctly return to the last screen. 80 | - Removed the `tft_bl` argument from the menu instance (can now be set in `TFT_eSPI`'s config). 81 | 82 | ![OpenMenuOS GIF Demo](images/OpenMenuOS_GIF.gif) 83 | This demo was recorded using the **"Screenshot"** feature provided by TFT_eSPI. 84 | The visible lines on the recording are not present on the actual display; they are likely caused by noise during data transmission from the display to the computer. 85 | 86 | Additionally, while the demo may appear laggy, this is due to the low recording frame rate. The display could not refresh while the screenshot data was being transmitted. 87 | 88 | ## Table of Contents 89 | - [Introduction](#introduction) 90 | - [Features](#features) 91 | - [Hardware Requirements](#hardware-requirements) 92 | - [Installation](#installation) 93 | - [Quick Start](#quick-start) 94 | - [PopupManager System](#popupmanager-system) 95 | - [Advanced Usage](#advanced-usage) 96 | - [API Reference](#api-reference) 97 | - [Examples](#examples) 98 | - [Troubleshooting](#troubleshooting) 99 | - [Performance Tips](#performance-tips) 100 | - [Contributing](#contributing) 101 | - [License](#license) 102 | 103 | ## 📚 Documentation 104 | - **📖 [Complete Documentation](docs/README.md)** - Arduino IDE compatible overview 105 | - **🔧 [Full API Reference](docs/api.md)** - Detailed method documentation 106 | - **💡 [Code Examples](docs/examples.md)** - Practical implementation guides 107 | 108 | ## Introduction 109 | OpenMenuOS is a comprehensive Arduino library designed to create professional, responsive menu systems on TFT displays using the TFT_eSPI library. Compatible with ESP32 and ESP8266 boards, it provides an intuitive API for building complex user interfaces with minimal code. 110 | 111 | ## Features 112 | 113 | ### **Core Menu System** 114 | - 🎛️ **Multi-level menus** with unlimited depth 115 | - 📱 **Responsive design** that adapts to any display size 116 | - 🎨 **Customizable themes** and style presets 117 | - 📊 **Dynamic scrollbars** with smooth animations 118 | - 🖼️ **Icon support** for enhanced visual appeal 119 | - ⚡ **Optimized rendering** with frame comparison 120 | 121 | ### **Professional PopupManager** 122 | - 💬 **5 popup types**: Info, Success, Warning, Error, Question 123 | - 🎭 **Visual customization**: Colors, icons, shadows, rounded corners 124 | - 🔄 **Auto-close functionality** for temporary notifications 125 | - 📝 **Automatic word wrapping** for long messages 126 | - 🎮 **Multiple input methods**: Buttons and rotary encoder support 127 | - 🚫 **Input blocking** prevents accidental interactions 128 | 129 | ### **Advanced Settings System** 130 | - ⚙️ **Multiple data types**: Boolean, Range, Option, Subscreen 131 | - 💾 **EEPROM/Preferences storage** with automatic persistence 132 | - 🔧 **Dynamic configuration** with real-time updates 133 | - 🎚️ **Animated toggle switches** and range sliders 134 | - 📋 **Option lists** with custom values 135 | 136 | ### **Input Handling** 137 | - 🔘 **Button support** with debouncing and long-press detection 138 | - 🔄 **Rotary encoder** support with interrupt-driven processing 139 | - 📱 **Touch support** (when available on display) 140 | - ⌨️ **Configurable pin assignments** and voltage levels 141 | - 🔒 **Input validation** and error prevention 142 | 143 | ### **Performance & Efficiency** 144 | - 🚀 **Optimized rendering** with sprite caching 145 | - 💾 **Memory efficient** with intelligent resource management 146 | - 🔄 **Smooth animations** with easing functions 147 | - 📊 **Frame rate optimization** and display update control 148 | - ⚡ **Fast response times** with optimized algorithms 149 | 150 | ## Hardware Requirements 151 | 152 | ### **Supported Microcontrollers** 153 | - **ESP32** (all variants) 154 | - **ESP8266** (NodeMCU, Wemos D1, etc.) 155 | - **Arduino-compatible boards** with sufficient memory 156 | 157 | ### **Display Requirements** 158 | - **TFT displays** supported by TFT_eSPI library 159 | - **Recommended**: 160x128 or higher for optimal experience 160 | - **Color depth**: 16-bit color (RGB565) 161 | 162 | ### **Input Hardware** 163 | - **Buttons**: Up, Down, Select (configurable pins) 164 | - **Rotary encoder**: CLK, DT, SW pins (optional) 165 | - **Voltage levels**: 3.3V or 5V compatible 166 | 167 | ### **Memory Requirements** 168 | - **Flash**: ~50KB for library code 169 | - **RAM**: 2-8KB depending on display size and features 170 | - **EEPROM/Preferences**: 1-2KB for settings storage 171 | 172 | ## Installation 173 | 174 | ### **Method 1: Arduino Library Manager** 175 | 1. Open Arduino IDE 176 | 2. Go to `Tools` → `Manage Libraries...` 177 | 3. Search for "OpenMenuOS" 178 | 4. Click `Install` 179 | 180 | ### **Method 2: ZIP Library** 181 | 1. Download the ZIP file from GitHub 182 | 2. In Arduino IDE: `Sketch` → `Include Library` → `Add .ZIP Library...` 183 | 3. Select the downloaded ZIP file 184 | 185 | ### **Dependencies** 186 | The following libraries are required and will be installed automatically: 187 | - **TFT_eSPI** (for display control) 188 | - **Arduino Core** libraries 189 | 190 | ### **Arduino IDE Compatibility** 191 | ✅ **Arduino IDE 1.8.x**: Fully supported 192 | ✅ **Arduino IDE 2.x**: Fully supported 193 | ✅ **Documentation**: Available in `Tools` → `Manage Libraries` → `OpenMenuOS` → `More info` 194 | ✅ **Examples**: Accessible via `File` → `Examples` → `OpenMenuOS` 195 | ✅ **Auto-complete**: Full IntelliSense support for all methods 196 | 197 | **📁 Library Structure for Arduino IDE:** 198 | ``` 199 | OpenMenuOS/ 200 | ├── src/ # Core library files 201 | │ ├── OpenMenuOS.h # Main header file 202 | │ └── OpenMenuOS.cpp # Implementation 203 | ├── examples/ # Example sketches 204 | │ └── OpenMenuOS_Simple/ # Basic usage example 205 | ├── docs/ # Documentation 206 | │ ├── README.md # Arduino IDE overview 207 | │ ├── api.md # Complete API reference 208 | │ └── examples.md # Code examples 209 | ├── library.properties # Arduino library metadata 210 | └── README.md # This comprehensive guide 211 | ``` 212 | 213 | ## Quick Start 214 | 215 | ### **Basic Setup** 216 | 217 | ```cpp 218 | #include "OpenMenuOS.h" 219 | 220 | // Pin configuration 221 | #define BTN_UP 19 // Up button pin 222 | #define BTN_DOWN -1 // Down button pin (disabled) 223 | #define BTN_SELECT 2 // Select button pin 224 | 225 | // Create menu system instance 226 | OpenMenuOS menu(BTN_UP, BTN_DOWN, BTN_SELECT); 227 | 228 | // Create screens 229 | MenuScreen mainMenu("Main Menu"); 230 | SettingsScreen settingsScreen("Settings"); 231 | 232 | void setup() { 233 | Serial.begin(115200); 234 | 235 | // Configure settings 236 | settingsScreen.addBooleanSetting("WiFi", true); 237 | settingsScreen.addRangeSetting("Brightness", 0, 100, 75, "%"); 238 | 239 | // Configure main menu 240 | mainMenu.addItem("Settings", &settingsScreen); 241 | mainMenu.addItem("About", nullptr, showAbout); 242 | 243 | // Apply style and start 244 | menu.useStylePreset("Rabbit_R1"); 245 | menu.begin(&mainMenu); 246 | } 247 | 248 | void loop() { 249 | menu.loop(); 250 | } 251 | 252 | void showAbout() { 253 | PopupManager::showInfo("OpenMenuOS v3.1.0\nProfessional Menu System", "About"); 254 | } 255 | ``` 256 | 257 | ### **Encoder Setup** 258 | ```cpp 259 | // Enable rotary encoder support 260 | menu.setEncoderPin(5, 2); // CLK, DT pins 261 | menu.setSelectPin(19); // Encoder button pin 262 | ``` 263 | 264 | ## PopupManager System 265 | 266 | ### **Popup Types** 267 | OpenMenuOS includes a professional popup system with 5 built-in types: 268 | 269 | #### **1. Info Popup** 270 | ```cpp 271 | PopupManager::showInfo("Operation completed successfully!", "Information"); 272 | ``` 273 | 274 | #### **2. Success Popup** 275 | ```cpp 276 | PopupManager::showSuccess("Settings saved!", "Success"); 277 | // Auto-closes after 3 seconds 278 | ``` 279 | 280 | #### **3. Warning Popup** 281 | ```cpp 282 | PopupManager::showWarning("Low battery detected", "Warning"); 283 | ``` 284 | 285 | #### **4. Error Popup** 286 | ```cpp 287 | PopupManager::showError("Connection failed", "Error"); 288 | ``` 289 | 290 | #### **5. Question Popup** 291 | ```cpp 292 | PopupResult result = PopupManager::showQuestion("Save changes?", "Confirm"); 293 | // Returns PopupResult::OK or PopupResult::CANCEL 294 | ``` 295 | 296 | ### **Advanced Popup Configuration** 297 | ```cpp 298 | PopupConfig config; 299 | config.message = "Custom popup message"; 300 | config.title = "Custom Title"; 301 | config.type = PopupType::INFO; 302 | config.autoClose = true; 303 | config.autoCloseDelay = 5000; // 5 seconds 304 | config.showButtons = true; 305 | config.showCancelButton = true; 306 | config.customIcon = myIcon; 307 | config.customIconWidth = 32; 308 | config.customIconHeight = 32; 309 | 310 | PopupResult result = PopupManager::show(config); 311 | ``` 312 | 313 | ### **Popup Input Handling** 314 | ```cpp 315 | void loop() { 316 | // Handle popup interactions first 317 | PopupResult popupResult = PopupManager::update(); 318 | 319 | // Only process menu if no popup is active 320 | if (!PopupManager::isActive()) { 321 | menu.loop(); 322 | } 323 | 324 | // Handle popup results 325 | if (popupResult != PopupResult::NONE) { 326 | switch (popupResult) { 327 | case PopupResult::OK: 328 | Serial.println("User confirmed"); 329 | break; 330 | case PopupResult::CANCEL: 331 | Serial.println("User cancelled"); 332 | break; 333 | } 334 | } 335 | } 336 | ``` 337 | 338 | ## Advanced Usage 339 | 340 | ### **Custom Screens** 341 | ```cpp 342 | CustomScreen myScreen("Custom"); 343 | 344 | void setup() { 345 | // Define custom drawing function 346 | myScreen.customDraw = []() { 347 | canvas.fillScreen(TFT_BLACK); 348 | canvas.setTextColor(TFT_WHITE); 349 | canvas.drawString("Custom Content", 10, 50); 350 | 351 | // Draw graphics 352 | canvas.fillCircle(100, 100, 20, TFT_BLUE); 353 | canvas.drawRect(50, 50, 100, 50, TFT_GREEN); 354 | }; 355 | 356 | mainMenu.addItem("Custom Screen", &myScreen); 357 | } 358 | ``` 359 | 360 | ### **Dynamic Menu Items** 361 | ```cpp 362 | void updateMenu() { 363 | mainMenu.clearItems(); 364 | 365 | if (wifiConnected) { 366 | mainMenu.addItem("Network Settings", &networkMenu); 367 | mainMenu.addItem("Cloud Sync", nullptr, syncToCloud); 368 | } else { 369 | mainMenu.addItem("Connect WiFi", nullptr, connectWiFi); 370 | } 371 | 372 | mainMenu.addItem("Local Settings", &localSettings); 373 | } 374 | ``` 375 | 376 | ### **Settings with Callbacks** 377 | ```cpp 378 | void onBrightnessChange(uint8_t newValue) { 379 | analogWrite(BACKLIGHT_PIN, newValue * 255 / 100); 380 | } 381 | 382 | void setup() { 383 | settingsScreen.addRangeSetting("Brightness", 0, 100, 75, "%"); 384 | settingsScreen.setSettingCallback("Brightness", onBrightnessChange); 385 | } 386 | ``` 387 | 388 | ### **Menu Styling** 389 | ```cpp 390 | // Use built-in presets 391 | menu.useStylePreset("Rabbit_R1"); // Modern blue theme 392 | menu.useStylePreset("Default"); // Classic theme 393 | 394 | // Custom styling 395 | menu.setMenuStyle(1); // Style mode 396 | menu.setSelectionBorderColor(TFT_CYAN); // Selection border 397 | menu.setSelectionFillColor(TFT_DARKGREY); // Selection fill 398 | menu.setScrollbar(true); // Enable scrollbar 399 | menu.setScrollbarStyle(1); // Modern scrollbar 400 | menu.setScrollbarColor(TFT_WHITE); // Scrollbar color 401 | ``` 402 | 403 | ### **Performance Optimization** 404 | ```cpp 405 | // Enable display optimization (saves CPU/memory) 406 | menu.setOptimizeDisplayUpdates(true); 407 | 408 | // Control animations 409 | menu.setAnimation(true); // Enable smooth animations 410 | 411 | // Disable text scrolling for better performance 412 | menu.setTextScroll(false); 413 | ``` 414 | 415 | ## API Reference 416 | 417 | ### **OpenMenuOS Class** 418 | 419 | #### **Constructor** 420 | ```cpp 421 | OpenMenuOS(int btn_up, int btn_down, int btn_sel) 422 | ``` 423 | - `btn_up`: Pin for up button (-1 to disable) 424 | - `btn_down`: Pin for down button (-1 to disable) 425 | - `btn_sel`: Pin for select button (-1 to disable) 426 | 427 | #### **Core Methods** 428 | ```cpp 429 | void begin(Screen* mainMenu) // Initialize with default rotation 430 | void begin(int rotation, Screen* mainMenu) // Initialize with custom rotation 431 | void loop() // Main update loop 432 | void redirectToScreen(Screen* screen) // Navigate to specific screen 433 | void navigateBack() // Go back to previous screen 434 | const char* getLibraryVersion() // Get library version string 435 | ``` 436 | 437 | #### **Display Configuration** 438 | ```cpp 439 | void setDisplayRotation(int rotation) // Set display rotation (0-3) 440 | void setOptimizeDisplayUpdates(bool enabled) // Enable frame comparison 441 | bool getOptimizeDisplayUpdates() // Get optimization state 442 | void drawCanvasOnTFT() // Manually update display 443 | ``` 444 | 445 | #### **Styling Methods** 446 | ```cpp 447 | void useStylePreset(char* preset) // Apply predefined style 448 | void useStylePreset(int preset) // Apply style by number 449 | void setMenuStyle(int style) // Set menu appearance style 450 | void setSelectionBorderColor(uint16_t color) // Selection border color 451 | void setSelectionFillColor(uint16_t color) // Selection fill color 452 | void setScrollbar(bool enabled) // Enable/disable scrollbar 453 | void setScrollbarStyle(int style) // Set scrollbar appearance 454 | void setScrollbarColor(uint16_t color) // Set scrollbar color 455 | ``` 456 | 457 | #### **Font Configuration** 458 | ```cpp 459 | void setMenuFont(const GFXfont* font) // Set regular menu font 460 | void setMenuFontBold(const GFXfont* font) // Set bold menu font 461 | ``` 462 | 463 | #### **Input Configuration** 464 | ```cpp 465 | void setButtonsMode(char* mode) // "High" or "Low" button logic 466 | void setEncoderPin(uint8_t clk, uint8_t dt) // Configure rotary encoder 467 | void setUpPin(uint8_t pin) // Set up button pin 468 | void setDownPin(uint8_t pin) // Set down button pin 469 | void setSelectPin(uint8_t pin) // Set select button pin 470 | ``` 471 | 472 | #### **Animation & Behavior** 473 | ```cpp 474 | void setAnimation(bool enabled) // Enable/disable animations 475 | void setTextScroll(bool enabled) // Enable/disable text scrolling 476 | void setButtonAnimation(bool enabled) // Enable button press animation 477 | ``` 478 | 479 | #### **Boot Configuration** 480 | ```cpp 481 | void showBootImage(bool enabled) // Show boot image on startup 482 | void setBootImage(uint16_t* image, uint16_t h, uint16_t w) // Set custom boot image 483 | ``` 484 | 485 | #### **Utility Methods** 486 | ```cpp 487 | int getTftHeight() // Get display height 488 | int getTftWidth() // Get display width 489 | int UpButton() // Get up button pin 490 | int DownButton() // Get down button pin 491 | int SelectButton() // Get select button pin 492 | ``` 493 | 494 | ### **MenuScreen Class** 495 | 496 | #### **Constructor** 497 | ```cpp 498 | MenuScreen() // Create menu without title 499 | MenuScreen(const char* title) // Create menu with title 500 | ``` 501 | 502 | #### **Item Management** 503 | ```cpp 504 | void addItem(const char* label, Screen* nextScreen = nullptr, 505 | ActionCallback action = nullptr, const uint16_t* image = nullptr) 506 | void addItem(Screen* nextScreen, ActionCallback action = nullptr, 507 | const uint16_t* image = nullptr) // Auto-use screen title 508 | void clearItems() // Remove all items 509 | int getIndex() // Get current selection index 510 | ``` 511 | 512 | #### **Display Methods** 513 | ```cpp 514 | void draw() // Render the menu 515 | void handleInput() // Process user input 516 | const char* getTitle() // Get menu title 517 | ``` 518 | 519 | ### **SettingsScreen Class** 520 | 521 | #### **Constructor** 522 | ```cpp 523 | SettingsScreen() // Create without title 524 | SettingsScreen(const char* title) // Create with title 525 | ``` 526 | 527 | #### **Setting Management** 528 | ```cpp 529 | void addBooleanSetting(const char* name, bool defaultValue) 530 | void addRangeSetting(const char* name, uint8_t min, uint8_t max, 531 | uint8_t defaultValue, const char* unit = nullptr) 532 | void addOptionSetting(const char* name, const char** options, 533 | uint8_t count, uint8_t defaultIndex = 0) 534 | void addSubscreenSetting(const char* name, Screen* targetScreen) 535 | ``` 536 | 537 | #### **Value Access** 538 | ```cpp 539 | uint8_t getSettingValue(int index) // Get value by index 540 | uint8_t getSettingValue(const char* name) // Get value by name 541 | String getSettingName(int index) // Get setting name 542 | Setting::Type getSettingType(uint8_t index) // Get setting type 543 | ``` 544 | 545 | #### **Storage Methods** 546 | ```cpp 547 | void saveToEEPROM() // Save all settings 548 | void readFromEEPROM() // Load all settings 549 | void resetSettings() // Reset to defaults 550 | ``` 551 | 552 | #### **Modification Methods** 553 | ```cpp 554 | void modify(int8_t direction, int index) // Modify setting by index 555 | void modify(int8_t direction, const char* name) // Modify setting by name 556 | ``` 557 | 558 | ### **CustomScreen Class** 559 | 560 | #### **Constructor** 561 | ```cpp 562 | CustomScreen() // Create without title 563 | CustomScreen(const char* title) // Create with title 564 | ``` 565 | 566 | #### **Custom Drawing** 567 | ```cpp 568 | std::function customDraw // Custom drawing function 569 | void draw() // Render the screen 570 | void handleInput() // Process user input 571 | ``` 572 | 573 | ### **PopupManager Class** 574 | 575 | #### **Static Methods** 576 | ```cpp 577 | static PopupResult show(const PopupConfig& config) // Show custom popup 578 | static PopupResult showInfo(const char* message, const char* title = nullptr) 579 | static PopupResult showSuccess(const char* message, const char* title = nullptr) 580 | static PopupResult showWarning(const char* message, const char* title = nullptr) 581 | static PopupResult showError(const char* message, const char* title = nullptr) 582 | static PopupResult showQuestion(const char* message, const char* title = nullptr) 583 | static PopupResult update() // Update popup state 584 | static bool isActive() // Check if popup visible 585 | static void hide() // Hide current popup 586 | ``` 587 | 588 | ### **Enums and Structures** 589 | 590 | #### **PopupType** 591 | ```cpp 592 | enum class PopupType { 593 | INFO, // Information popup (blue) 594 | SUCCESS, // Success popup (green, auto-close) 595 | WARNING, // Warning popup (orange) 596 | ERROR, // Error popup (red) 597 | QUESTION // Question popup (cyan, Yes/No buttons) 598 | }; 599 | ``` 600 | 601 | #### **PopupResult** 602 | ```cpp 603 | enum class PopupResult { 604 | NONE, // No interaction 605 | OK, // OK/Yes button pressed 606 | CANCEL // Cancel/No button pressed 607 | }; 608 | ``` 609 | 610 | #### **PopupConfig** 611 | ```cpp 612 | struct PopupConfig { 613 | const char* message = nullptr; // Popup message text 614 | const char* title = nullptr; // Popup title (optional) 615 | PopupType type = PopupType::INFO; // Popup type 616 | bool showButtons = true; // Show action buttons 617 | bool showCancelButton = false; // Show cancel/no button 618 | bool autoClose = false; // Auto-close popup 619 | uint32_t autoCloseDelay = 3000; // Auto-close delay (ms) 620 | const uint16_t* customIcon = nullptr; // Custom icon image 621 | uint16_t customIconWidth = 0; // Custom icon width 622 | uint16_t customIconHeight = 0; // Custom icon height 623 | }; 624 | ``` 625 | 626 | #### **Setting Types** 627 | ```cpp 628 | enum class Setting::Type { 629 | BOOLEAN, // Toggle switch (true/false) 630 | RANGE, // Numeric range with min/max 631 | OPTION, // Selection from predefined options 632 | SUBSCREEN // Navigation to another screen 633 | }; 634 | ``` 635 | 636 | ## Examples 637 | 638 | ### **Complete Application Example** 639 | ```cpp 640 | #include "OpenMenuOS.h" 641 | 642 | // Hardware configuration 643 | #define BTN_UP 19 644 | #define BTN_SELECT 2 645 | #define ENCODER_CLK 5 646 | #define ENCODER_DT 2 647 | 648 | // Menu system 649 | OpenMenuOS menu(BTN_UP, -1, BTN_SELECT); 650 | 651 | // Screens 652 | MenuScreen mainMenu("Smart Device"); 653 | MenuScreen deviceMenu("Device Control"); 654 | MenuScreen networkMenu("Network"); 655 | SettingsScreen settingsScreen("Settings"); 656 | CustomScreen aboutScreen("About"); 657 | 658 | // Device state 659 | bool ledState = false; 660 | uint8_t brightness = 75; 661 | bool wifiEnabled = true; 662 | 663 | void setup() { 664 | Serial.begin(115200); 665 | 666 | // Configure custom about screen 667 | aboutScreen.customDraw = []() { 668 | canvas.fillScreen(TFT_BLACK); 669 | canvas.setTextColor(TFT_WHITE); 670 | canvas.setFreeFont(&FreeMono9pt7b); 671 | 672 | canvas.drawString("Smart Device v1.0", 10, 30); 673 | canvas.drawString("OpenMenuOS v" + String(menu.getLibraryVersion()), 10, 50); 674 | canvas.drawString("ESP32-based IoT Device", 10, 70); 675 | 676 | canvas.setTextColor(TFT_CYAN); 677 | canvas.drawString("Press SELECT to return", 10, 100); 678 | }; 679 | 680 | // Configure settings 681 | settingsScreen.addBooleanSetting("WiFi", wifiEnabled); 682 | settingsScreen.addBooleanSetting("Bluetooth", false); 683 | settingsScreen.addRangeSetting("Brightness", 0, 100, brightness, "%"); 684 | settingsScreen.addRangeSetting("Volume", 0, 10, 5); 685 | 686 | const char* themes[] = {"Dark", "Light", "Auto"}; 687 | settingsScreen.addOptionSetting("Theme", themes, 3, 0); 688 | 689 | settingsScreen.addSubscreenSetting("Network Settings", &networkMenu); 690 | 691 | // Configure device menu 692 | deviceMenu.addItem("Toggle LED", nullptr, toggleLED); 693 | deviceMenu.addItem("Brightness Control", nullptr, showBrightnessControl); 694 | deviceMenu.addItem("Device Status", nullptr, showDeviceStatus); 695 | deviceMenu.addItem("Reset Device", nullptr, resetDevice); 696 | 697 | // Configure network menu 698 | networkMenu.addItem("WiFi Settings", nullptr, configureWiFi); 699 | networkMenu.addItem("Network Status", nullptr, showNetworkStatus); 700 | networkMenu.addItem("Reset Network", nullptr, resetNetwork); 701 | 702 | // Configure main menu 703 | mainMenu.addItem("Device Control", &deviceMenu); 704 | mainMenu.addItem("Settings", &settingsScreen); 705 | mainMenu.addItem("Network", &networkMenu); 706 | mainMenu.addItem("About", &aboutScreen); 707 | 708 | // Setup hardware 709 | menu.setEncoderPin(ENCODER_CLK, ENCODER_DT); 710 | menu.useStylePreset("Rabbit_R1"); 711 | menu.setScrollbar(true); 712 | menu.setAnimation(true); 713 | 714 | // Initialize 715 | menu.begin(&mainMenu); 716 | 717 | PopupManager::showSuccess("System initialized successfully!", "Welcome"); 718 | } 719 | 720 | void loop() { 721 | // Handle popup interactions first 722 | PopupResult popupResult = PopupManager::update(); 723 | 724 | // Only process menu if no popup is active 725 | if (!PopupManager::isActive()) { 726 | menu.loop(); 727 | } 728 | 729 | // Handle popup results 730 | if (popupResult != PopupResult::NONE) { 731 | handlePopupResult(popupResult); 732 | } 733 | 734 | // Update settings-based configurations 735 | brightness = settingsScreen.getSettingValue("Brightness"); 736 | wifiEnabled = settingsScreen.getSettingValue("WiFi"); 737 | } 738 | 739 | // Action callbacks 740 | void toggleLED() { 741 | ledState = !ledState; 742 | digitalWrite(LED_BUILTIN, ledState); 743 | 744 | if (ledState) { 745 | PopupManager::showSuccess("LED turned ON", "Success"); 746 | } else { 747 | PopupManager::showInfo("LED turned OFF", "Info"); 748 | } 749 | } 750 | 751 | void showBrightnessControl() { 752 | PopupConfig config; 753 | config.message = "Current brightness: " + String(brightness) + "%\nAdjust in Settings menu"; 754 | config.title = "Brightness Control"; 755 | config.type = PopupType::INFO; 756 | PopupManager::show(config); 757 | } 758 | 759 | void showDeviceStatus() { 760 | String status = "LED: " + String(ledState ? "ON" : "OFF") + "\n"; 761 | status += "Brightness: " + String(brightness) + "%\n"; 762 | status += "WiFi: " + String(wifiEnabled ? "Enabled" : "Disabled") + "\n"; 763 | status += "Free Memory: " + String(ESP.getFreeHeap()) + " bytes"; 764 | 765 | PopupManager::showInfo(status.c_str(), "Device Status"); 766 | } 767 | 768 | void resetDevice() { 769 | PopupResult result = PopupManager::showQuestion("This will restart the device.\nContinue?", "Reset Device"); 770 | 771 | if (result == PopupResult::OK) { 772 | PopupManager::showWarning("Restarting device...", "Reset"); 773 | delay(2000); 774 | ESP.restart(); 775 | } 776 | } 777 | 778 | void configureWiFi() { 779 | if (wifiEnabled) { 780 | PopupManager::showInfo("WiFi configuration would open here.\nNot implemented in this example.", "WiFi Setup"); 781 | } else { 782 | PopupManager::showWarning("WiFi is disabled.\nEnable in Settings first.", "WiFi Disabled"); 783 | } 784 | } 785 | 786 | void showNetworkStatus() { 787 | String status = "WiFi: " + String(wifiEnabled ? "Enabled" : "Disabled") + "\n"; 788 | if (wifiEnabled) { 789 | status += "Status: Connected\n"; 790 | status += "IP: 192.168.1.100\n"; 791 | status += "RSSI: -45 dBm"; 792 | } else { 793 | status += "Status: Disabled"; 794 | } 795 | 796 | PopupManager::showInfo(status.c_str(), "Network Status"); 797 | } 798 | 799 | void resetNetwork() { 800 | PopupResult result = PopupManager::showQuestion("Reset all network settings?", "Reset Network"); 801 | 802 | if (result == PopupResult::OK) { 803 | PopupManager::showSuccess("Network settings reset!", "Success"); 804 | } 805 | } 806 | 807 | void handlePopupResult(PopupResult result) { 808 | switch (result) { 809 | case PopupResult::OK: 810 | Serial.println("User confirmed action"); 811 | break; 812 | case PopupResult::CANCEL: 813 | Serial.println("User cancelled action"); 814 | break; 815 | default: 816 | break; 817 | } 818 | } 819 | ``` 820 | 821 | ## Troubleshooting 822 | 823 | ### **Common Issues** 824 | 825 | #### **Display Issues** 826 | - **Blank screen**: Check TFT_eSPI configuration and wiring 827 | - **Wrong colors**: Verify color order in TFT_eSPI settings 828 | - **Flickering**: Enable display optimization with `setOptimizeDisplayUpdates(true)` 829 | 830 | #### **Input Issues** 831 | - **Buttons not responding**: Check pin numbers and button voltage logic 832 | - **Encoder not working**: Verify CLK/DT pin connections and enable interrupts 833 | - **Multiple triggers**: Increase debounce time or check wiring 834 | 835 | #### **Memory Issues** 836 | - **Crashes on ESP8266**: Disable display optimization for large displays 837 | - **Heap overflow**: Reduce number of menu items or use simpler graphics 838 | - **EEPROM corruption**: Call `resetSettings()` to restore defaults 839 | 840 | #### **Performance Issues** 841 | - **Slow response**: Disable animations or text scrolling 842 | - **High CPU usage**: Enable frame comparison optimization 843 | - **Jerky animations**: Check for blocking code in main loop 844 | 845 | ### **Debug Tips** 846 | ```cpp 847 | // Enable debug output 848 | Serial.println("Library version: " + String(menu.getLibraryVersion())); 849 | Serial.println("Display size: " + String(menu.getTftWidth()) + "x" + String(menu.getTftHeight())); 850 | Serial.println("Free heap: " + String(ESP.getFreeHeap())); 851 | 852 | // Check settings values 853 | for (int i = 0; i < settingsScreen.getTotalSettings(); i++) { 854 | Serial.println("Setting " + String(i) + ": " + String(settingsScreen.getSettingValue(i))); 855 | } 856 | ``` 857 | 858 | ## Performance Tips 859 | 860 | ### **Memory Optimization** 861 | - Use `setOptimizeDisplayUpdates(false)` on ESP8266 with large displays 862 | - Minimize the number of concurrent menu items 863 | - Use simple icons (16x16 pixels or smaller) 864 | - Avoid deeply nested menu structures 865 | 866 | ### **CPU Optimization** 867 | - Enable frame comparison: `setOptimizeDisplayUpdates(true)` 868 | - Disable unnecessary animations: `setAnimation(false)` 869 | - Use shorter text to avoid scrolling: `setTextScroll(false)` 870 | - Optimize custom drawing functions 871 | 872 | ### **Display Optimization** 873 | - Choose appropriate display update frequency 874 | - Use solid colors instead of gradients 875 | - Minimize the use of anti-aliased graphics 876 | - Keep popup messages concise 877 | 878 | ### **Best Practices** 879 | - Initialize heavy objects in `setup()`, not in menu callbacks 880 | - Use static variables in callbacks to avoid repeated allocations 881 | - Handle long-running operations asynchronously 882 | - Test on target hardware early in development 883 | 884 | ## Contributing 885 | 886 | We welcome contributions to OpenMenuOS! Here's how you can help: 887 | 888 | ### **Ways to Contribute** 889 | - 🐛 **Bug Reports**: Report issues with detailed reproduction steps 890 | - 💡 **Feature Requests**: Suggest new features or improvements 891 | - 📝 **Documentation**: Improve documentation and examples 892 | - 🔧 **Code Contributions**: Submit bug fixes and new features 893 | - 🧪 **Testing**: Test on different hardware configurations 894 | - 🎨 **Design**: Create new themes and visual improvements 895 | 896 | ### **Development Setup** 897 | 1. Fork the repository on GitHub 898 | 2. Clone your fork locally 899 | 3. Create a new branch for your feature/fix 900 | 4. Make your changes following the coding standards 901 | 5. Test thoroughly on target hardware 902 | 6. Submit a pull request with clear description 903 | 904 | ### **Coding Standards** 905 | - Use descriptive variable and function names 906 | - Follow existing code formatting and style 907 | - Add comments for complex logic 908 | - Include examples for new features 909 | - Ensure compatibility with ESP32 and ESP8266 910 | - Test with different display sizes 911 | 912 | ### **Pull Request Guidelines** 913 | - One feature/fix per pull request 914 | - Include updated documentation 915 | - Add examples if introducing new features 916 | - Ensure all examples compile successfully 917 | - Update version history in README 918 | 919 | ### **Bug Report Template** 920 | ``` 921 | **Hardware:** 922 | - Board: ESP32/ESP8266 923 | - Display: [Model and size] 924 | - Library version: [Version] 925 | 926 | **Issue Description:** 927 | [Clear description of the problem] 928 | 929 | **Steps to Reproduce:** 930 | 1. [Step 1] 931 | 2. [Step 2] 932 | 3. [Step 3] 933 | 934 | **Expected Behavior:** 935 | [What should happen] 936 | 937 | **Actual Behavior:** 938 | [What actually happens] 939 | 940 | **Code:** 941 | ```cpp 942 | [Minimal code that reproduces the issue] 943 | ``` 944 | 945 | **Additional Information:** 946 | [Any other relevant details] 947 | ``` 948 | 949 | ## License 950 | 951 | This library is licensed under the **MIT License**. This means you can: 952 | 953 | ✅ **Use** the library in personal and commercial projects 954 | ✅ **Modify** the source code to fit your needs 955 | ✅ **Distribute** modified versions 956 | ✅ **Include** in proprietary software 957 | 958 | **Requirements:** 959 | - Include the original license notice 960 | - Include copyright notice 961 | 962 | See the [LICENSE](https://github.com/The-Young-Maker/OpenMenuOS/blob/main/LICENSE) file for complete terms. 963 | 964 | --- 965 | 966 | ## Credits 967 | 968 | **Author**: The Young Maker 969 | **GitHub**: [https://github.com/The-Young-Maker/OpenMenuOS](https://github.com/The-Young-Maker/OpenMenuOS) 970 | **Version**: 3.1.0 971 | **Last Updated**: June 15, 2025 972 | 973 | ### **Acknowledgments** 974 | - TFT_eSPI library by Bodmer for display handling 975 | - Arduino community for inspiration and feedback 976 | - Contributors who helped improve the library 977 | 978 | ### **Support** 979 | - 📖 **Documentation**: Check this README and examples 980 | - 💬 **Issues**: GitHub Issues for bug reports and questions 981 | - 🌟 **Star** the repository if you find it useful! 982 | - 🔔 **Watch** for updates and new releases 983 | 984 | --- 985 | 986 | **Made with ❤️ for the Arduino community** 987 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . --------------------------------------------------------------------------------