diff --git a/.gitattributes b/.gitattributes index f91f646..ca5c85e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,10 +3,9 @@ # # Linux start script should use lf /gradlew text eol=lf - # These are Windows script files and should use crlf *.bat text eol=crlf - # Binary files should be left untouched *.jar binary - +*.tga filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index de0d53b..7efe1e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ build .cxx .DS_Store - +/designer/test/*test_result.txt diff --git a/CLAUDE.md b/CLAUDE.md index c873b55..a0ce3b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,8 +2,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +## Project Overview + +Mosis is a **virtual smartphone OS** for VR games and applications. It provides a phone-like device that users can interact with inside VR environments, with real smartphone functionality. + +### Project Components + +| Component | Location | Purpose | +|-----------|----------|---------| +| Android Service | `src/main/` | Native service running RmlUi renderer | +| Desktop Designer | `designer/` | UI development with hot-reload | +| Designer Tests | `designer-test/` | Automated UI testing framework | +| UI Assets | `src/main/assets/` | Shared RML/RCSS/Lua assets | + ## Build Commands +### Android (Gradle) + ```bash # Build entire project ./gradlew build @@ -30,6 +45,35 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ./gradlew connectedAndroidTest ``` +### Desktop Designer (CMake) + +```bash +# Configure (from designer/ folder) +cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake + +# Build +cmake --build build --config Debug + +# Run with hot-reload +./build/Debug/mosis-designer.exe ../src/main/assets/apps/home/home.rml + +# Run with logging and hierarchy dump +./build/Debug/mosis-designer.exe ../src/main/assets/apps/home/home.rml --log output.log --hierarchy hierarchy.json +``` + +### Designer Tests (CMake) + +```bash +# Configure (from designer-test/ folder) +cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake + +# Build +cmake --build build --config Debug + +# Run tests +./build/Debug/designer-test.exe +``` + ## Environment Requirements Required environment variables: @@ -89,6 +133,145 @@ Kotlin NativeService → JNI → mosis-service (IMosisService) ## Dependencies -- vcpkg manages native dependencies (RmlUi) +- vcpkg manages native dependencies (RmlUi, GLFW, Freetype, Lua, libpng, nlohmann-json) - CMake build system with vcpkg toolchain integration -- Target architecture: arm64-v8a only +- Android target architecture: arm64-v8a only +- Desktop target: Windows x64 (MSVC) + +## Desktop Designer + +The desktop designer (`designer/`) provides rapid UI development with: + +- **Hot-reload**: Automatically reloads when RML/RCSS/Lua files change +- **UI Hierarchy Dumping**: Exports element tree to JSON for inspection +- **Screenshot Capture**: PNG export via F12 key +- **Logging**: Detailed output for debugging navigation and events + +### Key Files + +| File | Purpose | +|------|---------| +| `designer/src/main.cpp` | Main entry point, GLFW window, event loop | +| `designer/src/desktop_kernel.cpp` | RmlUi context management, rendering | +| `designer/src/testing/ui_inspector.cpp` | UI hierarchy JSON export | +| `designer/src/testing/visual_capture.cpp` | PNG screenshot capture | + +### Command Line Options + +``` +--log Write logs to file +--hierarchy Dump UI hierarchy JSON each frame +--dump Single-shot dump mode (screenshot + hierarchy) +``` + +## Automated Testing Framework + +The designer-test (`designer-test/`) provides automated UI testing: + +### Test Architecture + +1. **WindowController**: Finds designer window, sends mouse/keyboard events via Windows SendInput API +2. **HierarchyReader**: Parses UI hierarchy JSON to find elements by ID/class +3. **LogParser**: Monitors log file for navigation events +4. **TestRunner**: Orchestrates test execution, reports results + +### Writing Tests + +```cpp +// Find element by ID and click it +bool ClickById(TestContext& ctx, const std::string& id) { + ctx.hierarchy.Reload(); + auto element = ctx.hierarchy.FindById(id); + if (!element) return false; + + int x = element->bounds.centerX(); + int y = element->bounds.centerY(); + ScaleToPhysical(ctx, x, y); // Convert logical to physical coords + ctx.window.SendClick(x, y); + return true; +} + +// Test example +bool TestNavigateToDialer(TestContext& ctx) { + GoHome(ctx); + ctx.log.Clear(); + + if (!ClickById(ctx, "dock-phone")) return false; + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/dialer/dialer.rml"); +} +``` + +### Test Output + +Tests produce JSON results at `test_results.json`: +```json +{ + "name": "Mosis Designer UI Tests", + "summary": {"passed": 5, "failed": 0, "total": 5}, + "tests": [ + {"name": "Navigate to Dialer", "status": "passed", "duration_ms": 3500} + ] +} +``` + +## UI Assets Structure + +All UI assets are in `src/main/assets/`: + +``` +assets/ +├── apps/ # System apps (RML documents) +│ ├── home/home.rml # Home screen launcher +│ ├── dialer/dialer.rml # Phone dialer +│ ├── messages/ # Messages app +│ ├── contacts/ # Contacts app +│ ├── settings/ # Settings app +│ └── browser/ # Web browser +├── ui/ # Shared stylesheets +│ ├── html.rcss # Base HTML element styles +│ ├── theme.rcss # Design tokens (colors, typography) +│ └── components.rcss # Reusable UI components +├── scripts/ # Lua scripts +│ └── navigation.lua # Navigation system +├── icons/ # TGA icon files (24x24, 32x32) +└── fonts/ # TTF fonts (LatoLatin, Roboto) +``` + +### Navigation System + +Navigation is handled by `scripts/navigation.lua`: + +```lua +-- Navigate to a screen +navigateTo('dialer') -- Push to history, animate forward + +-- Go back +goBack() -- Pop from history, animate back + +-- Go home +goHome() -- Clear history, return to home +``` + +### Element IDs for Testing + +Key elements have IDs for automated testing: + +| ID | Location | Purpose | +|----|----------|---------| +| `dock-phone` | home.rml | Phone dock icon | +| `dock-messages` | home.rml | Messages dock icon | +| `dock-contacts` | home.rml | Contacts dock icon | +| `dock-browser` | home.rml | Browser dock icon | +| `app-settings` | home.rml | Settings app icon | + +### CSS Classes for Navigation + +Back buttons use `app-bar-nav` class for automated GoHome: +```html +
+ +
+``` diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..6c0e31e --- /dev/null +++ b/TESTING.md @@ -0,0 +1,397 @@ +# Mosis UI Testing Framework + +This document describes the automated UI testing framework for Mosis virtual smartphone. + +## Overview + +The testing framework enables automated validation of UI behavior through: + +1. **UI Hierarchy Inspection**: JSON dump of all UI elements with bounds +2. **Input Simulation**: Mouse clicks via Windows SendInput API +3. **Log Verification**: Check navigation events via log file parsing +4. **Test Results**: JSON output compatible with CI/CD pipelines + +## Components + +### Designer (mosis-designer.exe) + +The desktop designer serves as the test target. When launched with testing options: + +```bash +mosis-designer.exe home.rml --log test.log --hierarchy hierarchy.json +``` + +- `--log`: Writes all RmlUi INFO logs to file (navigation events, errors) +- `--hierarchy`: Dumps UI element tree to JSON each frame + +### Test Runner (designer-test.exe) + +Automated test executor that: + +1. Launches designer with testing options +2. Waits for window to appear +3. Reads UI hierarchy to find elements +4. Sends input events to simulate user interaction +5. Verifies results via log file +6. Outputs JSON test results + +## UI Hierarchy Format + +The hierarchy JSON contains all visible UI elements: + +```json +{ + "timestamp": 1705312200, + "screen": "apps/home/home.rml", + "resolution": {"width": 677, "height": 1202}, + "elements": { + "tag": "body", + "id": "", + "classes": ["home-screen"], + "bounds": {"x": 0, "y": 0, "width": 677, "height": 1202}, + "visible": true, + "text": null, + "children": [ + { + "tag": "div", + "id": "dock-phone", + "classes": ["dock-item"], + "bounds": {"x": 85, "y": 1138, "width": 56, "height": 56}, + "visible": true, + "text": null, + "children": [...] + } + ] + } +} +``` + +### Key Fields + +| Field | Description | +|-------|-------------| +| `tag` | HTML element tag (div, span, img, input) | +| `id` | Element ID attribute (empty if none) | +| `classes` | Array of CSS class names | +| `bounds` | Position and size in logical pixels | +| `visible` | Whether element is displayed | +| `text` | Text content (for leaf text nodes) | +| `children` | Nested child elements | + +### Resolution Note + +The `resolution` field contains RmlUi's logical dimensions, which may differ from physical window size due to DPI scaling. Tests must scale coordinates: + +```cpp +// Scale from hierarchy (logical) to window (physical) +int physicalX = logicalX * windowWidth / hierarchyWidth; +int physicalY = logicalY * windowHeight / hierarchyHeight; +``` + +## Writing Tests + +### Test Function Signature + +```cpp +bool MyTest(TestContext& ctx); +``` + +TestContext provides: +- `ctx.window` - WindowController for input +- `ctx.log` - LogParser for log verification +- `ctx.hierarchy` - HierarchyReader for element lookup + +### Finding Elements + +```cpp +// By ID +auto element = ctx.hierarchy.FindById("dock-phone"); + +// By class (returns all matches) +auto buttons = ctx.hierarchy.FindByClass("btn-icon"); + +// By tag +auto divs = ctx.hierarchy.FindByTag("div"); +``` + +### Clicking Elements + +```cpp +bool ClickById(TestContext& ctx, const std::string& id) { + ctx.hierarchy.Reload(); // Get fresh hierarchy + + auto element = ctx.hierarchy.FindById(id); + if (!element || !element->visible) { + return false; + } + + // Get center point + int x = element->bounds.centerX(); + int y = element->bounds.centerY(); + + // Scale to physical coordinates + ScaleToPhysical(ctx, x, y); + + // Send click + ctx.window.SendClick(x, y); + return true; +} +``` + +### Verifying Navigation + +```cpp +bool TestNavigateToDialer(TestContext& ctx) { + GoHome(ctx); // Return to home screen + ctx.log.Clear(); // Clear previous logs + + if (!ClickById(ctx, "dock-phone")) { + return false; + } + + // Wait for navigation animation + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Check log for navigation event + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/dialer/dialer.rml"); +} +``` + +### Navigation Helpers + +```cpp +// Go back to home screen by clicking back buttons +void GoHome(TestContext& ctx) { + for (int i = 0; i < 5; ++i) { + ctx.hierarchy.Reload(); + auto elements = ctx.hierarchy.FindByClass("app-bar-nav"); + if (!elements.empty() && elements[0].visible) { + int x = elements[0].bounds.centerX(); + int y = elements[0].bounds.centerY(); + ScaleToPhysical(ctx, x, y); + ctx.window.SendClick(x, y); + } + std::this_thread::sleep_for(std::chrono::milliseconds(400)); + } + std::this_thread::sleep_for(std::chrono::milliseconds(800)); +} +``` + +## Registering Tests + +In `main.cpp`: + +```cpp +TestRunner runner; +runner.SetDesignerPath(designerPath); +runner.SetDocumentPath(documentPath); +runner.SetLogPath(logPath); +runner.SetHierarchyPath(hierarchyPath); + +// Register tests +runner.AddTest("Navigate to Dialer", TestNavigateToDialer); +runner.AddTest("Navigate to Messages", TestNavigateToMessages); +runner.AddTest("Navigate to Contacts", TestNavigateToContacts); +runner.AddTest("Navigate to Browser", TestNavigateToBrowser); +runner.AddTest("Navigation Sequence", TestNavigationSequence); + +// Run all tests +auto results = runner.RunAll(); +results.SaveToFile(resultsPath); +``` + +## Test Results Format + +```json +{ + "name": "Mosis Designer UI Tests", + "summary": { + "passed": 5, + "failed": 0, + "skipped": 0, + "errors": 0, + "total": 5, + "duration_ms": 31162.3 + }, + "tests": [ + { + "name": "Navigate to Dialer", + "status": "passed", + "message": "Test passed", + "duration_ms": 4216.88 + }, + { + "name": "Navigate to Browser", + "status": "passed", + "message": "Test passed", + "duration_ms": 4067.34 + } + ] +} +``` + +## Running Tests + +### Command Line + +```bash +# Run with defaults (auto-detects paths) +designer-test.exe + +# Override paths +designer-test.exe --designer /path/to/mosis-designer.exe \ + --document /path/to/home.rml \ + --log /path/to/output.log \ + --hierarchy /path/to/hierarchy.json \ + --results /path/to/results.json +``` + +### Exit Codes + +- `0`: All tests passed +- `1`: One or more tests failed + +## Best Practices + +### Element Identification + +1. **Use IDs** for clickable elements that tests need to find +2. **Use semantic classes** like `app-bar-nav` for navigation buttons +3. **Avoid relying on position** - use hierarchy lookup instead + +### Timing + +1. **Wait after navigation** (1000ms) for animations to complete +2. **Reload hierarchy** before each element lookup +3. **Use retry logic** for file reads (hierarchy may be mid-write) + +### Test Independence + +1. **Start from known state** - call GoHome() at start of each test +2. **Clear logs** before verification +3. **Don't depend on previous test state** + +## Adding Test IDs to RML + +When creating new screens, add IDs to interactive elements: + +```html + +
+ +
+ + +
+ +
+``` + +## Troubleshooting + +### Element Not Found + +- Verify the screen has loaded (check hierarchy `screen` field) +- Check if element has the expected ID/class +- Ensure element is visible (`visible: true`) + +### Click Not Registering + +- Check coordinate scaling (hierarchy vs window size) +- Increase wait time for animations +- Verify window is in foreground + +### Timing Issues + +- Increase delays after GoHome() and navigation +- Add retries for hierarchy reload +- Check for race conditions in file I/O + +## Android Testing + +The Android app supports event injection for automated testing. + +### Event Injection Methods + +The `MainActivity` provides these methods for programmatic testing: + +```kotlin +// Inject touch events with normalized coordinates (0.0-1.0) +MainActivity.instance?.injectTouchDown(x, y) +MainActivity.instance?.injectTouchMove(x, y) +MainActivity.instance?.injectTouchUp(x, y) +MainActivity.instance?.injectClick(x, y) // Down + delay + Up +``` + +### Broadcast-Based Injection + +For external test frameworks or ADB, send broadcasts: + +```bash +# Inject a click at center (0.5, 0.5) +adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \ + --es touch_type "click" \ + --ef x 0.5 \ + --ef y 0.5 + +# Inject touch down +adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \ + --es touch_type "down" \ + --ef x 0.2 \ + --ef y 0.9 + +# Inject touch up +adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \ + --es touch_type "up" \ + --ef x 0.2 \ + --ef y 0.9 +``` + +### UI Element Coordinates + +Dock items are positioned at the bottom of the screen. Approximate normalized positions: + +| Element | X | Y | +|---------|---|---| +| dock-phone | 0.16 | 0.97 | +| dock-messages | 0.39 | 0.97 | +| dock-contacts | 0.61 | 0.97 | +| dock-browser | 0.84 | 0.97 | + +### Instrumentation Tests + +For full integration tests, use Android's instrumentation framework: + +```kotlin +@RunWith(AndroidJUnit4::class) +class NavigationTest { + @get:Rule + val activityRule = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun testNavigateToDialer() { + // Wait for service to connect + Thread.sleep(2000) + + // Click on phone dock icon (normalized coords) + activityRule.scenario.onActivity { activity -> + activity.injectClick(0.16f, 0.97f) + } + + // Wait for navigation + Thread.sleep(1000) + + // Verify navigation occurred (check logs or UI state) + } +} +``` + +## Future Improvements + +- [ ] Action recording (capture user interactions) +- [ ] Screenshot comparison (visual regression testing) +- [ ] Android instrumentation test suite +- [ ] Parallel test execution +- [ ] Test coverage reporting +- [ ] Cross-platform test runner (desktop + Android) diff --git a/designer-test/CMakeLists.txt b/designer-test/CMakeLists.txt new file mode 100644 index 0000000..f4b5943 --- /dev/null +++ b/designer-test/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.22.1) +project(designer-test) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Windows-only project +if(NOT WIN32) + message(FATAL_ERROR "designer-test is Windows-only") +endif() + +# Find nlohmann_json for test result output +find_package(nlohmann_json CONFIG REQUIRED) + +# Main test executable +add_executable(designer-test + src/main.cpp + src/window_controller.cpp + src/test_runner.cpp + src/log_parser.cpp + src/hierarchy_reader.cpp +) + +target_include_directories(designer-test PRIVATE src) + +target_link_libraries(designer-test PRIVATE + nlohmann_json::nlohmann_json +) + +# Link Windows libraries +target_link_libraries(designer-test PRIVATE + user32 + shell32 +) diff --git a/designer-test/src/hierarchy_reader.cpp b/designer-test/src/hierarchy_reader.cpp new file mode 100644 index 0000000..e9bd8a9 --- /dev/null +++ b/designer-test/src/hierarchy_reader.cpp @@ -0,0 +1,207 @@ +// Hierarchy reader implementation +#include "hierarchy_reader.h" +#include +#include +#include +#include + +namespace mosis::test { + +void HierarchyReader::SetHierarchyPath(const std::filesystem::path& path) { + m_path = path; + m_loaded = false; +} + +bool HierarchyReader::Reload() { + if (m_path.empty()) { + return false; + } + + // Retry a few times with delay to handle file write race conditions + for (int attempt = 0; attempt < 5; ++attempt) { + if (attempt > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + std::ifstream file(m_path); + if (!file.is_open()) { + continue; // File might not exist yet, retry + } + + try { + m_json = nlohmann::json::parse(file); + m_loaded = true; + return true; + } catch (const nlohmann::json::parse_error& e) { + // File might be partially written, retry + if (attempt == 4) { + std::cerr << "Failed to parse hierarchy JSON after 5 attempts: " << e.what() << std::endl; + } + } + } + + std::cerr << "Failed to load hierarchy file: " << m_path << std::endl; + m_loaded = false; + return false; +} + +std::string HierarchyReader::GetScreen() const { + if (!m_loaded) return ""; + return m_json.value("screen", ""); +} + +std::string HierarchyReader::GetScreenName() const { + std::string screen = GetScreen(); + // Extract just the filename from the path + size_t lastSlash = screen.find_last_of("/\\"); + if (lastSlash != std::string::npos) { + screen = screen.substr(lastSlash + 1); + } + return screen; +} + +std::vector HierarchyReader::GetAllElements() const { + std::vector results; + if (!m_loaded || !m_json.contains("elements")) { + return results; + } + + SearchElements(m_json["elements"], [](const ElementInfo&) { + return true; // Match all elements + }, results); + + return results; +} + +int HierarchyReader::GetWidth() const { + if (!m_loaded) return 0; + if (m_json.contains("resolution") && m_json["resolution"].contains("width")) { + return m_json["resolution"]["width"].get(); + } + return 0; +} + +int HierarchyReader::GetHeight() const { + if (!m_loaded) return 0; + if (m_json.contains("resolution") && m_json["resolution"].contains("height")) { + return m_json["resolution"]["height"].get(); + } + return 0; +} + +ElementInfo HierarchyReader::ParseElement(const nlohmann::json& j) const { + ElementInfo info; + + info.tag = j.value("tag", ""); + info.id = j.value("id", ""); + info.visible = j.value("visible", false); + + // Parse classes array + if (j.contains("classes") && j["classes"].is_array()) { + for (const auto& cls : j["classes"]) { + if (cls.is_string()) { + info.classes.push_back(cls.get()); + } + } + } + + // Parse bounds + if (j.contains("bounds") && j["bounds"].is_object()) { + const auto& b = j["bounds"]; + info.bounds.x = b.value("x", 0.0f); + info.bounds.y = b.value("y", 0.0f); + info.bounds.width = b.value("width", 0.0f); + info.bounds.height = b.value("height", 0.0f); + } + + // Parse text + if (j.contains("text") && !j["text"].is_null()) { + info.text = j["text"].get(); + } + + return info; +} + +void HierarchyReader::SearchElements( + const nlohmann::json& j, + std::function predicate, + std::vector& results) const +{ + if (!j.is_object()) return; + + // Parse current element + ElementInfo info = ParseElement(j); + if (predicate(info)) { + results.push_back(info); + } + + // Recurse into children + if (j.contains("children") && j["children"].is_array()) { + for (const auto& child : j["children"]) { + SearchElements(child, predicate, results); + } + } +} + +std::optional HierarchyReader::FindById(const std::string& id) const { + if (!m_loaded || !m_json.contains("elements")) { + return std::nullopt; + } + + std::vector results; + SearchElements(m_json["elements"], [&id](const ElementInfo& e) { + return e.id == id; + }, results); + + if (!results.empty()) { + return results[0]; + } + return std::nullopt; +} + +std::vector HierarchyReader::FindByClass(const std::string& className) const { + std::vector results; + if (!m_loaded || !m_json.contains("elements")) { + return results; + } + + SearchElements(m_json["elements"], [&className](const ElementInfo& e) { + for (const auto& cls : e.classes) { + if (cls == className) return true; + } + return false; + }, results); + + return results; +} + +std::vector HierarchyReader::FindByTag(const std::string& tagName) const { + std::vector results; + if (!m_loaded || !m_json.contains("elements")) { + return results; + } + + SearchElements(m_json["elements"], [&tagName](const ElementInfo& e) { + return e.tag == tagName; + }, results); + + return results; +} + +std::optional HierarchyReader::FindFirst( + std::function predicate) const +{ + if (!m_loaded || !m_json.contains("elements")) { + return std::nullopt; + } + + std::vector results; + SearchElements(m_json["elements"], predicate, results); + + if (!results.empty()) { + return results[0]; + } + return std::nullopt; +} + +} // namespace mosis::test diff --git a/designer-test/src/hierarchy_reader.h b/designer-test/src/hierarchy_reader.h new file mode 100644 index 0000000..babf5dc --- /dev/null +++ b/designer-test/src/hierarchy_reader.h @@ -0,0 +1,91 @@ +// Hierarchy reader for parsing UI dump JSON files +#pragma once + +#include +#include +#include +#include +#include + +namespace mosis::test { + +// Element bounds from hierarchy +struct ElementBounds { + float x = 0; + float y = 0; + float width = 0; + float height = 0; + + // Get center point + int centerX() const { return static_cast(x + width / 2); } + int centerY() const { return static_cast(y + height / 2); } +}; + +// Element info from hierarchy +struct ElementInfo { + std::string tag; + std::string id; + std::vector classes; + ElementBounds bounds; + bool visible = false; + std::string text; +}; + +// Reads and parses UI hierarchy dump files +class HierarchyReader { +public: + HierarchyReader() = default; + + // Set the path to the hierarchy file + void SetHierarchyPath(const std::filesystem::path& path); + + // Reload the hierarchy from file + bool Reload(); + + // Check if hierarchy is loaded + bool IsLoaded() const { return m_loaded; } + + // Get screen URL from hierarchy + std::string GetScreen() const; + + // Get screen name (filename only) from hierarchy + std::string GetScreenName() const; + + // Get resolution from hierarchy + int GetWidth() const; + int GetHeight() const; + + // Get all elements (for debugging) + std::vector GetAllElements() const; + + // Find element by ID + std::optional FindById(const std::string& id) const; + + // Find elements by class name + std::vector FindByClass(const std::string& className) const; + + // Find elements by tag name + std::vector FindByTag(const std::string& tagName) const; + + // Find first visible element matching a predicate + std::optional FindFirst( + std::function predicate) const; + + // Get raw JSON (for debugging) + const nlohmann::json& GetJson() const { return m_json; } + +private: + // Parse element info from JSON node + ElementInfo ParseElement(const nlohmann::json& j) const; + + // Recursively search for elements + void SearchElements(const nlohmann::json& j, + std::function predicate, + std::vector& results) const; + + std::filesystem::path m_path; + nlohmann::json m_json; + bool m_loaded = false; +}; + +} // namespace mosis::test diff --git a/designer-test/src/log_parser.cpp b/designer-test/src/log_parser.cpp new file mode 100644 index 0000000..29a21e6 --- /dev/null +++ b/designer-test/src/log_parser.cpp @@ -0,0 +1,103 @@ +// Log parser implementation +#include "log_parser.h" +#include +#include +#include +#include + +namespace mosis::test { + +LogParser::LogParser(const std::filesystem::path& logPath) + : m_logPath(logPath) { +} + +void LogParser::SetLogPath(const std::filesystem::path& path) { + m_logPath = path; + Clear(); +} + +bool LogParser::Reload() { + if (m_logPath.empty() || !std::filesystem::exists(m_logPath)) { + return false; + } + + std::ifstream file(m_logPath); + if (!file.is_open()) { + return false; + } + + // Read all lines + m_entries.clear(); + std::string line; + while (std::getline(file, line)) { + if (!line.empty()) { + LogEntry entry; + entry.message = line; + m_entries.push_back(entry); + } + } + + return true; +} + +void LogParser::Clear() { + m_entries.clear(); + m_lastReadPosition = 0; +} + +std::optional LogParser::FindEntry(const std::string& pattern) const { + // Search from newest to oldest + for (auto it = m_entries.rbegin(); it != m_entries.rend(); ++it) { + if (it->message.find(pattern) != std::string::npos) { + return *it; + } + } + return std::nullopt; +} + +bool LogParser::Contains(const std::string& pattern) const { + return FindEntry(pattern).has_value(); +} + +std::vector LogParser::FindAllEntries(const std::string& pattern) const { + std::vector results; + for (const auto& entry : m_entries) { + if (entry.message.find(pattern) != std::string::npos) { + results.push_back(entry); + } + } + return results; +} + +bool LogParser::WaitForEntry(const std::string& pattern, int timeoutMs) { + auto startTime = std::chrono::steady_clock::now(); + + while (true) { + Reload(); + if (Contains(pattern)) { + return true; + } + + auto elapsed = std::chrono::steady_clock::now() - startTime; + if (std::chrono::duration_cast(elapsed).count() >= timeoutMs) { + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +std::vector LogParser::GetLastEntries(size_t count) const { + if (count >= m_entries.size()) { + return m_entries; + } + + std::vector result; + result.reserve(count); + for (size_t i = m_entries.size() - count; i < m_entries.size(); ++i) { + result.push_back(m_entries[i]); + } + return result; +} + +} // namespace mosis::test diff --git a/designer-test/src/log_parser.h b/designer-test/src/log_parser.h new file mode 100644 index 0000000..b43d71c --- /dev/null +++ b/designer-test/src/log_parser.h @@ -0,0 +1,55 @@ +// Log file parser for verifying navigation +#pragma once + +#include +#include +#include +#include + +namespace mosis::test { + +// Represents a parsed log entry +struct LogEntry { + std::string message; + std::string timestamp; // If available +}; + +class LogParser { +public: + LogParser() = default; + explicit LogParser(const std::filesystem::path& logPath); + + // Set the log file path + void SetLogPath(const std::filesystem::path& path); + + // Reload log file from disk + bool Reload(); + + // Clear cached entries (for fresh start) + void Clear(); + + // Get all entries + const std::vector& GetEntries() const { return m_entries; } + + // Search for a pattern in log entries (returns first match or nullopt) + std::optional FindEntry(const std::string& pattern) const; + + // Check if pattern exists in log + bool Contains(const std::string& pattern) const; + + // Get entries matching a pattern + std::vector FindAllEntries(const std::string& pattern) const; + + // Wait for a pattern to appear in the log (reloads periodically) + bool WaitForEntry(const std::string& pattern, int timeoutMs = 5000); + + // Get last N entries + std::vector GetLastEntries(size_t count) const; + +private: + std::filesystem::path m_logPath; + std::vector m_entries; + size_t m_lastReadPosition = 0; +}; + +} // namespace mosis::test diff --git a/designer-test/src/main.cpp b/designer-test/src/main.cpp new file mode 100644 index 0000000..35fc854 --- /dev/null +++ b/designer-test/src/main.cpp @@ -0,0 +1,364 @@ +// Mosis Designer Test - Automated UI Testing +// Sends input events directly to the designer window using element coordinates from hierarchy dump + +#include "test_runner.h" +#include +#include +#include +#include + +using namespace mosis::test; + +// Helper: Scale coordinates from hierarchy (logical) to window (physical) space +void ScaleToPhysical(TestContext& ctx, int& x, int& y) { + // The hierarchy reports coordinates in RmlUi's logical space + // which may be DPI-scaled. We need to convert to physical window coordinates. + int hierarchyWidth = ctx.hierarchy.GetWidth(); + int hierarchyHeight = ctx.hierarchy.GetHeight(); + int windowWidth = ctx.window.GetInfo().clientWidth; + int windowHeight = ctx.window.GetInfo().clientHeight; + + if (hierarchyWidth > 0 && hierarchyHeight > 0) { + x = static_cast(x * static_cast(windowWidth) / hierarchyWidth); + y = static_cast(y * static_cast(windowHeight) / hierarchyHeight); + } +} + +// Helper: Click on an element found by ID in the hierarchy +bool ClickById(TestContext& ctx, const std::string& id) { + ctx.hierarchy.Reload(); + + // Debug: Print hierarchy info + std::cout << " Hierarchy: " << ctx.hierarchy.GetWidth() << "x" << ctx.hierarchy.GetHeight() + << ", screen: " << ctx.hierarchy.GetScreenName() << std::endl; + + auto element = ctx.hierarchy.FindById(id); + if (!element) { + std::cerr << " Element not found: #" << id << std::endl; + // List available IDs for debugging + auto allElements = ctx.hierarchy.GetAllElements(); + std::cerr << " Available IDs: "; + for (const auto& e : allElements) { + if (!e.id.empty()) { + std::cerr << "#" << e.id << " "; + } + } + std::cerr << std::endl; + return false; + } + if (!element->visible) { + std::cerr << " Element not visible: #" << id << std::endl; + return false; + } + + int x = element->bounds.centerX(); + int y = element->bounds.centerY(); + int logicalX = x, logicalY = y; + ScaleToPhysical(ctx, x, y); + + std::cout << " Clicking #" << id << " at logical(" << logicalX << "," << logicalY + << ") -> physical(" << x << "," << y << ")" << std::endl; + ctx.window.SendClick(x, y); + return true; +} + +// Helper: Click on an element found by class name (first visible match) +bool ClickByClass(TestContext& ctx, const std::string& className) { + ctx.hierarchy.Reload(); + auto elements = ctx.hierarchy.FindByClass(className); + for (const auto& element : elements) { + if (element.visible && element.bounds.width > 0 && element.bounds.height > 0) { + int x = element.bounds.centerX(); + int y = element.bounds.centerY(); + ScaleToPhysical(ctx, x, y); + std::cout << " Clicking ." << className << " at physical(" << x << "," << y << ")" << std::endl; + ctx.window.SendClick(x, y); + return true; + } + } + std::cerr << " No visible element found with class: " << className << std::endl; + return false; +} + +// Helper: Click on an element found by class name at a specific index +bool ClickByClassIndex(TestContext& ctx, const std::string& className, int index) { + ctx.hierarchy.Reload(); + auto elements = ctx.hierarchy.FindByClass(className); + int visibleIndex = 0; + for (const auto& element : elements) { + if (element.visible && element.bounds.width > 0 && element.bounds.height > 0) { + if (visibleIndex == index) { + int x = element.bounds.centerX(); + int y = element.bounds.centerY(); + ScaleToPhysical(ctx, x, y); + std::cout << " Clicking ." << className << "[" << index << "] at physical(" + << x << "," << y << ")" << std::endl; + ctx.window.SendClick(x, y); + return true; + } + visibleIndex++; + } + } + std::cerr << " Element ." << className << "[" << index << "] not found (only " + << visibleIndex << " visible)" << std::endl; + return false; +} + +// Helper: Go back to home screen by clicking back button multiple times +void GoHome(TestContext& ctx) { + // Click back button (app-bar-nav) up to 5 times to ensure we're at home + for (int i = 0; i < 5; ++i) { + ctx.hierarchy.Reload(); + // Look for the back button by class + auto elements = ctx.hierarchy.FindByClass("app-bar-nav"); + if (!elements.empty()) { + auto& btn = elements[0]; + if (btn.visible && btn.bounds.width > 0) { + int x = btn.bounds.centerX(); + int y = btn.bounds.centerY(); + ScaleToPhysical(ctx, x, y); + ctx.window.SendClick(x, y); + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(400)); + } + // Extra wait for animations to fully complete and hierarchy to update + std::this_thread::sleep_for(std::chrono::milliseconds(800)); +} + +// Test: Navigate to dialer by clicking Phone dock icon +bool TestNavigateToDialer(TestContext& ctx) { + // Ensure we start from home screen + GoHome(ctx); + ctx.log.Clear(); + + // Click on Phone dock icon (dock-phone id) + if (!ClickById(ctx, "dock-phone")) { + return false; + } + + // Wait for navigation + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Reload log and check for navigation + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/dialer/dialer.rml") || + ctx.log.Contains("apps/dialer"); +} + +// Test: Navigate to messages +bool TestNavigateToMessages(TestContext& ctx) { + // Ensure we start from home screen + GoHome(ctx); + ctx.log.Clear(); + + // Click on Messages dock icon + if (!ClickById(ctx, "dock-messages")) { + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/messages/messages.rml"); +} + +// Test: Navigate to contacts +bool TestNavigateToContacts(TestContext& ctx) { + // Ensure we start from home screen + GoHome(ctx); + ctx.log.Clear(); + + // Click on Contacts dock icon + if (!ClickById(ctx, "dock-contacts")) { + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/contacts/contacts.rml"); +} + +// Test: Navigate to browser +bool TestNavigateToBrowser(TestContext& ctx) { + // Ensure we start from home screen + GoHome(ctx); + ctx.log.Clear(); + + // Click on Browser dock icon + if (!ClickById(ctx, "dock-browser")) { + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/browser/browser.rml"); +} + +// Test: Navigate to settings (in app grid, not dock) +bool TestNavigateToSettings(TestContext& ctx) { + // Ensure we start from home screen + GoHome(ctx); + ctx.log.Clear(); + + // Settings is in the app grid - find by ID + if (!ClickById(ctx, "app-settings")) { + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + ctx.log.Reload(); + return ctx.log.Contains("Loaded screen: apps/settings/settings.rml"); +} + +// Test: Multiple navigation sequence from home +bool TestNavigationSequence(TestContext& ctx) { + // Ensure we start from home screen + GoHome(ctx); + ctx.log.Clear(); + + // Go to dialer from home + if (!ClickById(ctx, "dock-phone")) { + std::cerr << " Failed to click dock-phone" << std::endl; + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Go back to home + GoHome(ctx); + + // Go to messages from home + if (!ClickById(ctx, "dock-messages")) { + std::cerr << " Failed to click dock-messages" << std::endl; + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + // Go back to home + GoHome(ctx); + + // Go to contacts from home + if (!ClickById(ctx, "dock-contacts")) { + std::cerr << " Failed to click dock-contacts" << std::endl; + return false; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + + ctx.log.Reload(); + + // Verify all navigations happened + bool hasDialer = ctx.log.Contains("apps/dialer"); + bool hasMessages = ctx.log.Contains("apps/messages"); + bool hasContacts = ctx.log.Contains("apps/contacts"); + + std::cout << " Dialer: " << (hasDialer ? "yes" : "no") << std::endl; + std::cout << " Messages: " << (hasMessages ? "yes" : "no") << std::endl; + std::cout << " Contacts: " << (hasContacts ? "yes" : "no") << std::endl; + + return hasDialer && hasMessages && hasContacts; +} + +int main(int argc, char* argv[]) { + std::cout << "Mosis Designer Test v0.2.0" << std::endl; + std::cout << "==========================" << std::endl; + std::cout << "Using UI hierarchy for element coordinates" << std::endl; + + // Determine paths + // exe is at: designer-test/build/Debug/designer-test.exe + // project root is: MosisService/ + std::filesystem::path exePath = std::filesystem::absolute(argv[0]).parent_path(); + std::filesystem::path projectRoot = exePath.parent_path().parent_path().parent_path(); // Up 3 levels + + std::filesystem::path designerPath = projectRoot / "designer" / "build" / "Debug" / "mosis-designer.exe"; + std::filesystem::path documentPath = projectRoot / "src" / "main" / "assets" / "apps" / "home" / "home.rml"; + std::filesystem::path logPath = exePath / "designer_test.log"; + std::filesystem::path hierarchyPath = exePath / "ui_hierarchy.json"; + std::filesystem::path resultsPath = exePath / "test_results.json"; + + // Allow command-line overrides + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--designer" && i + 1 < argc) { + designerPath = argv[++i]; + } else if (arg == "--document" && i + 1 < argc) { + documentPath = argv[++i]; + } else if (arg == "--log" && i + 1 < argc) { + logPath = argv[++i]; + } else if (arg == "--hierarchy" && i + 1 < argc) { + hierarchyPath = argv[++i]; + } else if (arg == "--results" && i + 1 < argc) { + resultsPath = argv[++i]; + } else if (arg == "--help") { + std::cout << "Usage: designer-test [options]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " --designer PATH Path to mosis-designer.exe" << std::endl; + std::cout << " --document PATH Path to initial RML document" << std::endl; + std::cout << " --log PATH Path for log file" << std::endl; + std::cout << " --hierarchy PATH Path for UI hierarchy dump file" << std::endl; + std::cout << " --results PATH Path for JSON test results" << std::endl; + std::cout << " --help Show this help" << std::endl; + return 0; + } + } + + std::cout << "Designer: " << designerPath << std::endl; + std::cout << "Document: " << documentPath << std::endl; + std::cout << "Log file: " << logPath << std::endl; + std::cout << "Hierarchy: " << hierarchyPath << std::endl; + + // Check if designer exists + if (!std::filesystem::exists(designerPath)) { + std::cerr << "ERROR: Designer not found at: " << designerPath << std::endl; + std::cerr << "Build the designer first: cd designer && cmake --build build --config Debug" << std::endl; + return 1; + } + + if (!std::filesystem::exists(documentPath)) { + std::cerr << "ERROR: Document not found at: " << documentPath << std::endl; + return 1; + } + + // Create test runner + TestRunner runner; + runner.SetDesignerPath(designerPath); + runner.SetDocumentPath(documentPath); + runner.SetLogPath(logPath); + runner.SetHierarchyPath(hierarchyPath); + runner.SetStartupTimeoutMs(15000); + runner.SetActionDelayMs(500); + + // Register tests + runner.AddTest("Navigate to Dialer", TestNavigateToDialer); + runner.AddTest("Navigate to Messages", TestNavigateToMessages); + runner.AddTest("Navigate to Contacts", TestNavigateToContacts); + runner.AddTest("Navigate to Browser", TestNavigateToBrowser); + runner.AddTest("Navigation Sequence", TestNavigationSequence); + + // Start designer + std::cout << "\nStarting designer..." << std::endl; + if (!runner.StartDesigner()) { + std::cerr << "Failed to start designer" << std::endl; + return 1; + } + + std::cout << "Designer started successfully" << std::endl; + std::cout << "Window info:" << std::endl; + const auto& info = runner.GetWindow().GetInfo(); + std::cout << " Scale: " << info.scaleX << " x " << info.scaleY << std::endl; + + // Run tests + auto results = runner.RunAll(); + + // Save results + results.SaveToFile(resultsPath); + + // Stop designer + std::cout << "Stopping designer..." << std::endl; + runner.StopDesigner(); + + // Return exit code based on results + return (results.failed + results.errors) > 0 ? 1 : 0; +} diff --git a/designer-test/src/test_runner.cpp b/designer-test/src/test_runner.cpp new file mode 100644 index 0000000..cbf12df --- /dev/null +++ b/designer-test/src/test_runner.cpp @@ -0,0 +1,277 @@ +// Test runner implementation +#include "test_runner.h" +#include +#include +#include +#include + +namespace mosis::test { + +nlohmann::json TestResult::ToJson() const { + nlohmann::json j; + j["name"] = name; + j["status"] = status == TestStatus::Passed ? "passed" : + status == TestStatus::Failed ? "failed" : + status == TestStatus::Skipped ? "skipped" : "error"; + j["message"] = message; + j["duration_ms"] = durationMs; + return j; +} + +nlohmann::json TestSuiteResult::ToJson() const { + nlohmann::json j; + j["name"] = name; + j["summary"] = { + {"passed", passed}, + {"failed", failed}, + {"skipped", skipped}, + {"errors", errors}, + {"total", static_cast(tests.size())}, + {"duration_ms", totalDurationMs} + }; + + nlohmann::json testsJson = nlohmann::json::array(); + for (const auto& test : tests) { + testsJson.push_back(test.ToJson()); + } + j["tests"] = testsJson; + + return j; +} + +void TestSuiteResult::SaveToFile(const std::filesystem::path& path) const { + std::ofstream file(path); + if (file.is_open()) { + file << ToJson().dump(2); + std::cout << "Test results saved to: " << path << std::endl; + } else { + std::cerr << "Failed to save test results to: " << path << std::endl; + } +} + +TestRunner::TestRunner() = default; + +TestRunner::~TestRunner() { + StopDesigner(); +} + +void TestRunner::SetDesignerPath(const std::filesystem::path& path) { + m_designerPath = path; +} + +void TestRunner::SetDocumentPath(const std::filesystem::path& path) { + m_documentPath = path; +} + +void TestRunner::SetLogPath(const std::filesystem::path& path) { + m_logPath = path; + m_log.SetLogPath(path); +} + +void TestRunner::SetHierarchyPath(const std::filesystem::path& path) { + m_hierarchyPath = path; + m_hierarchy.SetHierarchyPath(path); +} + +void TestRunner::AddTest(const std::string& name, TestFunc func) { + m_tests.push_back({name, std::move(func)}); +} + +bool TestRunner::StartDesigner() { + if (m_designerPath.empty() || m_documentPath.empty()) { + std::cerr << "Designer or document path not set" << std::endl; + return false; + } + + // Delete old log file + if (!m_logPath.empty() && std::filesystem::exists(m_logPath)) { + std::filesystem::remove(m_logPath); + } + + // Delete old hierarchy file + if (!m_hierarchyPath.empty() && std::filesystem::exists(m_hierarchyPath)) { + std::filesystem::remove(m_hierarchyPath); + } + + // Build command line + std::string cmdLine = "\"" + m_designerPath.string() + "\" \"" + m_documentPath.string() + "\""; + if (!m_logPath.empty()) { + cmdLine += " --log \"" + m_logPath.string() + "\""; + } + if (!m_hierarchyPath.empty()) { + cmdLine += " --hierarchy \"" + m_hierarchyPath.string() + "\""; + } + + std::cout << "Starting designer: " << cmdLine << std::endl; + + // Create process + STARTUPINFOA si = {}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi = {}; + + // Need a mutable buffer for CreateProcess + std::vector cmdLineBuffer(cmdLine.begin(), cmdLine.end()); + cmdLineBuffer.push_back('\0'); + + if (!CreateProcessA( + nullptr, + cmdLineBuffer.data(), + nullptr, + nullptr, + FALSE, + 0, + nullptr, + nullptr, + &si, + &pi)) { + std::cerr << "Failed to start designer process: " << GetLastError() << std::endl; + return false; + } + + m_designerProcess = pi.hProcess; + CloseHandle(pi.hThread); + + std::cout << "Designer process started with PID: " << pi.dwProcessId << std::endl; + + // Wait for window to appear + if (!m_window.WaitForWindow("Mosis Designer", m_startupTimeoutMs)) { + std::cerr << "Designer window did not appear" << std::endl; + StopDesigner(); + return false; + } + + // Resize window to full phone resolution to ensure consistent UI layout + m_window.ResizeToPhone(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Wait for document to load + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + + // Verify log file was created + if (!m_logPath.empty()) { + m_log.Reload(); + if (!m_log.Contains("Document loaded successfully")) { + std::cerr << "Warning: Document load confirmation not found in log" << std::endl; + } + } + + return true; +} + +void TestRunner::StopDesigner() { + if (m_designerProcess) { + // Try graceful close first + m_window.Close(); + + // Wait briefly for graceful exit + if (WaitForSingleObject(m_designerProcess, 2000) == WAIT_TIMEOUT) { + // Force terminate + TerminateProcess(m_designerProcess, 1); + } + + CloseHandle(m_designerProcess); + m_designerProcess = nullptr; + } +} + +TestResult TestRunner::ExecuteTest(const TestCase& test) { + TestResult result; + result.name = test.name; + + auto startTime = std::chrono::steady_clock::now(); + + try { + // Reload hierarchy before each test + m_hierarchy.Reload(); + + TestContext ctx{m_window, m_log, m_hierarchy}; + + // Clear log before test for cleaner verification + m_log.Clear(); + + bool passed = test.func(ctx); + + result.status = passed ? TestStatus::Passed : TestStatus::Failed; + result.message = passed ? "Test passed" : "Test assertions failed"; + + } catch (const std::exception& e) { + result.status = TestStatus::Error; + result.message = std::string("Exception: ") + e.what(); + } + + auto endTime = std::chrono::steady_clock::now(); + result.durationMs = std::chrono::duration(endTime - startTime).count(); + + return result; +} + +TestResult TestRunner::RunTest(const std::string& name) { + for (const auto& test : m_tests) { + if (test.name == name) { + return ExecuteTest(test); + } + } + + TestResult result; + result.name = name; + result.status = TestStatus::Error; + result.message = "Test not found"; + return result; +} + +TestSuiteResult TestRunner::RunAll() { + TestSuiteResult suite; + suite.name = "Mosis Designer UI Tests"; + + auto suiteStart = std::chrono::steady_clock::now(); + + std::cout << "\n========================================" << std::endl; + std::cout << "Running " << m_tests.size() << " tests" << std::endl; + std::cout << "========================================\n" << std::endl; + + for (const auto& test : m_tests) { + std::cout << "Running: " << test.name << "..." << std::endl; + + TestResult result = ExecuteTest(test); + suite.tests.push_back(result); + + switch (result.status) { + case TestStatus::Passed: + suite.passed++; + std::cout << " PASSED (" << result.durationMs << "ms)" << std::endl; + break; + case TestStatus::Failed: + suite.failed++; + std::cout << " FAILED: " << result.message << std::endl; + break; + case TestStatus::Skipped: + suite.skipped++; + std::cout << " SKIPPED: " << result.message << std::endl; + break; + case TestStatus::Error: + suite.errors++; + std::cout << " ERROR: " << result.message << std::endl; + break; + } + + // Delay between tests + std::this_thread::sleep_for(std::chrono::milliseconds(m_actionDelayMs)); + } + + auto suiteEnd = std::chrono::steady_clock::now(); + suite.totalDurationMs = std::chrono::duration(suiteEnd - suiteStart).count(); + + std::cout << "\n========================================" << std::endl; + std::cout << "Test Results:" << std::endl; + std::cout << " Passed: " << suite.passed << std::endl; + std::cout << " Failed: " << suite.failed << std::endl; + std::cout << " Skipped: " << suite.skipped << std::endl; + std::cout << " Errors: " << suite.errors << std::endl; + std::cout << " Total: " << suite.tests.size() << std::endl; + std::cout << " Duration: " << suite.totalDurationMs << "ms" << std::endl; + std::cout << "========================================\n" << std::endl; + + return suite; +} + +} // namespace mosis::test diff --git a/designer-test/src/test_runner.h b/designer-test/src/test_runner.h new file mode 100644 index 0000000..0b6787b --- /dev/null +++ b/designer-test/src/test_runner.h @@ -0,0 +1,149 @@ +// Test runner for designer UI tests +#pragma once + +#include "window_controller.h" +#include "log_parser.h" +#include "hierarchy_reader.h" +#include +#include +#include +#include +#include + +namespace mosis::test { + +// Test result status +enum class TestStatus { + Passed, + Failed, + Skipped, + Error +}; + +// Individual test result +struct TestResult { + std::string name; + TestStatus status; + std::string message; + double durationMs; + + nlohmann::json ToJson() const; +}; + +// Test suite result +struct TestSuiteResult { + std::string name; + std::vector tests; + int passed = 0; + int failed = 0; + int skipped = 0; + int errors = 0; + double totalDurationMs = 0; + + nlohmann::json ToJson() const; + void SaveToFile(const std::filesystem::path& path) const; +}; + +// Test context passed to each test +struct TestContext { + WindowController& window; + LogParser& log; + HierarchyReader& hierarchy; +}; + +// Test function signature +using TestFunc = std::function; + +// Individual test case +struct TestCase { + std::string name; + TestFunc func; +}; + +// App icon positions for tests +struct AppPosition { + int x; + int y; +}; + +// Predefined positions on phone screen (540x960) +namespace AppPositions { + // Navigation buttons (in app-bar, 56px high starting after 24px status bar) + // Status bar: y=0-24, App bar: y=24-80, back button at left with 16px padding + // Use y=40 which is in app-bar on screens with app-bar, but in padding on home + constexpr AppPosition BackButton = {40, 40}; // In app-bar zone, safe on home screen + + // App grid positions - Row 1 (starting around y=100 after app-bar) + constexpr AppPosition Phone = {67, 120}; + constexpr AppPosition Messages = {202, 120}; + constexpr AppPosition Contacts = {337, 120}; + constexpr AppPosition Browser = {472, 120}; + // Row 2 + constexpr AppPosition Gallery = {67, 220}; + constexpr AppPosition Camera = {202, 220}; + constexpr AppPosition Settings = {337, 220}; + constexpr AppPosition Music = {472, 220}; + + // Dock positions - 80px tall at bottom, items centered at y = height - 40 + // 4 items with space-around in 540px - 64px padding = 476px content + // Each item 56px, total 224px, remaining 252px = ~63px per item spacing + // Item centers: ~92, ~211, ~330, ~449 + // Note: Using 920 for Y (phone space), will be scaled by window size + // Adjusted first item X slightly right due to hit testing issues + constexpr AppPosition DockPhone = {100, 920}; + constexpr AppPosition DockMessages = {211, 920}; + constexpr AppPosition DockContacts = {330, 920}; + constexpr AppPosition DockBrowser = {449, 920}; +} + +class TestRunner { +public: + TestRunner(); + ~TestRunner(); + + // Configuration + void SetDesignerPath(const std::filesystem::path& path); + void SetDocumentPath(const std::filesystem::path& path); + void SetLogPath(const std::filesystem::path& path); + void SetHierarchyPath(const std::filesystem::path& path); + void SetStartupTimeoutMs(int ms) { m_startupTimeoutMs = ms; } + void SetActionDelayMs(int ms) { m_actionDelayMs = ms; } + + // Add test cases + void AddTest(const std::string& name, TestFunc func); + + // Run all tests + TestSuiteResult RunAll(); + + // Run a specific test by name + TestResult RunTest(const std::string& name); + + // Start/stop designer + bool StartDesigner(); + void StopDesigner(); + + // Get components for manual testing + WindowController& GetWindow() { return m_window; } + LogParser& GetLog() { return m_log; } + HierarchyReader& GetHierarchy() { return m_hierarchy; } + +private: + TestResult ExecuteTest(const TestCase& test); + + std::filesystem::path m_designerPath; + std::filesystem::path m_documentPath; + std::filesystem::path m_logPath; + std::filesystem::path m_hierarchyPath; + + int m_startupTimeoutMs = 15000; + int m_actionDelayMs = 500; + + std::vector m_tests; + WindowController m_window; + LogParser m_log; + HierarchyReader m_hierarchy; + + HANDLE m_designerProcess = nullptr; +}; + +} // namespace mosis::test diff --git a/designer-test/src/window_controller.cpp b/designer-test/src/window_controller.cpp new file mode 100644 index 0000000..5bdb301 --- /dev/null +++ b/designer-test/src/window_controller.cpp @@ -0,0 +1,265 @@ +// Window controller implementation +#include "window_controller.h" +#include +#include +#include + +namespace mosis::test { + +bool WindowController::FindWindow(const std::string& title) { + m_hwnd = ::FindWindowA(nullptr, title.c_str()); + if (!m_hwnd) { + return false; + } + + m_info.hwnd = m_hwnd; + + // Get window rect + ::GetWindowRect(m_hwnd, &m_info.windowRect); + + // Get client rect (relative to window) + RECT clientRect; + ::GetClientRect(m_hwnd, &clientRect); + m_info.clientRect = clientRect; + + // Convert client area origin to screen coordinates + POINT clientOrigin = {0, 0}; + ::ClientToScreen(m_hwnd, &clientOrigin); + m_info.clientX = clientOrigin.x; + m_info.clientY = clientOrigin.y; + + // Calculate client size and DPI scale + int clientWidth = clientRect.right - clientRect.left; + int clientHeight = clientRect.bottom - clientRect.top; + m_info.clientWidth = clientWidth; + m_info.clientHeight = clientHeight; + + m_info.scaleX = static_cast(clientWidth) / PHONE_WIDTH; + m_info.scaleY = static_cast(clientHeight) / PHONE_HEIGHT; + + std::cout << "Found window: " << title << std::endl; + std::cout << " Window rect: " << m_info.windowRect.left << "," << m_info.windowRect.top + << " - " << m_info.windowRect.right << "," << m_info.windowRect.bottom << std::endl; + std::cout << " Client origin: " << m_info.clientX << "," << m_info.clientY << std::endl; + std::cout << " Client size: " << clientWidth << "x" << clientHeight << std::endl; + std::cout << " DPI scale: " << m_info.scaleX << " x " << m_info.scaleY << std::endl; + + return true; +} + +bool WindowController::WaitForWindow(const std::string& title, int timeoutMs) { + auto startTime = std::chrono::steady_clock::now(); + + while (true) { + if (FindWindow(title)) { + return true; + } + + auto elapsed = std::chrono::steady_clock::now() - startTime; + if (std::chrono::duration_cast(elapsed).count() >= timeoutMs) { + std::cerr << "Timeout waiting for window: " << title << std::endl; + return false; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +LPARAM WindowController::PhoneToClientLParam(int phoneX, int phoneY) { + // Scale phone coordinates to client coordinates + int clientX = static_cast(phoneX * m_info.scaleX); + int clientY = static_cast(phoneY * m_info.scaleY); + + // Clamp to client area bounds + clientX = std::max(0, std::min(clientX, static_cast(PHONE_WIDTH * m_info.scaleX) - 1)); + clientY = std::max(0, std::min(clientY, static_cast(PHONE_HEIGHT * m_info.scaleY) - 1)); + + return MAKELPARAM(clientX, clientY); +} + +bool WindowController::SendMouseDown(int phoneX, int phoneY) { + if (!m_hwnd) return false; + + // Convert to screen coordinates for SendInput + int clientX = static_cast(phoneX * m_info.scaleX); + int clientY = static_cast(phoneY * m_info.scaleY); + int screenX = m_info.clientX + clientX; + int screenY = m_info.clientY + clientY; + + // Use SendInput for proper GLFW compatibility + // First move the cursor to the position + SetCursorPos(screenX, screenY); + + // Send mouse down via SendInput + INPUT input = {}; + input.type = INPUT_MOUSE; + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + SendInput(1, &input, sizeof(INPUT)); + + std::cout << "MouseDown at phone(" << phoneX << "," << phoneY << ") -> screen(" + << screenX << "," << screenY << ")" << std::endl; + + return true; +} + +bool WindowController::SendMouseUp(int phoneX, int phoneY) { + if (!m_hwnd) return false; + + // Send mouse up via SendInput + INPUT input = {}; + input.type = INPUT_MOUSE; + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + SendInput(1, &input, sizeof(INPUT)); + + std::cout << "MouseUp at phone(" << phoneX << "," << phoneY << ")" << std::endl; + + return true; +} + +bool WindowController::SendMouseMove(int phoneX, int phoneY) { + if (!m_hwnd) return false; + + int clientX = static_cast(phoneX * m_info.scaleX); + int clientY = static_cast(phoneY * m_info.scaleY); + int screenX = m_info.clientX + clientX; + int screenY = m_info.clientY + clientY; + + SetCursorPos(screenX, screenY); + return true; +} + +bool WindowController::SendClick(int phoneX, int phoneY) { + if (!m_hwnd) return false; + + // Save current cursor position + POINT oldPos; + GetCursorPos(&oldPos); + + // Ensure window is active + SetForegroundWindow(m_hwnd); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // Perform click + SendMouseDown(phoneX, phoneY); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + SendMouseUp(phoneX, phoneY); + + // Restore cursor position (optional - comment out if cursor restoration causes issues) + // std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // SetCursorPos(oldPos.x, oldPos.y); + + return true; +} + +bool WindowController::SendKey(UINT vkCode) { + if (!m_hwnd) return false; + + // Send key down and up + LPARAM lParam = 1; // Repeat count = 1 + ::PostMessage(m_hwnd, WM_KEYDOWN, vkCode, lParam); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + ::PostMessage(m_hwnd, WM_KEYUP, vkCode, lParam | (1 << 30) | (1 << 31)); + + return true; +} + +bool WindowController::SendChar(char c) { + if (!m_hwnd) return false; + + ::PostMessage(m_hwnd, WM_CHAR, static_cast(c), 0); + return true; +} + +bool WindowController::Activate() { + if (!m_hwnd) return false; + + ::SetForegroundWindow(m_hwnd); + return true; +} + +bool WindowController::Close() { + if (!m_hwnd) return false; + + ::PostMessage(m_hwnd, WM_CLOSE, 0, 0); + return true; +} + +bool WindowController::ResizeToPhone() { + if (!m_hwnd) return false; + + // Get current window rect to preserve position + RECT currentRect; + ::GetWindowRect(m_hwnd, ¤tRect); + + // Calculate window size needed for phone-sized client area + RECT desiredRect = {0, 0, PHONE_WIDTH, PHONE_HEIGHT}; + DWORD style = ::GetWindowLong(m_hwnd, GWL_STYLE); + DWORD exStyle = ::GetWindowLong(m_hwnd, GWL_EXSTYLE); + ::AdjustWindowRectEx(&desiredRect, style, FALSE, exStyle); + + int newWidth = desiredRect.right - desiredRect.left; + int newHeight = desiredRect.bottom - desiredRect.top; + + // Check screen dimensions + int screenWidth = ::GetSystemMetrics(SM_CXSCREEN); + int screenHeight = ::GetSystemMetrics(SM_CYSCREEN); + + std::cout << "ResizeToPhone: screen=" << screenWidth << "x" << screenHeight + << ", needed=" << newWidth << "x" << newHeight << std::endl; + + // If screen is too small, we can't resize to full phone size + if (newHeight > screenHeight) { + std::cout << " Warning: Screen too small for full phone resolution, keeping current size" << std::endl; + return true; // Not an error, just can't resize + } + + // Move window to top-left to ensure it fits on screen + int newX = 0; + int newY = 0; + + std::cout << " Moving to (" << newX << "," << newY << ") size " << newWidth << "x" << newHeight << std::endl; + ::MoveWindow(m_hwnd, newX, newY, newWidth, newHeight, TRUE); + + // Re-acquire window info + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + return FindWindow("Mosis Designer"); +} + +bool WindowController::SendClickFromBottom(int clientX, int offsetFromBottom) { + if (!m_hwnd) return false; + + // Calculate Y position from bottom of client area + int clientY = m_info.clientHeight - offsetFromBottom; + + // Convert to screen coordinates + int screenX = m_info.clientX + clientX; + int screenY = m_info.clientY + clientY; + + // Save current cursor position + POINT oldPos; + GetCursorPos(&oldPos); + + // Ensure window is active + SetForegroundWindow(m_hwnd); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // Move cursor and click + SetCursorPos(screenX, screenY); + + INPUT input = {}; + input.type = INPUT_MOUSE; + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + SendInput(1, &input, sizeof(INPUT)); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + SendInput(1, &input, sizeof(INPUT)); + + std::cout << "ClickFromBottom at client(" << clientX << "," << clientY + << ") -> screen(" << screenX << "," << screenY << ")" << std::endl; + + return true; +} + +} // namespace mosis::test diff --git a/designer-test/src/window_controller.h b/designer-test/src/window_controller.h new file mode 100644 index 0000000..9d28d1a --- /dev/null +++ b/designer-test/src/window_controller.h @@ -0,0 +1,77 @@ +// Window controller for sending input events to the designer +#pragma once + +#define NOMINMAX +#include +#include +#include + +namespace mosis::test { + +// Phone UI dimensions (default resolution) +constexpr int PHONE_WIDTH = 540; +constexpr int PHONE_HEIGHT = 960; + +// Window information +struct WindowInfo { + HWND hwnd; + RECT windowRect; + RECT clientRect; + int clientX; // Client area X in screen coords + int clientY; // Client area Y in screen coords + int clientWidth; // Client area width + int clientHeight; // Client area height + float scaleX; // DPI scale X + float scaleY; // DPI scale Y +}; + +class WindowController { +public: + WindowController() = default; + ~WindowController() = default; + + // Find the designer window by title + bool FindWindow(const std::string& title = "Mosis Designer"); + + // Check if window is valid + bool IsValid() const { return m_hwnd != nullptr; } + + // Get window info + const WindowInfo& GetInfo() const { return m_info; } + + // Send mouse events at phone coordinates (0-540, 0-960) + // These coordinates are scaled to match the actual window size + bool SendMouseDown(int phoneX, int phoneY); + bool SendMouseUp(int phoneX, int phoneY); + bool SendMouseMove(int phoneX, int phoneY); + bool SendClick(int phoneX, int phoneY); + + // Send click at position relative to bottom of window + // clientX is absolute X in client area, offsetFromBottom is Y offset from bottom edge + bool SendClickFromBottom(int clientX, int offsetFromBottom); + + // Send keyboard events + bool SendKey(UINT vkCode); + bool SendChar(char c); + + // Bring window to foreground + bool Activate(); + + // Close the window + bool Close(); + + // Resize window to match phone dimensions + bool ResizeToPhone(); + + // Wait for window to appear (returns false on timeout) + bool WaitForWindow(const std::string& title, int timeoutMs = 10000); + +private: + // Convert phone coordinates to client coordinates (accounting for DPI scale) + LPARAM PhoneToClientLParam(int phoneX, int phoneY); + + HWND m_hwnd = nullptr; + WindowInfo m_info = {}; +}; + +} // namespace mosis::test diff --git a/designer-test/vcpkg.json b/designer-test/vcpkg.json new file mode 100644 index 0000000..45c7ada --- /dev/null +++ b/designer-test/vcpkg.json @@ -0,0 +1,7 @@ +{ + "name": "designer-test", + "version-string": "0.1.0", + "dependencies": [ + "nlohmann-json" + ] +} diff --git a/designer/CMakeLists.txt b/designer/CMakeLists.txt new file mode 100644 index 0000000..07393fe --- /dev/null +++ b/designer/CMakeLists.txt @@ -0,0 +1,104 @@ +cmake_minimum_required(VERSION 3.22.1) +project(mosis-designer) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find Lua before RmlUi so it can be used +find_package(Lua REQUIRED) + +# Find other dependencies via vcpkg +find_package(glfw3 CONFIG REQUIRED) +find_package(freetype CONFIG REQUIRED) +find_package(PNG REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) + +# Fetch RmlUi +include(FetchContent) +FetchContent_Declare( + rmlui + GIT_REPOSITORY https://github.com/mikke89/RmlUi.git + GIT_TAG 6.0 +) + +# Enable RmlUi Lua bindings before fetching +set(RMLUI_LUA_BINDINGS ON CACHE BOOL "" FORCE) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE) +set(RMLUI_TESTS OFF CACHE BOOL "" FORCE) +set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE) + +FetchContent_MakeAvailable(rmlui) + +# Get the RmlUi source directory for backend sources +FetchContent_GetProperties(rmlui) +set(RMLUI_SOURCE_DIR ${rmlui_SOURCE_DIR}) + +# Shared kernel library sources (platform-agnostic code) +set(KERNEL_SOURCES + ../src/main/kernel/src/platform.cpp + ../src/main/kernel/src/file_interface.cpp +) + +# Desktop platform sources +set(DESIGNER_SOURCES + src/main.cpp + src/desktop_platform.cpp + src/hot_reload.cpp + src/data_models.cpp + src/kernel_impl.cpp + src/testing/action_recorder.cpp + src/testing/action_player.cpp + src/testing/ui_inspector.cpp + src/testing/visual_capture.cpp + # RmlUi backend sources + ${RMLUI_SOURCE_DIR}/Backends/RmlUi_Backend_GLFW_GL3.cpp + ${RMLUI_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp + ${RMLUI_SOURCE_DIR}/Backends/RmlUi_Renderer_GL3.cpp +) + +# Designer executable +add_executable(mosis-designer + ${KERNEL_SOURCES} + ${DESIGNER_SOURCES} +) + +target_include_directories(mosis-designer PRIVATE + src + ../src/main/kernel/include + ${RMLUI_SOURCE_DIR} + ${RMLUI_SOURCE_DIR}/Backends + ${LUA_INCLUDE_DIR} +) + +target_link_libraries(mosis-designer PRIVATE + glfw + freetype + PNG::PNG + RmlUi::RmlUi + RmlUi::Lua + nlohmann_json::nlohmann_json +) + +target_compile_definitions(mosis-designer PRIVATE + MOSIS_PLATFORM_DESKTOP + RMLUI_STATIC_LIB +) + +# Platform-specific libraries +if(WIN32) + target_link_libraries(mosis-designer PRIVATE opengl32) +elseif(APPLE) + find_library(OPENGL_LIBRARY OpenGL) + target_link_libraries(mosis-designer PRIVATE ${OPENGL_LIBRARY}) +else() + find_package(OpenGL REQUIRED) + target_link_libraries(mosis-designer PRIVATE OpenGL::GL) +endif() + +# Copy assets for development +add_custom_command(TARGET mosis-designer POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/../src/main/assets + $/assets +) diff --git a/designer/glad/include/KHR/khrplatform.h b/designer/glad/include/KHR/khrplatform.h new file mode 100644 index 0000000..d930cd6 --- /dev/null +++ b/designer/glad/include/KHR/khrplatform.h @@ -0,0 +1,137 @@ +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) +# define KHRONOS_STATIC 1 +#endif + +#if defined(KHRONOS_STATIC) +# define KHRONOS_APICALL +#elif defined(_WIN32) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(KHRONOS_STATIC) +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +#if defined(KHRONOS_SUPPORT_INT64) +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__cplusplus) +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#else +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 +#endif + +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +#ifdef KHRONOS_SUPPORT_FLOAT +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +typedef khronos_uint64_t khronos_usize_t; +typedef khronos_int64_t khronos_ssize_t; + +#endif /* __khrplatform_h_ */ diff --git a/designer/glad/include/glad/gl.h b/designer/glad/include/glad/gl.h new file mode 100644 index 0000000..3debac9 --- /dev/null +++ b/designer/glad/include/glad/gl.h @@ -0,0 +1,623 @@ +/* + * GLAD OpenGL 3.3 Core Profile Loader + * Generated for Mosis Designer + */ + +#ifndef GLAD_GL_H_ +#define GLAD_GL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define GLAD_VERSION_MAJOR 2 +#define GLAD_VERSION_MINOR 0 + +#ifndef GLAD_API_CALL +#if defined(_WIN32) || defined(__CYGWIN__) +# define GLAD_API_CALL __declspec(dllexport) +#else +# define GLAD_API_CALL __attribute__((visibility("default"))) +#endif +#endif + +#define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor) +#define GLAD_VERSION GLAD_MAKE_VERSION(GLAD_VERSION_MAJOR, GLAD_VERSION_MINOR) + +typedef void (*GLADapiproc)(void); +typedef GLADapiproc (*GLADloadfunc)(const char *name); + +GLAD_API_CALL int gladLoadGL(GLADloadfunc load); +GLAD_API_CALL int gladLoadGLUserPtr(GLADloadfunc load, void *userptr); + +/* GL Types */ +typedef void GLvoid; +typedef unsigned int GLenum; +typedef float GLfloat; +typedef int GLint; +typedef int GLsizei; +typedef unsigned int GLbitfield; +typedef double GLdouble; +typedef unsigned int GLuint; +typedef unsigned char GLboolean; +typedef khronos_uint8_t GLubyte; +typedef khronos_int8_t GLbyte; +typedef khronos_int16_t GLshort; +typedef khronos_uint16_t GLushort; +typedef khronos_float_t GLclampf; +typedef double GLclampd; +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +typedef char GLchar; +typedef khronos_int64_t GLint64; +typedef khronos_uint64_t GLuint64; + +#ifndef __gl_glcorearb_h_ + +/* GL Constants */ +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_NONE 0 +#define GL_ZERO 0 +#define GL_ONE 1 + +/* Primitives */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 + +/* Depth */ +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_DEPTH_WRITEMASK 0x0B72 + +/* Stencil */ +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_INVERT 0x150A +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 + +/* Blending */ +#define GL_BLEND 0x0BE2 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B + +/* Culling */ +#define GL_CULL_FACE 0x0B44 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 + +/* Comparison */ +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 + +/* Errors */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 + +/* Data types */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_HALF_FLOAT 0x140B + +/* Gets */ +#define GL_VIEWPORT 0x0BA2 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_UNPACK_ALIGNMENT 0x0CF5 + +/* Textures */ +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_REPEAT 0x2901 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_MIRRORED_REPEAT 0x8370 + +/* Pixel formats */ +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_R8 0x8229 +#define GL_RG8 0x822B +#define GL_RGB8 0x8051 +#define GL_RGBA8 0x8058 +#define GL_SRGB8 0x8C41 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_DEPTH_STENCIL 0x84F9 + +/* Shaders */ +#define GL_VERTEX_SHADER 0x8B31 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_SHADER_TYPE 0x8B4F +#define GL_DELETE_STATUS 0x8B80 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A + +/* Buffers */ +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD + +/* VAOs */ +#define GL_VERTEX_ARRAY_BINDING 0x85B5 + +/* Framebuffers */ +#define GL_FRAMEBUFFER 0x8D40 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_RENDERBUFFER 0x8D41 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_MAX_SAMPLES 0x8D57 + +/* Multisample */ +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 + +/* Misc */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_DITHER 0x0BD0 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_PROGRAM_POINT_SIZE 0x8642 + +#endif /* __gl_glcorearb_h_ */ + +/* Version flags */ +#define GLAD_GL 1 +#define GLAD_GL_VERSION_1_0 1 +#define GLAD_GL_VERSION_1_1 1 +#define GLAD_GL_VERSION_1_2 1 +#define GLAD_GL_VERSION_1_3 1 +#define GLAD_GL_VERSION_1_4 1 +#define GLAD_GL_VERSION_1_5 1 +#define GLAD_GL_VERSION_2_0 1 +#define GLAD_GL_VERSION_2_1 1 +#define GLAD_GL_VERSION_3_0 1 +#define GLAD_GL_VERSION_3_1 1 +#define GLAD_GL_VERSION_3_2 1 +#define GLAD_GL_VERSION_3_3 1 + +/* Function pointer types */ +typedef void (KHRONOS_APIENTRY *PFNGLACTIVETEXTUREPROC)(GLenum texture); +typedef void (KHRONOS_APIENTRY *PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); +typedef void (KHRONOS_APIENTRY *PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); +typedef void (KHRONOS_APIENTRY *PFNGLBINDBUFFERBASEPROC)(GLenum target, GLuint index, GLuint buffer); +typedef void (KHRONOS_APIENTRY *PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer); +typedef void (KHRONOS_APIENTRY *PFNGLBINDRENDERBUFFERPROC)(GLenum target, GLuint renderbuffer); +typedef void (KHRONOS_APIENTRY *PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture); +typedef void (KHRONOS_APIENTRY *PFNGLBINDVERTEXARRAYPROC)(GLuint array); +typedef void (KHRONOS_APIENTRY *PFNGLBLENDCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (KHRONOS_APIENTRY *PFNGLBLENDEQUATIONPROC)(GLenum mode); +typedef void (KHRONOS_APIENTRY *PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, GLenum modeAlpha); +typedef void (KHRONOS_APIENTRY *PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor); +typedef void (KHRONOS_APIENTRY *PFNGLBLENDFUNCSEPARATEPROC)(GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (KHRONOS_APIENTRY *PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (KHRONOS_APIENTRY *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (KHRONOS_APIENTRY *PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef GLenum (KHRONOS_APIENTRY *PFNGLCHECKFRAMEBUFFERSTATUSPROC)(GLenum target); +typedef void (KHRONOS_APIENTRY *PFNGLCLEARPROC)(GLbitfield mask); +typedef void (KHRONOS_APIENTRY *PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (KHRONOS_APIENTRY *PFNGLCLEARDEPTHPROC)(GLdouble depth); +typedef void (KHRONOS_APIENTRY *PFNGLCLEARSTENCILPROC)(GLint s); +typedef void (KHRONOS_APIENTRY *PFNGLCOLORMASKPROC)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +typedef void (KHRONOS_APIENTRY *PFNGLCOMPILESHADERPROC)(GLuint shader); +typedef GLuint (KHRONOS_APIENTRY *PFNGLCREATEPROGRAMPROC)(void); +typedef GLuint (KHRONOS_APIENTRY *PFNGLCREATESHADERPROC)(GLenum type); +typedef void (KHRONOS_APIENTRY *PFNGLCULLFACEPROC)(GLenum mode); +typedef void (KHRONOS_APIENTRY *PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers); +typedef void (KHRONOS_APIENTRY *PFNGLDELETEFRAMEBUFFERSPROC)(GLsizei n, const GLuint *framebuffers); +typedef void (KHRONOS_APIENTRY *PFNGLDELETEPROGRAMPROC)(GLuint program); +typedef void (KHRONOS_APIENTRY *PFNGLDELETERENDERBUFFERSPROC)(GLsizei n, const GLuint *renderbuffers); +typedef void (KHRONOS_APIENTRY *PFNGLDELETESHADERPROC)(GLuint shader); +typedef void (KHRONOS_APIENTRY *PFNGLDELETETEXTURESPROC)(GLsizei n, const GLuint *textures); +typedef void (KHRONOS_APIENTRY *PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint *arrays); +typedef void (KHRONOS_APIENTRY *PFNGLDEPTHFUNCPROC)(GLenum func); +typedef void (KHRONOS_APIENTRY *PFNGLDEPTHMASKPROC)(GLboolean flag); +typedef void (KHRONOS_APIENTRY *PFNGLDISABLEPROC)(GLenum cap); +typedef void (KHRONOS_APIENTRY *PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); +typedef void (KHRONOS_APIENTRY *PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count); +typedef void (KHRONOS_APIENTRY *PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum *bufs); +typedef void (KHRONOS_APIENTRY *PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (KHRONOS_APIENTRY *PFNGLENABLEPROC)(GLenum cap); +typedef void (KHRONOS_APIENTRY *PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); +typedef void (KHRONOS_APIENTRY *PFNGLFINISHPROC)(void); +typedef void (KHRONOS_APIENTRY *PFNGLFLUSHPROC)(void); +typedef void (KHRONOS_APIENTRY *PFNGLFRAMEBUFFERRENDERBUFFERPROC)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (KHRONOS_APIENTRY *PFNGLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (KHRONOS_APIENTRY *PFNGLFRONTFACEPROC)(GLenum mode); +typedef void (KHRONOS_APIENTRY *PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers); +typedef void (KHRONOS_APIENTRY *PFNGLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint *framebuffers); +typedef void (KHRONOS_APIENTRY *PFNGLGENRENDERBUFFERSPROC)(GLsizei n, GLuint *renderbuffers); +typedef void (KHRONOS_APIENTRY *PFNGLGENTEXTURESPROC)(GLsizei n, GLuint *textures); +typedef void (KHRONOS_APIENTRY *PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint *arrays); +typedef void (KHRONOS_APIENTRY *PFNGLGENERATEMIPMAPPROC)(GLenum target); +typedef GLint (KHRONOS_APIENTRY *PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar *name); +typedef GLenum (KHRONOS_APIENTRY *PFNGLGETERRORPROC)(void); +typedef void (KHRONOS_APIENTRY *PFNGLGETINTEGERVPROC)(GLenum pname, GLint *data); +typedef void (KHRONOS_APIENTRY *PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (KHRONOS_APIENTRY *PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint *params); +typedef void (KHRONOS_APIENTRY *PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (KHRONOS_APIENTRY *PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint *params); +typedef const GLubyte * (KHRONOS_APIENTRY *PFNGLGETSTRINGPROC)(GLenum name); +typedef const GLubyte * (KHRONOS_APIENTRY *PFNGLGETSTRINGIPROC)(GLenum name, GLuint index); +typedef GLint (KHRONOS_APIENTRY *PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar *name); +typedef GLboolean (KHRONOS_APIENTRY *PFNGLISENABLEDPROC)(GLenum cap); +typedef void (KHRONOS_APIENTRY *PFNGLLINKPROGRAMPROC)(GLuint program); +typedef void * (KHRONOS_APIENTRY *PFNGLMAPBUFFERPROC)(GLenum target, GLenum access); +typedef void (KHRONOS_APIENTRY *PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); +typedef void (KHRONOS_APIENTRY *PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); +typedef void (KHRONOS_APIENTRY *PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +typedef void (KHRONOS_APIENTRY *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (KHRONOS_APIENTRY *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (KHRONOS_APIENTRY *PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (KHRONOS_APIENTRY *PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (KHRONOS_APIENTRY *PFNGLSTENCILFUNCPROC)(GLenum func, GLint ref, GLuint mask); +typedef void (KHRONOS_APIENTRY *PFNGLSTENCILFUNCSEPARATEPROC)(GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (KHRONOS_APIENTRY *PFNGLSTENCILMASKPROC)(GLuint mask); +typedef void (KHRONOS_APIENTRY *PFNGLSTENCILMASKSEPARATEPROC)(GLenum face, GLuint mask); +typedef void (KHRONOS_APIENTRY *PFNGLSTENCILOPPROC)(GLenum fail, GLenum zfail, GLenum zpass); +typedef void (KHRONOS_APIENTRY *PFNGLSTENCILOPSEPARATEPROC)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (KHRONOS_APIENTRY *PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (KHRONOS_APIENTRY *PFNGLTEXPARAMETERFPROC)(GLenum target, GLenum pname, GLfloat param); +typedef void (KHRONOS_APIENTRY *PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); +typedef void (KHRONOS_APIENTRY *PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM1FVPROC)(GLint location, GLsizei count, const GLfloat *value); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM1IPROC)(GLint location, GLint v0); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM2FVPROC)(GLint location, GLsizei count, const GLfloat *value); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORM4FVPROC)(GLint location, GLsizei count, const GLfloat *value); +typedef void (KHRONOS_APIENTRY *PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef GLboolean (KHRONOS_APIENTRY *PFNGLUNMAPBUFFERPROC)(GLenum target); +typedef void (KHRONOS_APIENTRY *PFNGLUSEPROGRAMPROC)(GLuint program); +typedef void (KHRONOS_APIENTRY *PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (KHRONOS_APIENTRY *PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height); + +/* Global function pointers */ +GLAD_API_CALL PFNGLACTIVETEXTUREPROC glad_glActiveTexture; +#define glActiveTexture glad_glActiveTexture +GLAD_API_CALL PFNGLATTACHSHADERPROC glad_glAttachShader; +#define glAttachShader glad_glAttachShader +GLAD_API_CALL PFNGLBINDBUFFERPROC glad_glBindBuffer; +#define glBindBuffer glad_glBindBuffer +GLAD_API_CALL PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase; +#define glBindBufferBase glad_glBindBufferBase +GLAD_API_CALL PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer; +#define glBindFramebuffer glad_glBindFramebuffer +GLAD_API_CALL PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer; +#define glBindRenderbuffer glad_glBindRenderbuffer +GLAD_API_CALL PFNGLBINDTEXTUREPROC glad_glBindTexture; +#define glBindTexture glad_glBindTexture +GLAD_API_CALL PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray; +#define glBindVertexArray glad_glBindVertexArray +GLAD_API_CALL PFNGLBLENDCOLORPROC glad_glBlendColor; +#define glBlendColor glad_glBlendColor +GLAD_API_CALL PFNGLBLENDEQUATIONPROC glad_glBlendEquation; +#define glBlendEquation glad_glBlendEquation +GLAD_API_CALL PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate; +#define glBlendEquationSeparate glad_glBlendEquationSeparate +GLAD_API_CALL PFNGLBLENDFUNCPROC glad_glBlendFunc; +#define glBlendFunc glad_glBlendFunc +GLAD_API_CALL PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate; +#define glBlendFuncSeparate glad_glBlendFuncSeparate +GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer; +#define glBlitFramebuffer glad_glBlitFramebuffer +GLAD_API_CALL PFNGLBUFFERDATAPROC glad_glBufferData; +#define glBufferData glad_glBufferData +GLAD_API_CALL PFNGLBUFFERSUBDATAPROC glad_glBufferSubData; +#define glBufferSubData glad_glBufferSubData +GLAD_API_CALL PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus; +#define glCheckFramebufferStatus glad_glCheckFramebufferStatus +GLAD_API_CALL PFNGLCLEARPROC glad_glClear; +#define glClear glad_glClear +GLAD_API_CALL PFNGLCLEARCOLORPROC glad_glClearColor; +#define glClearColor glad_glClearColor +GLAD_API_CALL PFNGLCLEARDEPTHPROC glad_glClearDepth; +#define glClearDepth glad_glClearDepth +GLAD_API_CALL PFNGLCLEARSTENCILPROC glad_glClearStencil; +#define glClearStencil glad_glClearStencil +GLAD_API_CALL PFNGLCOLORMASKPROC glad_glColorMask; +#define glColorMask glad_glColorMask +GLAD_API_CALL PFNGLCOMPILESHADERPROC glad_glCompileShader; +#define glCompileShader glad_glCompileShader +GLAD_API_CALL PFNGLCREATEPROGRAMPROC glad_glCreateProgram; +#define glCreateProgram glad_glCreateProgram +GLAD_API_CALL PFNGLCREATESHADERPROC glad_glCreateShader; +#define glCreateShader glad_glCreateShader +GLAD_API_CALL PFNGLCULLFACEPROC glad_glCullFace; +#define glCullFace glad_glCullFace +GLAD_API_CALL PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers; +#define glDeleteBuffers glad_glDeleteBuffers +GLAD_API_CALL PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers; +#define glDeleteFramebuffers glad_glDeleteFramebuffers +GLAD_API_CALL PFNGLDELETEPROGRAMPROC glad_glDeleteProgram; +#define glDeleteProgram glad_glDeleteProgram +GLAD_API_CALL PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers; +#define glDeleteRenderbuffers glad_glDeleteRenderbuffers +GLAD_API_CALL PFNGLDELETESHADERPROC glad_glDeleteShader; +#define glDeleteShader glad_glDeleteShader +GLAD_API_CALL PFNGLDELETETEXTURESPROC glad_glDeleteTextures; +#define glDeleteTextures glad_glDeleteTextures +GLAD_API_CALL PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays; +#define glDeleteVertexArrays glad_glDeleteVertexArrays +GLAD_API_CALL PFNGLDEPTHFUNCPROC glad_glDepthFunc; +#define glDepthFunc glad_glDepthFunc +GLAD_API_CALL PFNGLDEPTHMASKPROC glad_glDepthMask; +#define glDepthMask glad_glDepthMask +GLAD_API_CALL PFNGLDISABLEPROC glad_glDisable; +#define glDisable glad_glDisable +GLAD_API_CALL PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray; +#define glDisableVertexAttribArray glad_glDisableVertexAttribArray +GLAD_API_CALL PFNGLDRAWARRAYSPROC glad_glDrawArrays; +#define glDrawArrays glad_glDrawArrays +GLAD_API_CALL PFNGLDRAWBUFFERSPROC glad_glDrawBuffers; +#define glDrawBuffers glad_glDrawBuffers +GLAD_API_CALL PFNGLDRAWELEMENTSPROC glad_glDrawElements; +#define glDrawElements glad_glDrawElements +GLAD_API_CALL PFNGLENABLEPROC glad_glEnable; +#define glEnable glad_glEnable +GLAD_API_CALL PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray; +#define glEnableVertexAttribArray glad_glEnableVertexAttribArray +GLAD_API_CALL PFNGLFINISHPROC glad_glFinish; +#define glFinish glad_glFinish +GLAD_API_CALL PFNGLFLUSHPROC glad_glFlush; +#define glFlush glad_glFlush +GLAD_API_CALL PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer; +#define glFramebufferRenderbuffer glad_glFramebufferRenderbuffer +GLAD_API_CALL PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D; +#define glFramebufferTexture2D glad_glFramebufferTexture2D +GLAD_API_CALL PFNGLFRONTFACEPROC glad_glFrontFace; +#define glFrontFace glad_glFrontFace +GLAD_API_CALL PFNGLGENBUFFERSPROC glad_glGenBuffers; +#define glGenBuffers glad_glGenBuffers +GLAD_API_CALL PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers; +#define glGenFramebuffers glad_glGenFramebuffers +GLAD_API_CALL PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers; +#define glGenRenderbuffers glad_glGenRenderbuffers +GLAD_API_CALL PFNGLGENTEXTURESPROC glad_glGenTextures; +#define glGenTextures glad_glGenTextures +GLAD_API_CALL PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays; +#define glGenVertexArrays glad_glGenVertexArrays +GLAD_API_CALL PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap; +#define glGenerateMipmap glad_glGenerateMipmap +GLAD_API_CALL PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation; +#define glGetAttribLocation glad_glGetAttribLocation +GLAD_API_CALL PFNGLGETERRORPROC glad_glGetError; +#define glGetError glad_glGetError +GLAD_API_CALL PFNGLGETINTEGERVPROC glad_glGetIntegerv; +#define glGetIntegerv glad_glGetIntegerv +GLAD_API_CALL PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog; +#define glGetProgramInfoLog glad_glGetProgramInfoLog +GLAD_API_CALL PFNGLGETPROGRAMIVPROC glad_glGetProgramiv; +#define glGetProgramiv glad_glGetProgramiv +GLAD_API_CALL PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog; +#define glGetShaderInfoLog glad_glGetShaderInfoLog +GLAD_API_CALL PFNGLGETSHADERIVPROC glad_glGetShaderiv; +#define glGetShaderiv glad_glGetShaderiv +GLAD_API_CALL PFNGLGETSTRINGPROC glad_glGetString; +#define glGetString glad_glGetString +GLAD_API_CALL PFNGLGETSTRINGIPROC glad_glGetStringi; +#define glGetStringi glad_glGetStringi +GLAD_API_CALL PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation; +#define glGetUniformLocation glad_glGetUniformLocation +GLAD_API_CALL PFNGLISENABLEDPROC glad_glIsEnabled; +#define glIsEnabled glad_glIsEnabled +GLAD_API_CALL PFNGLLINKPROGRAMPROC glad_glLinkProgram; +#define glLinkProgram glad_glLinkProgram +GLAD_API_CALL PFNGLMAPBUFFERPROC glad_glMapBuffer; +#define glMapBuffer glad_glMapBuffer +GLAD_API_CALL PFNGLPIXELSTOREIPROC glad_glPixelStorei; +#define glPixelStorei glad_glPixelStorei +GLAD_API_CALL PFNGLPOLYGONMODEPROC glad_glPolygonMode; +#define glPolygonMode glad_glPolygonMode +GLAD_API_CALL PFNGLREADPIXELSPROC glad_glReadPixels; +#define glReadPixels glad_glReadPixels +GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage; +#define glRenderbufferStorage glad_glRenderbufferStorage +GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample; +#define glRenderbufferStorageMultisample glad_glRenderbufferStorageMultisample +GLAD_API_CALL PFNGLSCISSORPROC glad_glScissor; +#define glScissor glad_glScissor +GLAD_API_CALL PFNGLSHADERSOURCEPROC glad_glShaderSource; +#define glShaderSource glad_glShaderSource +GLAD_API_CALL PFNGLSTENCILFUNCPROC glad_glStencilFunc; +#define glStencilFunc glad_glStencilFunc +GLAD_API_CALL PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate; +#define glStencilFuncSeparate glad_glStencilFuncSeparate +GLAD_API_CALL PFNGLSTENCILMASKPROC glad_glStencilMask; +#define glStencilMask glad_glStencilMask +GLAD_API_CALL PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate; +#define glStencilMaskSeparate glad_glStencilMaskSeparate +GLAD_API_CALL PFNGLSTENCILOPPROC glad_glStencilOp; +#define glStencilOp glad_glStencilOp +GLAD_API_CALL PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate; +#define glStencilOpSeparate glad_glStencilOpSeparate +GLAD_API_CALL PFNGLTEXIMAGE2DPROC glad_glTexImage2D; +#define glTexImage2D glad_glTexImage2D +GLAD_API_CALL PFNGLTEXPARAMETERFPROC glad_glTexParameterf; +#define glTexParameterf glad_glTexParameterf +GLAD_API_CALL PFNGLTEXPARAMETERIPROC glad_glTexParameteri; +#define glTexParameteri glad_glTexParameteri +GLAD_API_CALL PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D; +#define glTexSubImage2D glad_glTexSubImage2D +GLAD_API_CALL PFNGLUNIFORM1FPROC glad_glUniform1f; +#define glUniform1f glad_glUniform1f +GLAD_API_CALL PFNGLUNIFORM1FVPROC glad_glUniform1fv; +#define glUniform1fv glad_glUniform1fv +GLAD_API_CALL PFNGLUNIFORM1IPROC glad_glUniform1i; +#define glUniform1i glad_glUniform1i +GLAD_API_CALL PFNGLUNIFORM2FPROC glad_glUniform2f; +#define glUniform2f glad_glUniform2f +GLAD_API_CALL PFNGLUNIFORM2FVPROC glad_glUniform2fv; +#define glUniform2fv glad_glUniform2fv +GLAD_API_CALL PFNGLUNIFORM3FPROC glad_glUniform3f; +#define glUniform3f glad_glUniform3f +GLAD_API_CALL PFNGLUNIFORM4FPROC glad_glUniform4f; +#define glUniform4f glad_glUniform4f +GLAD_API_CALL PFNGLUNIFORM4FVPROC glad_glUniform4fv; +#define glUniform4fv glad_glUniform4fv +GLAD_API_CALL PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv; +#define glUniformMatrix4fv glad_glUniformMatrix4fv +GLAD_API_CALL PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer; +#define glUnmapBuffer glad_glUnmapBuffer +GLAD_API_CALL PFNGLUSEPROGRAMPROC glad_glUseProgram; +#define glUseProgram glad_glUseProgram +GLAD_API_CALL PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer; +#define glVertexAttribPointer glad_glVertexAttribPointer +GLAD_API_CALL PFNGLVIEWPORTPROC glad_glViewport; +#define glViewport glad_glViewport + +#ifdef __cplusplus +} +#endif + +#endif /* GLAD_GL_H_ */ diff --git a/designer/glad/src/gl.c b/designer/glad/src/gl.c new file mode 100644 index 0000000..eecde4d --- /dev/null +++ b/designer/glad/src/gl.c @@ -0,0 +1,322 @@ +/* + * GLAD OpenGL 3.3 Core Profile Loader Implementation + */ + +#include +#include + +#ifdef _WIN32 +#include +static HMODULE libGL; + +static int open_gl(void) { + libGL = LoadLibraryW(L"opengl32.dll"); + return libGL != NULL; +} + +static void close_gl(void) { + if (libGL != NULL) { + FreeLibrary(libGL); + libGL = NULL; + } +} + +static GLADapiproc get_proc(const char *name) { + GLADapiproc result = NULL; + + /* Try wglGetProcAddress first for extension functions */ + typedef GLADapiproc (KHRONOS_APIENTRY *PFNWGLGETPROCADDRESSPROC)(const char*); + static PFNWGLGETPROCADDRESSPROC wglGetProcAddress_ptr = NULL; + + if (wglGetProcAddress_ptr == NULL && libGL != NULL) { + wglGetProcAddress_ptr = (PFNWGLGETPROCADDRESSPROC)GetProcAddress(libGL, "wglGetProcAddress"); + } + + if (wglGetProcAddress_ptr != NULL) { + result = wglGetProcAddress_ptr(name); + } + + /* Fall back to GetProcAddress for core functions */ + if (result == NULL && libGL != NULL) { + result = (GLADapiproc)GetProcAddress(libGL, name); + } + + return result; +} + +#elif defined(__APPLE__) +#include +static void* libGL; + +static int open_gl(void) { + libGL = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_LOCAL); + return libGL != NULL; +} + +static void close_gl(void) { + if (libGL != NULL) { + dlclose(libGL); + libGL = NULL; + } +} + +static GLADapiproc get_proc(const char *name) { + if (libGL == NULL) return NULL; + return (GLADapiproc)dlsym(libGL, name); +} + +#else /* Linux/Unix */ +#include +static void* libGL; + +static int open_gl(void) { + libGL = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL); + if (libGL == NULL) { + libGL = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); + } + return libGL != NULL; +} + +static void close_gl(void) { + if (libGL != NULL) { + dlclose(libGL); + libGL = NULL; + } +} + +static GLADapiproc get_proc(const char *name) { + if (libGL == NULL) return NULL; + return (GLADapiproc)dlsym(libGL, name); +} +#endif + +/* Function pointers */ +PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL; +PFNGLATTACHSHADERPROC glad_glAttachShader = NULL; +PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL; +PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL; +PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL; +PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL; +PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL; +PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL; +PFNGLBLENDCOLORPROC glad_glBlendColor = NULL; +PFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL; +PFNGLBLENDEQUATIONSEPARATEPROC glad_glBlendEquationSeparate = NULL; +PFNGLBLENDFUNCPROC glad_glBlendFunc = NULL; +PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL; +PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL; +PFNGLBUFFERDATAPROC glad_glBufferData = NULL; +PFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL; +PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus = NULL; +PFNGLCLEARPROC glad_glClear = NULL; +PFNGLCLEARCOLORPROC glad_glClearColor = NULL; +PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL; +PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL; +PFNGLCOLORMASKPROC glad_glColorMask = NULL; +PFNGLCOMPILESHADERPROC glad_glCompileShader = NULL; +PFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL; +PFNGLCREATESHADERPROC glad_glCreateShader = NULL; +PFNGLCULLFACEPROC glad_glCullFace = NULL; +PFNGLDELETEBUFFERSPROC glad_glDeleteBuffers = NULL; +PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers = NULL; +PFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL; +PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers = NULL; +PFNGLDELETESHADERPROC glad_glDeleteShader = NULL; +PFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL; +PFNGLDELETEVERTEXARRAYSPROC glad_glDeleteVertexArrays = NULL; +PFNGLDEPTHFUNCPROC glad_glDepthFunc = NULL; +PFNGLDEPTHMASKPROC glad_glDepthMask = NULL; +PFNGLDISABLEPROC glad_glDisable = NULL; +PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL; +PFNGLDRAWARRAYSPROC glad_glDrawArrays = NULL; +PFNGLDRAWBUFFERSPROC glad_glDrawBuffers = NULL; +PFNGLDRAWELEMENTSPROC glad_glDrawElements = NULL; +PFNGLENABLEPROC glad_glEnable = NULL; +PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL; +PFNGLFINISHPROC glad_glFinish = NULL; +PFNGLFLUSHPROC glad_glFlush = NULL; +PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL; +PFNGLFRAMEBUFFERTEXTURE2DPROC glad_glFramebufferTexture2D = NULL; +PFNGLFRONTFACEPROC glad_glFrontFace = NULL; +PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL; +PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL; +PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers = NULL; +PFNGLGENTEXTURESPROC glad_glGenTextures = NULL; +PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL; +PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL; +PFNGLGETATTRIBLOCATIONPROC glad_glGetAttribLocation = NULL; +PFNGLGETERRORPROC glad_glGetError = NULL; +PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL; +PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL; +PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL; +PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL; +PFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL; +PFNGLGETSTRINGPROC glad_glGetString = NULL; +PFNGLGETSTRINGIPROC glad_glGetStringi = NULL; +PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL; +PFNGLISENABLEDPROC glad_glIsEnabled = NULL; +PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL; +PFNGLMAPBUFFERPROC glad_glMapBuffer = NULL; +PFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL; +PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL; +PFNGLREADPIXELSPROC glad_glReadPixels = NULL; +PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL; +PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL; +PFNGLSCISSORPROC glad_glScissor = NULL; +PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL; +PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL; +PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL; +PFNGLSTENCILMASKPROC glad_glStencilMask = NULL; +PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL; +PFNGLSTENCILOPPROC glad_glStencilOp = NULL; +PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL; +PFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL; +PFNGLTEXPARAMETERFPROC glad_glTexParameterf = NULL; +PFNGLTEXPARAMETERIPROC glad_glTexParameteri = NULL; +PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL; +PFNGLUNIFORM1FPROC glad_glUniform1f = NULL; +PFNGLUNIFORM1FVPROC glad_glUniform1fv = NULL; +PFNGLUNIFORM1IPROC glad_glUniform1i = NULL; +PFNGLUNIFORM2FPROC glad_glUniform2f = NULL; +PFNGLUNIFORM2FVPROC glad_glUniform2fv = NULL; +PFNGLUNIFORM3FPROC glad_glUniform3f = NULL; +PFNGLUNIFORM4FPROC glad_glUniform4f = NULL; +PFNGLUNIFORM4FVPROC glad_glUniform4fv = NULL; +PFNGLUNIFORMMATRIX4FVPROC glad_glUniformMatrix4fv = NULL; +PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL; +PFNGLUSEPROGRAMPROC glad_glUseProgram = NULL; +PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL; +PFNGLVIEWPORTPROC glad_glViewport = NULL; + +static void load_GL_VERSION_1_0(GLADloadfunc load) { + glad_glClear = (PFNGLCLEARPROC)load("glClear"); + glad_glClearColor = (PFNGLCLEARCOLORPROC)load("glClearColor"); + glad_glClearDepth = (PFNGLCLEARDEPTHPROC)load("glClearDepth"); + glad_glClearStencil = (PFNGLCLEARSTENCILPROC)load("glClearStencil"); + glad_glColorMask = (PFNGLCOLORMASKPROC)load("glColorMask"); + glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace"); + glad_glDepthFunc = (PFNGLDEPTHFUNCPROC)load("glDepthFunc"); + glad_glDepthMask = (PFNGLDEPTHMASKPROC)load("glDepthMask"); + glad_glDisable = (PFNGLDISABLEPROC)load("glDisable"); + glad_glDrawArrays = (PFNGLDRAWARRAYSPROC)load("glDrawArrays"); + glad_glDrawElements = (PFNGLDRAWELEMENTSPROC)load("glDrawElements"); + glad_glEnable = (PFNGLENABLEPROC)load("glEnable"); + glad_glFinish = (PFNGLFINISHPROC)load("glFinish"); + glad_glFlush = (PFNGLFLUSHPROC)load("glFlush"); + glad_glFrontFace = (PFNGLFRONTFACEPROC)load("glFrontFace"); + glad_glGetError = (PFNGLGETERRORPROC)load("glGetError"); + glad_glGetIntegerv = (PFNGLGETINTEGERVPROC)load("glGetIntegerv"); + glad_glGetString = (PFNGLGETSTRINGPROC)load("glGetString"); + glad_glIsEnabled = (PFNGLISENABLEDPROC)load("glIsEnabled"); + glad_glPixelStorei = (PFNGLPIXELSTOREIPROC)load("glPixelStorei"); + glad_glPolygonMode = (PFNGLPOLYGONMODEPROC)load("glPolygonMode"); + glad_glReadPixels = (PFNGLREADPIXELSPROC)load("glReadPixels"); + glad_glScissor = (PFNGLSCISSORPROC)load("glScissor"); + glad_glStencilFunc = (PFNGLSTENCILFUNCPROC)load("glStencilFunc"); + glad_glStencilMask = (PFNGLSTENCILMASKPROC)load("glStencilMask"); + glad_glStencilOp = (PFNGLSTENCILOPPROC)load("glStencilOp"); + glad_glTexImage2D = (PFNGLTEXIMAGE2DPROC)load("glTexImage2D"); + glad_glTexParameterf = (PFNGLTEXPARAMETERFPROC)load("glTexParameterf"); + glad_glTexParameteri = (PFNGLTEXPARAMETERIPROC)load("glTexParameteri"); + glad_glViewport = (PFNGLVIEWPORTPROC)load("glViewport"); +} + +static void load_GL_VERSION_1_1(GLADloadfunc load) { + glad_glBindTexture = (PFNGLBINDTEXTUREPROC)load("glBindTexture"); + glad_glDeleteTextures = (PFNGLDELETETEXTURESPROC)load("glDeleteTextures"); + glad_glGenTextures = (PFNGLGENTEXTURESPROC)load("glGenTextures"); + glad_glTexSubImage2D = (PFNGLTEXSUBIMAGE2DPROC)load("glTexSubImage2D"); +} + +static void load_GL_VERSION_1_3(GLADloadfunc load) { + glad_glActiveTexture = (PFNGLACTIVETEXTUREPROC)load("glActiveTexture"); +} + +static void load_GL_VERSION_1_4(GLADloadfunc load) { + glad_glBlendColor = (PFNGLBLENDCOLORPROC)load("glBlendColor"); + glad_glBlendEquation = (PFNGLBLENDEQUATIONPROC)load("glBlendEquation"); + glad_glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)load("glBlendFuncSeparate"); +} + +static void load_GL_VERSION_1_5(GLADloadfunc load) { + glad_glBindBuffer = (PFNGLBINDBUFFERPROC)load("glBindBuffer"); + glad_glBufferData = (PFNGLBUFFERDATAPROC)load("glBufferData"); + glad_glBufferSubData = (PFNGLBUFFERSUBDATAPROC)load("glBufferSubData"); + glad_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)load("glDeleteBuffers"); + glad_glGenBuffers = (PFNGLGENBUFFERSPROC)load("glGenBuffers"); + glad_glMapBuffer = (PFNGLMAPBUFFERPROC)load("glMapBuffer"); + glad_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)load("glUnmapBuffer"); +} + +static void load_GL_VERSION_2_0(GLADloadfunc load) { + glad_glAttachShader = (PFNGLATTACHSHADERPROC)load("glAttachShader"); + glad_glBlendEquationSeparate = (PFNGLBLENDEQUATIONSEPARATEPROC)load("glBlendEquationSeparate"); + glad_glBlendFunc = (PFNGLBLENDFUNCPROC)load("glBlendFunc"); + glad_glCompileShader = (PFNGLCOMPILESHADERPROC)load("glCompileShader"); + glad_glCreateProgram = (PFNGLCREATEPROGRAMPROC)load("glCreateProgram"); + glad_glCreateShader = (PFNGLCREATESHADERPROC)load("glCreateShader"); + glad_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)load("glDeleteProgram"); + glad_glDeleteShader = (PFNGLDELETESHADERPROC)load("glDeleteShader"); + glad_glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)load("glDisableVertexAttribArray"); + glad_glDrawBuffers = (PFNGLDRAWBUFFERSPROC)load("glDrawBuffers"); + glad_glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)load("glEnableVertexAttribArray"); + glad_glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)load("glGetAttribLocation"); + glad_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)load("glGetProgramInfoLog"); + glad_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)load("glGetProgramiv"); + glad_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)load("glGetShaderInfoLog"); + glad_glGetShaderiv = (PFNGLGETSHADERIVPROC)load("glGetShaderiv"); + glad_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)load("glGetUniformLocation"); + glad_glLinkProgram = (PFNGLLINKPROGRAMPROC)load("glLinkProgram"); + glad_glShaderSource = (PFNGLSHADERSOURCEPROC)load("glShaderSource"); + glad_glStencilFuncSeparate = (PFNGLSTENCILFUNCSEPARATEPROC)load("glStencilFuncSeparate"); + glad_glStencilMaskSeparate = (PFNGLSTENCILMASKSEPARATEPROC)load("glStencilMaskSeparate"); + glad_glStencilOpSeparate = (PFNGLSTENCILOPSEPARATEPROC)load("glStencilOpSeparate"); + glad_glUniform1f = (PFNGLUNIFORM1FPROC)load("glUniform1f"); + glad_glUniform1fv = (PFNGLUNIFORM1FVPROC)load("glUniform1fv"); + glad_glUniform1i = (PFNGLUNIFORM1IPROC)load("glUniform1i"); + glad_glUniform2f = (PFNGLUNIFORM2FPROC)load("glUniform2f"); + glad_glUniform2fv = (PFNGLUNIFORM2FVPROC)load("glUniform2fv"); + glad_glUniform3f = (PFNGLUNIFORM3FPROC)load("glUniform3f"); + glad_glUniform4f = (PFNGLUNIFORM4FPROC)load("glUniform4f"); + glad_glUniform4fv = (PFNGLUNIFORM4FVPROC)load("glUniform4fv"); + glad_glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)load("glUniformMatrix4fv"); + glad_glUseProgram = (PFNGLUSEPROGRAMPROC)load("glUseProgram"); + glad_glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)load("glVertexAttribPointer"); +} + +static void load_GL_VERSION_3_0(GLADloadfunc load) { + glad_glBindBufferBase = (PFNGLBINDBUFFERBASEPROC)load("glBindBufferBase"); + glad_glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)load("glBindFramebuffer"); + glad_glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)load("glBindRenderbuffer"); + glad_glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)load("glBindVertexArray"); + glad_glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)load("glBlitFramebuffer"); + glad_glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)load("glCheckFramebufferStatus"); + glad_glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)load("glDeleteFramebuffers"); + glad_glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)load("glDeleteRenderbuffers"); + glad_glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)load("glDeleteVertexArrays"); + glad_glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)load("glFramebufferRenderbuffer"); + glad_glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)load("glFramebufferTexture2D"); + glad_glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)load("glGenFramebuffers"); + glad_glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)load("glGenRenderbuffers"); + glad_glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)load("glGenVertexArrays"); + glad_glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)load("glGenerateMipmap"); + glad_glGetStringi = (PFNGLGETSTRINGIPROC)load("glGetStringi"); + glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)load("glRenderbufferStorage"); + glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)load("glRenderbufferStorageMultisample"); +} + +int gladLoadGL(GLADloadfunc load) { + load_GL_VERSION_1_0(load); + load_GL_VERSION_1_1(load); + load_GL_VERSION_1_3(load); + load_GL_VERSION_1_4(load); + load_GL_VERSION_1_5(load); + load_GL_VERSION_2_0(load); + load_GL_VERSION_3_0(load); + return GLAD_MAKE_VERSION(3, 3); +} + +int gladLoadGLUserPtr(GLADloadfunc load, void *userptr) { + (void)userptr; + return gladLoadGL(load); +} diff --git a/designer/src/data_models.cpp b/designer/src/data_models.cpp new file mode 100644 index 0000000..6564309 --- /dev/null +++ b/designer/src/data_models.cpp @@ -0,0 +1,276 @@ +// Data models implementation +#include "data_models.h" +#include + +// Global data instances +SettingsData g_settings; +PhoneData g_phone; +BrowserData g_browser; +std::vector g_conversations; +std::vector g_contacts; +int g_selected_conversation = -1; +int g_selected_contact = -1; + +// Data model handles +Rml::DataModelHandle g_settings_model; +Rml::DataModelHandle g_phone_model; +Rml::DataModelHandle g_messages_model; +Rml::DataModelHandle g_contacts_model; +Rml::DataModelHandle g_browser_model; + +void initializeSampleData() +{ + // Initialize contacts + g_contacts = { + {1, "Alice Johnson", "+1 (555) 123-4567", "alice@example.com", "#E91E63", "A"}, + {2, "Andrew Smith", "+1 (555) 234-5678", "andrew@example.com", "#9C27B0", "A"}, + {3, "Bob Williams", "+1 (555) 345-6789", "bob@example.com", "#2196F3", "B"}, + {4, "Brian Davis", "+1 (555) 456-7890", "brian@example.com", "#00BCD4", "B"}, + {5, "Carol Martinez", "+1 (555) 567-8901", "carol@example.com", "#4CAF50", "C"}, + {6, "David Lee", "+1 (555) 678-9012", "david@example.com", "#FF9800", "D"}, + {7, "John Wilson", "+1 (555) 789-0123", "john@example.com", "#F44336", "J"}, + {8, "Mom", "+1 (555) 890-1234", "mom@example.com", "#673AB7", "M"}, + {9, "Mike Brown", "+1 (555) 901-2345", "mike@example.com", "#3F51B5", "M"}, + {10, "Sarah Taylor", "+1 (555) 012-3456", "sarah@example.com", "#009688", "S"} + }; + + // Initialize conversations + g_conversations = { + {1, "John Wilson", "#4CAF50", "Hey, are you coming to the party tonight?", "2:30 PM", 2, { + {"them", "Hey!", "2:25 PM"}, + {"them", "What are you up to?", "2:26 PM"}, + {"me", "Not much, just working", "2:27 PM"}, + {"them", "Cool! There's a party at Mike's tonight", "2:28 PM"}, + {"them", "Hey, are you coming to the party tonight?", "2:30 PM"} + }}, + {2, "Mom", "#673AB7", "Don't forget to call your grandmother!", "1:15 PM", 0, { + {"them", "Hi sweetie!", "1:00 PM"}, + {"me", "Hi Mom!", "1:05 PM"}, + {"them", "How are you doing?", "1:10 PM"}, + {"me", "I'm good, how are you?", "1:12 PM"}, + {"them", "Don't forget to call your grandmother!", "1:15 PM"} + }}, + {3, "Alice Johnson", "#E91E63", "Thanks for the help with the project!", "Yesterday", 0, { + {"me", "Here's the file you needed", "Yesterday"}, + {"them", "Thanks for the help with the project!", "Yesterday"} + }}, + {4, "Bob Williams", "#2196F3", "Did you see the game last night?", "Yesterday", 0, { + {"them", "Did you see the game last night?", "Yesterday"} + }}, + {5, "Work Group", "#FF9800", "Sarah: Meeting moved to 3pm", "Mon", 0, { + {"Sarah", "Meeting moved to 3pm", "Mon"} + }}, + {6, "Sarah Taylor", "#009688", "See you at the coffee shop!", "Sun", 0, { + {"them", "See you at the coffee shop!", "Sun"} + }}, + {7, "David Lee", "#F44336", "Great talking to you!", "Sat", 0, { + {"them", "Great talking to you!", "Sat"} + }} + }; + + // Initialize browser tabs + g_browser.tabs = { + {"example.com", "Example Domain", false}, + {"rmlui.github.io", "RmlUi Documentation", false}, + {"github.com", "GitHub", false} + }; + + std::cout << "Sample data initialized: " << g_contacts.size() << " contacts, " + << g_conversations.size() << " conversations, " + << g_browser.tabs.size() << " browser tabs" << std::endl; +} + +void setupDataModels(Rml::Context* context) +{ + // Messages data model + if (auto constructor = context->CreateDataModel("messages")) + { + if (auto msg_handle = constructor.RegisterStruct()) + { + msg_handle.RegisterMember("from", &Message::from); + msg_handle.RegisterMember("text", &Message::text); + msg_handle.RegisterMember("time", &Message::time); + } + + if (auto conv_handle = constructor.RegisterStruct()) + { + conv_handle.RegisterMember("id", &Conversation::id); + conv_handle.RegisterMember("name", &Conversation::name); + conv_handle.RegisterMember("color", &Conversation::color); + conv_handle.RegisterMember("last_message", &Conversation::last_message); + conv_handle.RegisterMember("time", &Conversation::time); + conv_handle.RegisterMember("unread", &Conversation::unread); + conv_handle.RegisterMember("messages", &Conversation::messages); + } + + constructor.RegisterArray>(); + constructor.RegisterArray>(); + + constructor.Bind("conversations", &g_conversations); + constructor.Bind("selected_conversation", &g_selected_conversation); + + constructor.BindEventCallback("select_conversation", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + if (!args.empty()) { + g_selected_conversation = args[0].Get(); + std::cout << "Selected conversation: " << g_selected_conversation << std::endl; + handle.DirtyVariable("selected_conversation"); + } + }); + + g_messages_model = constructor.GetModelHandle(); + std::cout << "Messages data model created" << std::endl; + } + + // Contacts data model + if (auto constructor = context->CreateDataModel("contacts")) + { + if (auto contact_handle = constructor.RegisterStruct()) + { + contact_handle.RegisterMember("id", &Contact::id); + contact_handle.RegisterMember("name", &Contact::name); + contact_handle.RegisterMember("phone", &Contact::phone); + contact_handle.RegisterMember("email", &Contact::email); + contact_handle.RegisterMember("color", &Contact::color); + contact_handle.RegisterMember("initial", &Contact::initial); + } + + constructor.RegisterArray>(); + + constructor.Bind("contacts", &g_contacts); + constructor.Bind("selected_contact", &g_selected_contact); + + constructor.BindEventCallback("select_contact", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + if (!args.empty()) { + g_selected_contact = args[0].Get(); + std::cout << "Selected contact: " << g_selected_contact << std::endl; + handle.DirtyVariable("selected_contact"); + } + }); + + g_contacts_model = constructor.GetModelHandle(); + std::cout << "Contacts data model created" << std::endl; + } + + // Settings data model + if (auto constructor = context->CreateDataModel("settings")) + { + constructor.Bind("wifi", &g_settings.wifi); + constructor.Bind("bluetooth", &g_settings.bluetooth); + constructor.Bind("airplane_mode", &g_settings.airplane_mode); + constructor.Bind("location", &g_settings.location); + constructor.Bind("dark_mode", &g_settings.dark_mode); + constructor.Bind("notifications", &g_settings.notifications); + constructor.Bind("do_not_disturb", &g_settings.do_not_disturb); + constructor.Bind("user_name", &g_settings.user_name); + constructor.Bind("user_email", &g_settings.user_email); + constructor.Bind("wifi_network", &g_settings.wifi_network); + constructor.Bind("battery_percent", &g_settings.battery_percent); + constructor.Bind("battery_remaining", &g_settings.battery_remaining); + constructor.Bind("storage_used", &g_settings.storage_used); + + g_settings_model = constructor.GetModelHandle(); + std::cout << "Settings data model created" << std::endl; + } + + // Phone data model + if (auto constructor = context->CreateDataModel("phone")) + { + constructor.Bind("dial_number", &g_phone.dial_number); + constructor.Bind("is_calling", &g_phone.is_calling); + constructor.Bind("call_contact", &g_phone.call_contact); + constructor.Bind("call_duration", &g_phone.call_duration); + + constructor.BindEventCallback("dial_press", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + if (!args.empty()) { + g_phone.dial_number += args[0].Get(); + std::cout << "Dial: " << g_phone.dial_number << std::endl; + handle.DirtyVariable("dial_number"); + } + }); + + constructor.BindEventCallback("dial_backspace", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + if (!g_phone.dial_number.empty()) { + g_phone.dial_number.pop_back(); + handle.DirtyVariable("dial_number"); + } + }); + + constructor.BindEventCallback("dial_clear", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + g_phone.dial_number.clear(); + handle.DirtyVariable("dial_number"); + }); + + constructor.BindEventCallback("make_call", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + if (!g_phone.dial_number.empty()) { + g_phone.is_calling = true; + g_phone.call_contact = g_phone.dial_number; + std::cout << "Calling: " << g_phone.call_contact << std::endl; + handle.DirtyVariable("is_calling"); + handle.DirtyVariable("call_contact"); + } + }); + + constructor.BindEventCallback("end_call", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + g_phone.is_calling = false; + g_phone.call_contact = ""; + g_phone.call_duration = "00:00"; + handle.DirtyVariable("is_calling"); + handle.DirtyVariable("call_contact"); + handle.DirtyVariable("call_duration"); + }); + + g_phone_model = constructor.GetModelHandle(); + std::cout << "Phone data model created" << std::endl; + } + + // Browser data model + if (auto constructor = context->CreateDataModel("browser")) + { + if (auto tab_handle = constructor.RegisterStruct()) + { + tab_handle.RegisterMember("url", &BrowserTab::url); + tab_handle.RegisterMember("title", &BrowserTab::title); + tab_handle.RegisterMember("is_loading", &BrowserTab::is_loading); + } + + constructor.RegisterArray>(); + + constructor.Bind("current_url", &g_browser.current_url); + constructor.Bind("page_title", &g_browser.page_title); + constructor.Bind("page_content", &g_browser.page_content); + constructor.Bind("tabs", &g_browser.tabs); + constructor.Bind("current_tab", &g_browser.current_tab); + constructor.Bind("can_go_back", &g_browser.can_go_back); + constructor.Bind("can_go_forward", &g_browser.can_go_forward); + + constructor.BindEventCallback("navigate", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + if (!args.empty()) { + g_browser.current_url = args[0].Get(); + g_browser.page_title = "Loading..."; + g_browser.can_go_back = true; + std::cout << "Navigating to: " << g_browser.current_url << std::endl; + handle.DirtyVariable("current_url"); + handle.DirtyVariable("page_title"); + handle.DirtyVariable("can_go_back"); + } + }); + + constructor.BindEventCallback("refresh", + [](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) { + std::cout << "Refreshing page" << std::endl; + }); + + g_browser_model = constructor.GetModelHandle(); + std::cout << "Browser data model created" << std::endl; + } + + std::cout << "All data models initialized" << std::endl; +} diff --git a/designer/src/data_models.h b/designer/src/data_models.h new file mode 100644 index 0000000..c1d54a1 --- /dev/null +++ b/designer/src/data_models.h @@ -0,0 +1,99 @@ +// Data models for Mosis phone UI +#pragma once + +#include +#include +#include + +// Settings data model +struct SettingsData { + bool wifi = true; + bool bluetooth = false; + bool airplane_mode = false; + bool location = true; + bool dark_mode = true; + bool notifications = true; + bool do_not_disturb = false; + + // User profile + Rml::String user_name = "User Name"; + Rml::String user_email = "user@example.com"; + + // Device info + Rml::String wifi_network = "Home_Network"; + int battery_percent = 85; + Rml::String battery_remaining = "About 12h remaining"; + Rml::String storage_used = "45.2 GB used of 128 GB"; +}; + +// Phone state data model +struct PhoneData { + Rml::String dial_number = ""; + bool is_calling = false; + Rml::String call_contact = ""; + Rml::String call_duration = "00:00"; +}; + +// Messages data model +struct Message { + Rml::String from; + Rml::String text; + Rml::String time; +}; + +struct Conversation { + int id; + Rml::String name; + Rml::String color; + Rml::String last_message; + Rml::String time; + int unread; + std::vector messages; +}; + +// Contact data model +struct Contact { + int id; + Rml::String name; + Rml::String phone; + Rml::String email; + Rml::String color; + Rml::String initial; +}; + +// Browser data model +struct BrowserTab { + Rml::String url; + Rml::String title; + bool is_loading; +}; + +struct BrowserData { + Rml::String current_url = "example.com"; + Rml::String page_title = "Example Domain"; + Rml::String page_content = "This domain is for use in illustrative examples."; + std::vector tabs; + int current_tab = 0; + bool can_go_back = false; + bool can_go_forward = false; +}; + +// Global data instances +extern SettingsData g_settings; +extern PhoneData g_phone; +extern BrowserData g_browser; +extern std::vector g_conversations; +extern std::vector g_contacts; +extern int g_selected_conversation; +extern int g_selected_contact; + +// Data model handles +extern Rml::DataModelHandle g_settings_model; +extern Rml::DataModelHandle g_phone_model; +extern Rml::DataModelHandle g_messages_model; +extern Rml::DataModelHandle g_contacts_model; +extern Rml::DataModelHandle g_browser_model; + +// Functions +void initializeSampleData(); +void setupDataModels(Rml::Context* context); diff --git a/designer/src/desktop_platform.cpp b/designer/src/desktop_platform.cpp new file mode 100644 index 0000000..fafb20d --- /dev/null +++ b/designer/src/desktop_platform.cpp @@ -0,0 +1,89 @@ +// Desktop platform implementation (simplified - uses RmlUi backend for graphics) +#include "desktop_platform.h" +#include +#include + +// Note: Graphics context and rendering is handled by RmlUi's backend. +// This platform implementation provides additional utilities and state management. + +namespace mosis::desktop { + +DesktopPlatform::DesktopPlatform() + : m_file_interface(std::make_unique()) +{ + auto now = std::chrono::steady_clock::now(); + m_start_time = std::chrono::duration(now.time_since_epoch()).count(); +} + +DesktopPlatform::~DesktopPlatform() = default; + +bool DesktopPlatform::Initialize(uint32_t width, uint32_t height, const char* title) { + m_width = width; + m_height = height; + // Graphics initialization is done by RmlUi Backend + return true; +} + +void DesktopPlatform::Shutdown() { + // Graphics shutdown is done by RmlUi Backend +} + +std::unique_ptr DesktopPlatform::CreateGraphicsContext() { + // Graphics context is managed by RmlUi Backend + return nullptr; +} + +std::unique_ptr DesktopPlatform::CreateRenderTarget(uint32_t width, uint32_t height) { + // Render targets are managed by RmlUi Backend + return nullptr; +} + +IFileInterface& DesktopPlatform::GetFileInterface() { + return *m_file_interface; +} + +void DesktopPlatform::Log(const std::string& message) { + std::cout << "[INFO] " << message << std::endl; +} + +void DesktopPlatform::LogError(const std::string& message) { + std::cerr << "[ERROR] " << message << std::endl; +} + +bool DesktopPlatform::PollEvents() { + // Events are handled by RmlUi Backend + return true; +} + +void DesktopPlatform::SwapBuffers() { + // Swap is handled by RmlUi Backend +} + +bool DesktopPlatform::ShouldClose() const { + return false; // Determined by RmlUi Backend +} + +void DesktopPlatform::SetResolution(uint32_t width, uint32_t height) { + m_width = width; + m_height = height; +} + +float DesktopPlatform::GetDpiScale() const { + return 1.0f; +} + +double DesktopPlatform::GetElapsedTime() const { + auto now = std::chrono::steady_clock::now(); + double current = std::chrono::duration(now.time_since_epoch()).count(); + return current - m_start_time; +} + +bool DesktopPlatform::IsMouseButtonDown() const { + return false; // Input is handled through RmlUi +} + +void DesktopPlatform::GetMousePosition(double& x, double& y) const { + x = y = 0; // Input is handled through RmlUi +} + +} // namespace mosis::desktop diff --git a/designer/src/desktop_platform.h b/designer/src/desktop_platform.h new file mode 100644 index 0000000..3c25716 --- /dev/null +++ b/designer/src/desktop_platform.h @@ -0,0 +1,50 @@ +// Desktop platform implementation using GLFW + OpenGL 3.3 +// Note: Graphics context and rendering is handled by RmlUi's backend. +// This platform implementation provides file interface and utilities. +#pragma once + +#include "platform.h" +#include "file_interface.h" +#include + +namespace mosis::desktop { + +class DesktopPlatform : public IPlatform { + uint32_t m_width = 540; + uint32_t m_height = 960; + std::unique_ptr m_file_interface; + double m_start_time = 0.0; + +public: + DesktopPlatform(); + ~DesktopPlatform() override; + + // Initialize the platform + bool Initialize(uint32_t width, uint32_t height, const char* title); + void Shutdown(); + + // IPlatform implementation + std::unique_ptr CreateGraphicsContext() override; + std::unique_ptr CreateRenderTarget(uint32_t width, uint32_t height) override; + IFileInterface& GetFileInterface() override; + + void Log(const std::string& message) override; + void LogError(const std::string& message) override; + + bool PollEvents() override; + void SwapBuffers() override; + bool ShouldClose() const override; + + uint32_t GetWidth() const override { return m_width; } + uint32_t GetHeight() const override { return m_height; } + void SetResolution(uint32_t width, uint32_t height) override; + float GetDpiScale() const override; + + double GetElapsedTime() const override; + + // Input state (delegated to RmlUi backend) + bool IsMouseButtonDown() const; + void GetMousePosition(double& x, double& y) const; +}; + +} // namespace mosis::desktop diff --git a/designer/src/hot_reload.cpp b/designer/src/hot_reload.cpp new file mode 100644 index 0000000..c0a497d --- /dev/null +++ b/designer/src/hot_reload.cpp @@ -0,0 +1,93 @@ +// Hot-reload file watcher implementation +#include "hot_reload.h" +#include + +#ifdef _WIN32 +#include +#endif + +namespace mosis::desktop { + +HotReload::HotReload(const std::filesystem::path& watch_path, ReloadCallback callback) + : m_watch_path(watch_path) + , m_callback(std::move(callback)) + , m_last_change(std::chrono::steady_clock::now()) +{} + +HotReload::~HotReload() { + Stop(); +} + +void HotReload::Start() { + if (m_running) return; + +#ifdef _WIN32 + m_notification_handle = FindFirstChangeNotificationW( + m_watch_path.c_str(), + TRUE, // Watch subtree + FILE_NOTIFY_CHANGE_LAST_WRITE + ); + + if (m_notification_handle == INVALID_HANDLE_VALUE) { + std::cerr << "Failed to create file change notification for: " + << m_watch_path << std::endl; + return; + } +#endif + + m_running = true; + m_watch_thread = std::thread(&HotReload::WatchThread, this); +} + +void HotReload::Stop() { + m_running = false; + + if (m_watch_thread.joinable()) { + m_watch_thread.join(); + } + +#ifdef _WIN32 + if (m_notification_handle && m_notification_handle != INVALID_HANDLE_VALUE) { + FindCloseChangeNotification(m_notification_handle); + m_notification_handle = nullptr; + } +#endif +} + +bool HotReload::CheckForChanges() { + if (m_change_detected) { + auto now = std::chrono::steady_clock::now(); + if (now - m_last_change >= m_debounce_delay) { + m_change_detected = false; + if (m_callback) { + m_callback(); + } + return true; + } + } + return false; +} + +void HotReload::WatchThread() { + while (m_running) { +#ifdef _WIN32 + DWORD result = WaitForSingleObject(m_notification_handle, 100); // 100ms timeout + if (result == WAIT_OBJECT_0) { + m_last_change = std::chrono::steady_clock::now(); + m_change_detected = true; + + // Reset the notification + if (!FindNextChangeNotification(m_notification_handle)) { + std::cerr << "FindNextChangeNotification failed" << std::endl; + break; + } + } +#else + // TODO: Linux inotify / macOS FSEvents implementation + // For now, just sleep to avoid busy loop + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +#endif + } +} + +} // namespace mosis::desktop diff --git a/designer/src/hot_reload.h b/designer/src/hot_reload.h new file mode 100644 index 0000000..5e90438 --- /dev/null +++ b/designer/src/hot_reload.h @@ -0,0 +1,44 @@ +// Hot-reload file watcher for development +#pragma once + +#include +#include +#include +#include +#include + +namespace mosis::desktop { + +class HotReload { +public: + using ReloadCallback = std::function; + + HotReload(const std::filesystem::path& watch_path, ReloadCallback callback); + ~HotReload(); + + void Start(); + void Stop(); + bool IsRunning() const { return m_running; } + + // Check for changes (call from main thread if not using threaded mode) + bool CheckForChanges(); + +private: + void WatchThread(); + + std::filesystem::path m_watch_path; + ReloadCallback m_callback; + std::thread m_watch_thread; + std::atomic m_running{false}; + std::atomic m_change_detected{false}; + +#ifdef _WIN32 + void* m_notification_handle = nullptr; // HANDLE +#endif + + // Debounce settings + std::chrono::milliseconds m_debounce_delay{100}; + std::chrono::steady_clock::time_point m_last_change; +}; + +} // namespace mosis::desktop diff --git a/designer/src/kernel_impl.cpp b/designer/src/kernel_impl.cpp new file mode 100644 index 0000000..af33b07 --- /dev/null +++ b/designer/src/kernel_impl.cpp @@ -0,0 +1,330 @@ +// Desktop kernel implementation +#include "kernel_impl.h" +#include "platform.h" +#include "file_interface.h" +#include "log.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace mosis { + +// Global kernel instance for Lua access +static DesktopKernel* g_kernel_instance = nullptr; + +// Implementation class +class DesktopKernel::Impl { +public: + KernelConfig config; + Rml::Context* context = nullptr; + Rml::ElementDocument* document = nullptr; + std::vector> listeners; + std::vector> tasks; + std::mutex mutex; + std::atomic running{false}; + std::atomic reload_requested{false}; + std::string current_document_path; + + // Status bar time + std::string status_time; + Rml::DataModelHandle status_time_handle; + + explicit Impl(const KernelConfig& cfg) : config(cfg) {} +}; + +DesktopKernel::DesktopKernel(const KernelConfig& config) + : m_impl(std::make_unique(config)) +{ + g_kernel_instance = this; +} + +DesktopKernel::~DesktopKernel() { + Stop(); + g_kernel_instance = nullptr; +} + +void DesktopKernel::AddListener(std::shared_ptr listener) { + std::lock_guard lock(m_impl->mutex); + m_impl->listeners.push_back(listener); + if (m_impl->running) { + listener->OnServiceInitialized(true); + } +} + +void DesktopKernel::RemoveListener(std::shared_ptr listener) { + std::lock_guard lock(m_impl->mutex); + m_impl->listeners.erase( + std::remove(m_impl->listeners.begin(), m_impl->listeners.end(), listener), + m_impl->listeners.end() + ); +} + +void DesktopKernel::OnTouchDown(float x, float y) { + std::lock_guard lock(m_impl->mutex); + auto px = static_cast(x * m_impl->config.width); + auto py = static_cast(y * m_impl->config.height); + m_impl->tasks.emplace_back([this, px, py]() { + if (m_impl->context) { + m_impl->context->ProcessMouseMove(px, py, 0); + m_impl->context->ProcessMouseButtonDown(0, 0); + } + }); +} + +void DesktopKernel::OnTouchMove(float x, float y) { + std::lock_guard lock(m_impl->mutex); + auto px = static_cast(x * m_impl->config.width); + auto py = static_cast(y * m_impl->config.height); + m_impl->tasks.emplace_back([this, px, py]() { + if (m_impl->context) { + m_impl->context->ProcessMouseMove(px, py, 0); + } + }); +} + +void DesktopKernel::OnTouchUp(float x, float y) { + std::lock_guard lock(m_impl->mutex); + auto px = static_cast(x * m_impl->config.width); + auto py = static_cast(y * m_impl->config.height); + m_impl->tasks.emplace_back([this, px, py]() { + if (m_impl->context) { + m_impl->context->ProcessMouseMove(px, py, 0); + m_impl->context->ProcessMouseButtonUp(0, 0); + } + }); +} + +void DesktopKernel::OnBackButton() { + std::lock_guard lock(m_impl->mutex); + m_impl->tasks.emplace_back([this]() { + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_getglobal(L, "goBack"); + if (lua_isfunction(L, -1)) { + lua_pcall(L, 0, 0, 0); + } else { + lua_pop(L, 1); + } + }); +} + +void DesktopKernel::OnHomeButton() { + std::lock_guard lock(m_impl->mutex); + m_impl->tasks.emplace_back([this]() { + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_getglobal(L, "goHome"); + if (lua_isfunction(L, -1)) { + lua_pcall(L, 0, 0, 0); + } else { + lua_pop(L, 1); + } + }); +} + +void DesktopKernel::OnRecentsButton() { + std::cout << "Recents button pressed (not implemented)" << std::endl; +} + +void DesktopKernel::RequestReload() { + m_impl->reload_requested = true; +} + +void DesktopKernel::LoadDocument(const std::string& path) { + std::lock_guard lock(m_impl->mutex); + m_impl->tasks.emplace_back([this, path]() { + auto& file_interface = GetPlatform().GetFileInterface(); + std::string full_path = file_interface.ResolvePath(path); + + if (m_impl->document) { + m_impl->context->UnloadDocument(m_impl->document); + m_impl->document = nullptr; + } + + m_impl->document = m_impl->context->LoadDocument(full_path); + if (m_impl->document) { + m_impl->document->Show(); + m_impl->current_document_path = full_path; + std::cout << "Loaded document: " << path << std::endl; + } else { + std::cerr << "Failed to load document: " << path << std::endl; + } + }); +} + +void DesktopKernel::Start() { + if (m_impl->running) return; + m_impl->running = true; + + std::lock_guard lock(m_impl->mutex); + for (auto& listener : m_impl->listeners) { + listener->OnServiceInitialized(true); + } +} + +void DesktopKernel::Stop() { + m_impl->running = false; +} + +bool DesktopKernel::IsRunning() const { + return m_impl->running; +} + +void DesktopKernel::Update() { + if (!m_impl->running || !m_impl->context) return; + + // Process pending tasks + { + std::lock_guard lock(m_impl->mutex); + for (const auto& task : m_impl->tasks) { + task(); + } + m_impl->tasks.clear(); + } + + // Handle reload request + if (m_impl->reload_requested) { + m_impl->reload_requested = false; + + if (!m_impl->current_document_path.empty()) { + std::cout << "Reloading document..." << std::endl; + + if (m_impl->document) { + m_impl->context->UnloadDocument(m_impl->document); + m_impl->document = nullptr; + } + + m_impl->context->Update(); + + m_impl->document = m_impl->context->LoadDocument(m_impl->current_document_path); + if (m_impl->document) { + m_impl->document->ReloadStyleSheet(); + m_impl->document->Show(); + std::cout << "Document reloaded" << std::endl; + } + } + } + + // Update status bar time + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::tm tm{}; +#ifdef _WIN32 + localtime_s(&tm, &time_t); +#else + localtime_r(&time_t, &tm); +#endif + char buffer[16]; + std::strftime(buffer, sizeof(buffer), "%H:%M", &tm); + m_impl->status_time = buffer; + + if (m_impl->status_time_handle) { + m_impl->status_time_handle.DirtyVariable("time"); + } + + m_impl->context->Update(); +} + +void DesktopKernel::Render() { + if (!m_impl->running || !m_impl->context) return; + m_impl->context->Render(); + + std::lock_guard lock(m_impl->mutex); + for (auto& listener : m_impl->listeners) { + listener->OnFrameAvailable(); + } +} + +void DesktopKernel::SetContext(Rml::Context* context) { + m_impl->context = context; + + // Setup status data model + if (m_impl->context) { + if (auto constructor = m_impl->context->CreateDataModel("status-data")) { + constructor.Bind("time", &m_impl->status_time); + m_impl->status_time_handle = constructor.GetModelHandle(); + } + } +} + +Rml::Context* DesktopKernel::GetContext() const { + return m_impl->context; +} + +void DesktopKernel::SetDocument(Rml::ElementDocument* doc) { + m_impl->document = doc; +} + +Rml::ElementDocument* DesktopKernel::GetDocument() const { + return m_impl->document; +} + +void DesktopKernel::SetCurrentDocumentPath(const std::string& path) { + m_impl->current_document_path = path; +} + +int DesktopKernel::LuaLoadScreen(lua_State* L) { + if (!g_kernel_instance) { + lua_pushboolean(L, false); + return 1; + } + + const char* path = luaL_checkstring(L, 1); + Log(std::string("Loading screen: ") + path); + + auto& file_interface = GetPlatform().GetFileInterface(); + std::string full_path = file_interface.ResolvePath(path); + + if (!file_interface.FileExists(full_path)) { + Log(std::string("Screen not found: ") + full_path); + lua_pushboolean(L, false); + return 1; + } + + auto* impl = g_kernel_instance->m_impl.get(); + + // Unload current document + if (impl->document) { + impl->context->UnloadDocument(impl->document); + impl->document = nullptr; + } + + // Load new document + impl->document = impl->context->LoadDocument(full_path); + if (impl->document) { + impl->document->Show(); + impl->current_document_path = full_path; + Log(std::string("Loaded screen: ") + path); + lua_pushboolean(L, true); + } else { + Log(std::string("Failed to load screen: ") + path); + lua_pushboolean(L, false); + } + + return 1; +} + +void DesktopKernel::RegisterLuaFunctions() { + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_pushcfunction(L, LuaLoadScreen); + lua_setglobal(L, "loadScreen"); + std::cout << "Registered Lua loadScreen function" << std::endl; +} + +// Factory function +std::unique_ptr CreateKernel(const KernelConfig& config) { + return std::make_unique(config); +} + +} // namespace mosis diff --git a/designer/src/kernel_impl.h b/designer/src/kernel_impl.h new file mode 100644 index 0000000..1163e28 --- /dev/null +++ b/designer/src/kernel_impl.h @@ -0,0 +1,57 @@ +// Desktop kernel header +#pragma once + +#include "service_interface.h" +#include +#include +#include + +// Forward declarations +namespace Rml { +class Context; +class ElementDocument; +} +struct lua_State; + +namespace mosis { + +// Desktop kernel implementation +class DesktopKernel : public IKernel { +public: + explicit DesktopKernel(const KernelConfig& config); + ~DesktopKernel() override; + + // IKernel implementation + void AddListener(std::shared_ptr listener) override; + void RemoveListener(std::shared_ptr listener) override; + void OnTouchDown(float x, float y) override; + void OnTouchMove(float x, float y) override; + void OnTouchUp(float x, float y) override; + void OnBackButton() override; + void OnHomeButton() override; + void OnRecentsButton() override; + void RequestReload() override; + void LoadDocument(const std::string& path) override; + void Start() override; + void Stop() override; + bool IsRunning() const override; + void Update() override; + void Render() override; + + // Desktop-specific methods + void SetContext(Rml::Context* context); + Rml::Context* GetContext() const; + void SetDocument(Rml::ElementDocument* doc); + Rml::ElementDocument* GetDocument() const; + void SetCurrentDocumentPath(const std::string& path); + + // Static Lua function registration + static int LuaLoadScreen(lua_State* L); + static void RegisterLuaFunctions(); + +private: + class Impl; + std::unique_ptr m_impl; +}; + +} // namespace mosis diff --git a/designer/src/log.h b/designer/src/log.h new file mode 100644 index 0000000..e8d4d5c --- /dev/null +++ b/designer/src/log.h @@ -0,0 +1,22 @@ +// Logging utility for Mosis Designer +#pragma once + +#include +#include +#include + +namespace mosis { + +// Global log file - defined in main.cpp +extern std::ofstream* g_log_file_ptr; + +// Log function that writes to both stdout and file +inline void Log(const std::string& message) { + std::cout << message << std::endl; + if (g_log_file_ptr && g_log_file_ptr->is_open()) { + *g_log_file_ptr << message << std::endl; + g_log_file_ptr->flush(); + } +} + +} // namespace mosis diff --git a/designer/src/main.cpp b/designer/src/main.cpp new file mode 100644 index 0000000..33f87bb --- /dev/null +++ b/designer/src/main.cpp @@ -0,0 +1,379 @@ +// Mosis Designer - Desktop designer and testing tool for Mosis virtual phone UI +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "platform.h" +#include "file_interface.h" +#include "service_interface.h" +#include "kernel_impl.h" +#include "data_models.h" +#include "hot_reload.h" +#include "desktop_platform.h" +#include "testing/ui_inspector.h" + +// Command-line options +struct Options { + std::string document_path; + uint32_t width = 540; + uint32_t height = 960; + bool dump_mode = false; + bool debug_mode = false; + std::string output_dir = "dump"; + std::string log_file; // If set, write logs to this file + std::string hierarchy_file; // If set, dump UI hierarchy to this file each frame +}; + +// Global log file stream +static std::ofstream g_log_file; + +// Pointer for shared logging (used by kernel_impl.cpp via log.h) +namespace mosis { + std::ofstream* g_log_file_ptr = nullptr; +} + +// Log function that writes to both stdout and file +void LogMessage(const std::string& message) { + std::cout << message << std::endl; + if (g_log_file.is_open()) { + g_log_file << message << std::endl; + g_log_file.flush(); + } +} + +// Forward declarations +void PrintUsage(const char* program); +Options ParseOptions(int argc, const char* argv[]); +void LoadFonts(const std::filesystem::path& fonts_path); +std::filesystem::path FindAssetsPath(const std::filesystem::path& start_path); + +// Custom system interface with logging +class DesignerSystemInterface : public Rml::SystemInterface { + Rml::SystemInterface* m_backend_interface; + +public: + explicit DesignerSystemInterface(Rml::SystemInterface* backend) + : m_backend_interface(backend) {} + + double GetElapsedTime() override { + return m_backend_interface ? m_backend_interface->GetElapsedTime() : 0.0; + } + + bool LogMessage(Rml::Log::Type type, const Rml::String& message) override { + const char* type_str = "INFO"; + switch (type) { + case Rml::Log::LT_ERROR: type_str = "ERROR"; break; + case Rml::Log::LT_WARNING: type_str = "WARNING"; break; + case Rml::Log::LT_INFO: type_str = "INFO"; break; + case Rml::Log::LT_DEBUG: type_str = "DEBUG"; break; + default: break; + } + std::cout << "[RmlUi " << type_str << "] " << message << std::endl; + return true; + } +}; + +// Global state +static DesignerSystemInterface* g_system_interface = nullptr; +static mosis::DesktopFileInterface* g_file_interface = nullptr; +static mosis::IKernel* g_kernel = nullptr; +static std::filesystem::path g_assets_path; +static mosis::testing::UIInspector g_ui_inspector; + + +int main(int argc, const char* argv[]) +{ + // Parse command-line options first + Options opts = ParseOptions(argc, argv); + + // Open log file if specified + if (!opts.log_file.empty()) { + g_log_file.open(opts.log_file, std::ios::out | std::ios::trunc); + if (!g_log_file.is_open()) { + std::cerr << "Warning: Could not open log file: " << opts.log_file << std::endl; + } else { + mosis::g_log_file_ptr = &g_log_file; + } + } + + LogMessage("Mosis Designer v0.1.0"); + + if (opts.document_path.empty()) { + PrintUsage(argv[0]); + return EXIT_FAILURE; + } + + std::filesystem::path document_file = opts.document_path; + if (!std::filesystem::exists(document_file)) { + std::cerr << "File not found: " << opts.document_path << std::endl; + return EXIT_FAILURE; + } + document_file = std::filesystem::absolute(document_file); + + // Initialize the RmlUi backend (GLFW + OpenGL) + if (!Backend::Initialize("Mosis Designer", opts.width, opts.height, true)) { + std::cerr << "Failed to initialize backend" << std::endl; + return EXIT_FAILURE; + } + + // Find assets path + g_assets_path = FindAssetsPath(document_file.parent_path()); + LogMessage("Assets path: " + g_assets_path.generic_string()); + + // Setup custom interfaces + g_system_interface = new DesignerSystemInterface(Backend::GetSystemInterface()); + g_file_interface = new mosis::DesktopFileInterface(g_assets_path.string()); + + // Set platform for kernel + auto platform = std::make_unique(); + platform->GetFileInterface().SetAssetsPath(g_assets_path.string()); + mosis::SetPlatform(std::move(platform)); + + Rml::SetSystemInterface(g_system_interface); + Rml::SetFileInterface(g_file_interface); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // Initialize RmlUi + Rml::Initialise(); + Rml::Lua::Initialise(); + LogMessage("RmlUi and Lua initialized"); + + // Create context + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(opts.width, opts.height)); + if (!context) { + std::cerr << "Failed to create RmlUi context" << std::endl; + Rml::Shutdown(); + Backend::Shutdown(); + return EXIT_FAILURE; + } + + // Enable debugger in debug mode + if (opts.debug_mode) { + Rml::Debugger::Initialise(context); + Rml::Debugger::SetVisible(true); + } + + // Load fonts + std::filesystem::path fonts_path = g_assets_path / "fonts"; + LoadFonts(fonts_path); + + // Initialize sample data and data models + initializeSampleData(); + setupDataModels(context); + + // Create and configure kernel + mosis::KernelConfig kernel_config; + kernel_config.width = opts.width; + kernel_config.height = opts.height; + kernel_config.initial_document = document_file.string(); + kernel_config.threaded = false; + + auto kernel = mosis::CreateKernel(kernel_config); + g_kernel = kernel.get(); + + // Set context and register Lua functions + auto* desktop_kernel = dynamic_cast(g_kernel); + if (desktop_kernel) { + desktop_kernel->SetContext(context); + mosis::DesktopKernel::RegisterLuaFunctions(); + } + + // Load the initial document + std::string document_path_str = document_file.generic_string(); + LogMessage("Loading document: " + document_path_str); + + Rml::ElementDocument* document = context->LoadDocument(document_path_str); + if (document) { + document->Show(); + if (desktop_kernel) { + desktop_kernel->SetDocument(document); + desktop_kernel->SetCurrentDocumentPath(document_path_str); + } + LogMessage("Document loaded successfully"); + } else { + LogMessage("ERROR: Failed to load document!"); + } + + // Start kernel + kernel->Start(); + + // Setup hot-reload + std::unique_ptr hot_reload; + if (!opts.dump_mode) { + hot_reload = std::make_unique( + g_assets_path, + [&kernel]() { + std::cout << "File change detected, requesting reload..." << std::endl; + kernel->RequestReload(); + } + ); + hot_reload->Start(); + std::cout << "Hot-reload enabled for: " << g_assets_path.generic_string() << std::endl; + } + + // Main loop + bool running = true; + while (running) { + // Process hot-reload + if (hot_reload) { + hot_reload->CheckForChanges(); + } + + // Process events and update + running = Backend::ProcessEvents(context); + + // Update kernel (processes tasks, updates time, etc.) + kernel->Update(); + + // Render + Backend::BeginFrame(); + kernel->Render(); + Backend::PresentFrame(); + + // Dump hierarchy if enabled (quiet mode to avoid spamming console) + if (!opts.hierarchy_file.empty() && desktop_kernel) { + Rml::ElementDocument* doc = desktop_kernel->GetDocument(); + if (doc) { + g_ui_inspector.SaveToFile(doc, opts.hierarchy_file, true); + } + } + } + + // Cleanup + kernel->Stop(); + kernel.reset(); + g_kernel = nullptr; + + if (hot_reload) { + hot_reload->Stop(); + } + + if (opts.debug_mode) { + Rml::Debugger::Shutdown(); + } + + delete g_system_interface; + delete g_file_interface; + + Rml::Shutdown(); + Backend::Shutdown(); + + std::cout << "Mosis Designer shutdown complete" << std::endl; + return EXIT_SUCCESS; +} + +void PrintUsage(const char* program) +{ + std::cout << "Usage: " << program << " [options]" << std::endl; + std::cout << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " --resolution WxH Set phone resolution (default: 540x960)" << std::endl; + std::cout << " --dump Dump mode: screenshot + hierarchy, then exit" << std::endl; + std::cout << " --debug Enable RmlUi debugger" << std::endl; + std::cout << " --output DIR Output directory for dump mode (default: dump)" << std::endl; + std::cout << " --log FILE Write log output to file (for automated testing)" << std::endl; + std::cout << " --hierarchy FILE Continuously dump UI hierarchy to JSON file" << std::endl; + std::cout << std::endl; + std::cout << "Examples:" << std::endl; + std::cout << " " << program << " assets/apps/home/home.rml" << std::endl; + std::cout << " " << program << " assets/apps/home/home.rml --resolution 720x1280" << std::endl; + std::cout << " " << program << " assets/apps/home/home.rml --dump" << std::endl; +} + +Options ParseOptions(int argc, const char* argv[]) +{ + Options opts; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + + if (arg == "--resolution" && i + 1 < argc) { + std::string res = argv[++i]; + size_t x_pos = res.find('x'); + if (x_pos != std::string::npos) { + opts.width = std::stoi(res.substr(0, x_pos)); + opts.height = std::stoi(res.substr(x_pos + 1)); + } + } else if (arg == "--dump") { + opts.dump_mode = true; + } else if (arg == "--debug") { + opts.debug_mode = true; + } else if (arg == "--output" && i + 1 < argc) { + opts.output_dir = argv[++i]; + } else if (arg == "--log" && i + 1 < argc) { + opts.log_file = argv[++i]; + } else if (arg == "--hierarchy" && i + 1 < argc) { + opts.hierarchy_file = argv[++i]; + } else if (arg[0] != '-') { + opts.document_path = arg; + } + } + + return opts; +} + +void LoadFonts(const std::filesystem::path& fonts_path) +{ + if (!std::filesystem::exists(fonts_path)) { + std::cerr << "Fonts directory not found: " << fonts_path << std::endl; + return; + } + + int count = 0; + for (const auto& entry : std::filesystem::directory_iterator(fonts_path)) { + if (entry.path().extension() == ".ttf") { + std::string font_path = entry.path().generic_string(); + if (Rml::LoadFontFace(font_path)) { + std::cout << "Loaded font: " << entry.path().filename() << std::endl; + ++count; + } + } + } + + // Also try Roboto subdirectory + std::filesystem::path roboto_path = fonts_path / "Roboto"; + if (std::filesystem::exists(roboto_path)) { + for (const auto& entry : std::filesystem::recursive_directory_iterator(roboto_path)) { + if (entry.path().extension() == ".ttf") { + std::string font_path = entry.path().generic_string(); + if (Rml::LoadFontFace(font_path)) { + std::cout << "Loaded font: " << entry.path().filename() << std::endl; + ++count; + } + } + } + } + + std::cout << "Loaded " << count << " fonts" << std::endl; +} + +std::filesystem::path FindAssetsPath(const std::filesystem::path& start_path) +{ + std::filesystem::path current = std::filesystem::absolute(start_path); + + // Walk up the directory tree looking for a fonts/ subdirectory + while (!current.empty() && current.has_parent_path()) { + std::filesystem::path fonts_path = current / "fonts"; + if (std::filesystem::exists(fonts_path) && std::filesystem::is_directory(fonts_path)) { + // Check if it contains TTF files + for (const auto& entry : std::filesystem::directory_iterator(fonts_path)) { + if (entry.path().extension() == ".ttf") { + return current; + } + } + } + current = current.parent_path(); + } + + // Fall back to start path + return std::filesystem::absolute(start_path); +} diff --git a/designer/src/testing/action_player.cpp b/designer/src/testing/action_player.cpp new file mode 100644 index 0000000..feb45dc --- /dev/null +++ b/designer/src/testing/action_player.cpp @@ -0,0 +1,131 @@ +// Action player implementation +#include "action_player.h" +#include "service_interface.h" +#include +#include + +namespace mosis::testing { + +void ActionPlayer::LoadActions(const std::vector& actions) { + m_actions = actions; + Reset(); +} + +void ActionPlayer::LoadFromFile(const std::string& path) { + ActionRecorder recorder; + recorder.LoadFromFile(path); + m_actions = recorder.GetActions(); + Reset(); +} + +void ActionPlayer::Play() { + if (m_actions.empty()) { + std::cout << "No actions to play" << std::endl; + return; + } + m_playing = true; + std::cout << "Playback started" << std::endl; +} + +void ActionPlayer::Pause() { + m_playing = false; + std::cout << "Playback paused at action " << m_current_index << std::endl; +} + +void ActionPlayer::Stop() { + m_playing = false; + Reset(); + std::cout << "Playback stopped" << std::endl; +} + +void ActionPlayer::Reset() { + m_current_index = 0; + m_elapsed_time_ms = 0; +} + +void ActionPlayer::StepForward() { + if (m_current_index < m_actions.size()) { + ExecuteAction(m_actions[m_current_index]); + ++m_current_index; + } +} + +void ActionPlayer::Update(double delta_time_ms) { + if (!m_playing || m_current_index >= m_actions.size()) { + return; + } + + m_elapsed_time_ms += delta_time_ms; + + // Execute all actions whose timestamp has passed + while (m_current_index < m_actions.size()) { + const auto& action = m_actions[m_current_index]; + if (action.timestamp_ms <= m_elapsed_time_ms) { + ExecuteAction(action); + ++m_current_index; + } else { + break; + } + } + + // Check if finished + if (m_current_index >= m_actions.size()) { + m_playing = false; + std::cout << "Playback finished" << std::endl; + } +} + +void ActionPlayer::ExecuteAction(const Action& action) { + if (!m_kernel) { + std::cerr << "No kernel set for action player" << std::endl; + return; + } + + switch (action.type) { + case ActionType::Tap: + std::cout << "Execute tap at (" << action.x << ", " << action.y << ")" << std::endl; + m_kernel->OnTouchDown(static_cast(action.x), static_cast(action.y)); + m_kernel->OnTouchUp(static_cast(action.x), static_cast(action.y)); + break; + + case ActionType::Swipe: + std::cout << "Execute swipe from (" << action.x1 << ", " << action.y1 + << ") to (" << action.x2 << ", " << action.y2 << ")" << std::endl; + // Simplified swipe - just start and end + m_kernel->OnTouchDown(static_cast(action.x1), static_cast(action.y1)); + m_kernel->OnTouchMove(static_cast(action.x2), static_cast(action.y2)); + m_kernel->OnTouchUp(static_cast(action.x2), static_cast(action.y2)); + break; + + case ActionType::LongPress: + std::cout << "Execute long press at (" << action.x << ", " << action.y + << ") for " << action.duration_ms << "ms" << std::endl; + m_kernel->OnTouchDown(static_cast(action.x), static_cast(action.y)); + // In a real implementation, we'd hold for duration + m_kernel->OnTouchUp(static_cast(action.x), static_cast(action.y)); + break; + + case ActionType::Button: + std::cout << "Execute button: " << action.button << std::endl; + if (action.button == "back") { + m_kernel->OnBackButton(); + } else if (action.button == "home") { + m_kernel->OnHomeButton(); + } else if (action.button == "recents") { + m_kernel->OnRecentsButton(); + } + break; + + case ActionType::Wait: + std::cout << "Wait " << action.duration_ms << "ms" << std::endl; + // Wait is handled by timestamp comparison + break; + } + + // Call callback if set + if (m_action_callback) { + m_action_callback(action, m_current_index); + } +} + +} // namespace mosis::testing diff --git a/designer/src/testing/action_player.h b/designer/src/testing/action_player.h new file mode 100644 index 0000000..c142185 --- /dev/null +++ b/designer/src/testing/action_player.h @@ -0,0 +1,60 @@ +// Action player for replaying recorded UI interactions +#pragma once + +#include "action_recorder.h" +#include + +namespace mosis { +class IKernel; +} + +namespace mosis::testing { + +// Callback for when an action is executed +using ActionCallback = std::function; + +// Plays back recorded actions +class ActionPlayer { +public: + ActionPlayer() = default; + + // Set the kernel for executing actions + void SetKernel(IKernel* kernel) { m_kernel = kernel; } + + // Load actions to play + void LoadActions(const std::vector& actions); + void LoadFromFile(const std::string& path); + + // Playback control + void Play(); + void Pause(); + void Stop(); + void Reset(); + + // Step through one action at a time + void StepForward(); + + // Update (call each frame) + void Update(double delta_time_ms); + + // State + bool IsPlaying() const { return m_playing; } + bool IsFinished() const { return m_current_index >= m_actions.size(); } + size_t GetCurrentIndex() const { return m_current_index; } + size_t GetActionCount() const { return m_actions.size(); } + + // Callbacks + void SetActionCallback(ActionCallback callback) { m_action_callback = callback; } + +private: + void ExecuteAction(const Action& action); + + IKernel* m_kernel = nullptr; + std::vector m_actions; + size_t m_current_index = 0; + double m_elapsed_time_ms = 0; + bool m_playing = false; + ActionCallback m_action_callback; +}; + +} // namespace mosis::testing diff --git a/designer/src/testing/action_recorder.cpp b/designer/src/testing/action_recorder.cpp new file mode 100644 index 0000000..baa777a --- /dev/null +++ b/designer/src/testing/action_recorder.cpp @@ -0,0 +1,191 @@ +// Action recorder implementation +#include "action_recorder.h" +#include +#include + +namespace mosis::testing { + +nlohmann::json Action::ToJson() const { + nlohmann::json j; + + switch (type) { + case ActionType::Tap: + j["type"] = "tap"; + j["x"] = x; + j["y"] = y; + break; + case ActionType::Swipe: + j["type"] = "swipe"; + j["x1"] = x1; + j["y1"] = y1; + j["x2"] = x2; + j["y2"] = y2; + j["duration"] = duration_ms; + break; + case ActionType::LongPress: + j["type"] = "long_press"; + j["x"] = x; + j["y"] = y; + j["duration"] = duration_ms; + break; + case ActionType::Button: + j["type"] = "button"; + j["button"] = button; + break; + case ActionType::Wait: + j["type"] = "wait"; + j["duration"] = duration_ms; + break; + } + + j["timestamp"] = timestamp_ms; + return j; +} + +Action Action::FromJson(const nlohmann::json& j) { + Action action; + action.timestamp_ms = j.value("timestamp", 0); + + std::string type_str = j.value("type", ""); + if (type_str == "tap") { + action.type = ActionType::Tap; + action.x = j.value("x", 0.0); + action.y = j.value("y", 0.0); + } else if (type_str == "swipe") { + action.type = ActionType::Swipe; + action.x1 = j.value("x1", 0.0); + action.y1 = j.value("y1", 0.0); + action.x2 = j.value("x2", 0.0); + action.y2 = j.value("y2", 0.0); + action.duration_ms = j.value("duration", 0); + } else if (type_str == "long_press") { + action.type = ActionType::LongPress; + action.x = j.value("x", 0.0); + action.y = j.value("y", 0.0); + action.duration_ms = j.value("duration", 0); + } else if (type_str == "button") { + action.type = ActionType::Button; + action.button = j.value("button", ""); + } else if (type_str == "wait") { + action.type = ActionType::Wait; + action.duration_ms = j.value("duration", 0); + } + + return action; +} + +void ActionRecorder::StartRecording() { + m_recording = true; + m_start_time = std::chrono::steady_clock::now(); + m_actions.clear(); + std::cout << "Action recording started" << std::endl; +} + +void ActionRecorder::StopRecording() { + m_recording = false; + std::cout << "Action recording stopped. Recorded " << m_actions.size() << " actions" << std::endl; +} + +void ActionRecorder::RecordTap(double x, double y) { + if (!m_recording) return; + + Action action; + action.type = ActionType::Tap; + action.x = x; + action.y = y; + action.timestamp_ms = GetTimestamp(); + m_actions.push_back(action); +} + +void ActionRecorder::RecordSwipe(double x1, double y1, double x2, double y2, int duration_ms) { + if (!m_recording) return; + + Action action; + action.type = ActionType::Swipe; + action.x1 = x1; + action.y1 = y1; + action.x2 = x2; + action.y2 = y2; + action.duration_ms = duration_ms; + action.timestamp_ms = GetTimestamp(); + m_actions.push_back(action); +} + +void ActionRecorder::RecordLongPress(double x, double y, int duration_ms) { + if (!m_recording) return; + + Action action; + action.type = ActionType::LongPress; + action.x = x; + action.y = y; + action.duration_ms = duration_ms; + action.timestamp_ms = GetTimestamp(); + m_actions.push_back(action); +} + +void ActionRecorder::RecordButton(const std::string& button) { + if (!m_recording) return; + + Action action; + action.type = ActionType::Button; + action.button = button; + action.timestamp_ms = GetTimestamp(); + m_actions.push_back(action); +} + +void ActionRecorder::RecordWait(int duration_ms) { + if (!m_recording) return; + + Action action; + action.type = ActionType::Wait; + action.duration_ms = duration_ms; + action.timestamp_ms = GetTimestamp(); + m_actions.push_back(action); +} + +void ActionRecorder::SaveToFile(const std::string& path) const { + nlohmann::json j; + j["actions"] = nlohmann::json::array(); + + for (const auto& action : m_actions) { + j["actions"].push_back(action.ToJson()); + } + + std::ofstream file(path); + if (file) { + file << j.dump(2); + std::cout << "Saved " << m_actions.size() << " actions to " << path << std::endl; + } else { + std::cerr << "Failed to save actions to " << path << std::endl; + } +} + +void ActionRecorder::LoadFromFile(const std::string& path) { + std::ifstream file(path); + if (!file) { + std::cerr << "Failed to load actions from " << path << std::endl; + return; + } + + nlohmann::json j; + file >> j; + + m_actions.clear(); + for (const auto& action_json : j["actions"]) { + m_actions.push_back(Action::FromJson(action_json)); + } + + std::cout << "Loaded " << m_actions.size() << " actions from " << path << std::endl; +} + +void ActionRecorder::Clear() { + m_actions.clear(); +} + +int64_t ActionRecorder::GetTimestamp() const { + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now - m_start_time); + return duration.count(); +} + +} // namespace mosis::testing diff --git a/designer/src/testing/action_recorder.h b/designer/src/testing/action_recorder.h new file mode 100644 index 0000000..4880539 --- /dev/null +++ b/designer/src/testing/action_recorder.h @@ -0,0 +1,69 @@ +// Action recorder for UI testing automation +#pragma once + +#include +#include +#include +#include + +namespace mosis::testing { + +// Action types for recording user interactions +enum class ActionType { + Tap, + Swipe, + LongPress, + Button, + Wait +}; + +// Represents a single recorded action +struct Action { + ActionType type; + double x = 0, y = 0; // For tap, long_press + double x1 = 0, y1 = 0; // For swipe start + double x2 = 0, y2 = 0; // For swipe end + int duration_ms = 0; // For swipe, long_press, wait + std::string button; // For button actions (back, home, recents) + int64_t timestamp_ms = 0; // Time offset from recording start + + nlohmann::json ToJson() const; + static Action FromJson(const nlohmann::json& j); +}; + +// Records user interactions into a sequence of actions +class ActionRecorder { +public: + ActionRecorder() = default; + + // Start/stop recording + void StartRecording(); + void StopRecording(); + bool IsRecording() const { return m_recording; } + + // Record individual actions + void RecordTap(double x, double y); + void RecordSwipe(double x1, double y1, double x2, double y2, int duration_ms); + void RecordLongPress(double x, double y, int duration_ms); + void RecordButton(const std::string& button); + void RecordWait(int duration_ms); + + // Get recorded actions + const std::vector& GetActions() const { return m_actions; } + + // Save/load to JSON + void SaveToFile(const std::string& path) const; + void LoadFromFile(const std::string& path); + + // Clear recorded actions + void Clear(); + +private: + int64_t GetTimestamp() const; + + bool m_recording = false; + std::vector m_actions; + std::chrono::steady_clock::time_point m_start_time; +}; + +} // namespace mosis::testing diff --git a/designer/src/testing/ui_inspector.cpp b/designer/src/testing/ui_inspector.cpp new file mode 100644 index 0000000..7f5cfe7 --- /dev/null +++ b/designer/src/testing/ui_inspector.cpp @@ -0,0 +1,183 @@ +// UI Inspector implementation +#include "ui_inspector.h" +#include +#include +#include +#include + +namespace mosis::testing { + +nlohmann::json UIInspector::DumpDocument(Rml::ElementDocument* document) const { + nlohmann::json j; + + if (!document) { + return j; + } + + // Add metadata + j["timestamp"] = std::time(nullptr); + j["screen"] = document->GetSourceURL(); + j["resolution"] = { + {"width", document->GetContext()->GetDimensions().x}, + {"height", document->GetContext()->GetDimensions().y} + }; + + // Dump element tree + j["elements"] = DumpElement(document); + + return j; +} + +nlohmann::json UIInspector::DumpElement(Rml::Element* element) const { + nlohmann::json j; + + if (!element) { + return j; + } + + j["tag"] = element->GetTagName(); + j["id"] = element->GetId(); + + // Get classes + nlohmann::json classes = nlohmann::json::array(); + Rml::String class_attr = element->GetAttribute("class", ""); + if (!class_attr.empty()) { + // Split by space + size_t start = 0; + size_t end; + while ((end = class_attr.find(' ', start)) != std::string::npos) { + if (end > start) { + classes.push_back(class_attr.substr(start, end - start)); + } + start = end + 1; + } + if (start < class_attr.size()) { + classes.push_back(class_attr.substr(start)); + } + } + j["classes"] = classes; + + // Get bounds + auto bounds = GetBounds(element); + j["bounds"] = bounds.ToJson(); + + // Visibility + j["visible"] = IsVisible(element); + + // Children + nlohmann::json children = nlohmann::json::array(); + for (int i = 0; i < element->GetNumChildren(); ++i) { + Rml::Element* child = element->GetChild(i); + if (child && child->GetTagName() != "#text") { + children.push_back(DumpElement(child)); + } + } + + // Text content (only for leaf elements without children to avoid huge JSON) + if (children.empty()) { + std::string text = GetText(element); + // Only store short text to avoid huge JSON and escaping issues + if (!text.empty() && text.length() < 200) { + j["text"] = text; + } else { + j["text"] = nlohmann::json(); // null + } + } else { + j["text"] = nlohmann::json(); // null for non-leaf elements + j["children"] = children; + } + + return j; +} + +Rml::Element* UIInspector::FindById(Rml::ElementDocument* document, const std::string& id) const { + if (!document) return nullptr; + return document->GetElementById(id); +} + +std::vector UIInspector::FindByClass(Rml::ElementDocument* document, const std::string& class_name) const { + std::vector results; + + if (!document) return results; + + Rml::ElementList elements; + document->GetElementsByClassName(elements, class_name); + + for (auto* element : elements) { + results.push_back(element); + } + + return results; +} + +bool UIInspector::IsVisible(Rml::Element* element) const { + if (!element) return false; + + // Check if element has zero size + auto box = element->GetBox(); + if (box.GetSize().x <= 0 || box.GetSize().y <= 0) { + return false; + } + + // Check display property + auto display = element->GetProperty("display"); + if (display == Rml::Style::Display::None) { + return false; + } + + // Check visibility property + auto visibility = element->GetProperty("visibility"); + if (visibility == Rml::Style::Visibility::Hidden) { + return false; + } + + return true; +} + +ElementBounds UIInspector::GetBounds(Rml::Element* element) const { + ElementBounds bounds = {0, 0, 0, 0}; + + if (!element) return bounds; + + auto abs_offset = element->GetAbsoluteOffset(Rml::BoxArea::Border); + auto box = element->GetBox(); + + bounds.x = abs_offset.x; + bounds.y = abs_offset.y; + bounds.width = box.GetSize(Rml::BoxArea::Border).x; + bounds.height = box.GetSize(Rml::BoxArea::Border).y; + + return bounds; +} + +std::string UIInspector::GetText(Rml::Element* element) const { + if (!element) return ""; + return element->GetInnerRML(); +} + +void UIInspector::SaveToFile(Rml::ElementDocument* document, const std::string& path, bool quiet) const { + nlohmann::json j = DumpDocument(document); + + // Write to temp file first, then rename for atomic update + std::string tempPath = path + ".tmp"; + std::ofstream file(tempPath); + if (file) { + file << j.dump(2); + file.close(); // Ensure file is closed and flushed + + // Atomic rename (on Windows, need to remove destination first) + std::error_code ec; + std::filesystem::remove(path, ec); + std::filesystem::rename(tempPath, path, ec); + + if (ec && !quiet) { + std::cerr << "Failed to rename temp file: " << ec.message() << std::endl; + } else if (!quiet) { + std::cout << "Saved UI hierarchy to " << path << std::endl; + } + } else { + std::cerr << "Failed to save UI hierarchy to " << path << std::endl; + } +} + +} // namespace mosis::testing diff --git a/designer/src/testing/ui_inspector.h b/designer/src/testing/ui_inspector.h new file mode 100644 index 0000000..7fda709 --- /dev/null +++ b/designer/src/testing/ui_inspector.h @@ -0,0 +1,53 @@ +// UI Inspector for dumping element hierarchy +#pragma once + +#include +#include + +namespace Rml { +class Element; +class ElementDocument; +} + +namespace mosis::testing { + +// Element bounds +struct ElementBounds { + float x, y, width, height; + + nlohmann::json ToJson() const { + return {{"x", x}, {"y", y}, {"width", width}, {"height", height}}; + } +}; + +// Inspects and dumps UI element hierarchy +class UIInspector { +public: + UIInspector() = default; + + // Dump the element tree of a document to JSON + nlohmann::json DumpDocument(Rml::ElementDocument* document) const; + + // Dump a single element and its children + nlohmann::json DumpElement(Rml::Element* element) const; + + // Find element by ID + Rml::Element* FindById(Rml::ElementDocument* document, const std::string& id) const; + + // Find elements by class + std::vector FindByClass(Rml::ElementDocument* document, const std::string& class_name) const; + + // Check if element is visible + bool IsVisible(Rml::Element* element) const; + + // Get element bounds (in screen coordinates) + ElementBounds GetBounds(Rml::Element* element) const; + + // Get element text content + std::string GetText(Rml::Element* element) const; + + // Save hierarchy to file (quiet=true suppresses log message) + void SaveToFile(Rml::ElementDocument* document, const std::string& path, bool quiet = false) const; +}; + +} // namespace mosis::testing diff --git a/designer/src/testing/visual_capture.cpp b/designer/src/testing/visual_capture.cpp new file mode 100644 index 0000000..ad2d300 --- /dev/null +++ b/designer/src/testing/visual_capture.cpp @@ -0,0 +1,244 @@ +// Visual capture implementation +#include "visual_capture.h" +#include +#include +#include +#include +#include + +// OpenGL header (from RmlUi backend) +#include + +namespace mosis::testing { + +ImageData VisualCapture::CaptureFramebuffer(uint32_t width, uint32_t height) const { + ImageData image; + image.width = width; + image.height = height; + image.pixels.resize(width * height * 4); + + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, image.pixels.data()); + + // Flip vertically (OpenGL origin is bottom-left) + FlipVertically(image); + + return image; +} + +bool VisualCapture::SavePNG(const ImageData& image, const std::string& path) const { + if (!image.IsValid()) { + std::cerr << "Invalid image data" << std::endl; + return false; + } + + FILE* fp = fopen(path.c_str(), "wb"); + if (!fp) { + std::cerr << "Failed to open file for writing: " << path << std::endl; + return false; + } + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) { + fclose(fp); + return false; + } + + png_infop info = png_create_info_struct(png); + if (!info) { + png_destroy_write_struct(&png, nullptr); + fclose(fp); + return false; + } + + if (setjmp(png_jmpbuf(png))) { + png_destroy_write_struct(&png, &info); + fclose(fp); + return false; + } + + png_init_io(png, fp); + + png_set_IHDR(png, info, image.width, image.height, 8, + PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png, info); + + // Write rows + std::vector rows(image.height); + for (uint32_t y = 0; y < image.height; ++y) { + rows[y] = const_cast(image.pixels.data() + y * image.width * 4); + } + + png_write_image(png, rows.data()); + png_write_end(png, nullptr); + + png_destroy_write_struct(&png, &info); + fclose(fp); + + std::cout << "Saved screenshot to " << path << std::endl; + return true; +} + +ImageData VisualCapture::LoadPNG(const std::string& path) const { + ImageData image; + + FILE* fp = fopen(path.c_str(), "rb"); + if (!fp) { + std::cerr << "Failed to open file for reading: " << path << std::endl; + return image; + } + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) { + fclose(fp); + return image; + } + + png_infop info = png_create_info_struct(png); + if (!info) { + png_destroy_read_struct(&png, nullptr, nullptr); + fclose(fp); + return image; + } + + if (setjmp(png_jmpbuf(png))) { + png_destroy_read_struct(&png, &info, nullptr); + fclose(fp); + return image; + } + + png_init_io(png, fp); + png_read_info(png, info); + + image.width = png_get_image_width(png, info); + image.height = png_get_image_height(png, info); + png_byte color_type = png_get_color_type(png, info); + png_byte bit_depth = png_get_bit_depth(png, info); + + // Convert to RGBA + if (bit_depth == 16) { + png_set_strip_16(png); + } + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png); + } + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(png); + } + if (png_get_valid(png, info, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png); + } + if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + } + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png); + } + + png_read_update_info(png, info); + + // Read rows + image.pixels.resize(image.width * image.height * 4); + std::vector rows(image.height); + for (uint32_t y = 0; y < image.height; ++y) { + rows[y] = image.pixels.data() + y * image.width * 4; + } + + png_read_image(png, rows.data()); + + png_destroy_read_struct(&png, &info, nullptr); + fclose(fp); + + return image; +} + +CompareResult VisualCapture::Compare(const ImageData& actual, const ImageData& expected, double threshold) const { + CompareResult result; + + if (!actual.IsValid() || !expected.IsValid()) { + return result; + } + + if (actual.width != expected.width || actual.height != expected.height) { + std::cerr << "Image dimensions don't match" << std::endl; + return result; + } + + uint32_t total_pixels = actual.width * actual.height; + uint32_t diff_pixels = 0; + + for (uint32_t i = 0; i < actual.pixels.size(); i += 4) { + int dr = std::abs(static_cast(actual.pixels[i]) - static_cast(expected.pixels[i])); + int dg = std::abs(static_cast(actual.pixels[i+1]) - static_cast(expected.pixels[i+1])); + int db = std::abs(static_cast(actual.pixels[i+2]) - static_cast(expected.pixels[i+2])); + int da = std::abs(static_cast(actual.pixels[i+3]) - static_cast(expected.pixels[i+3])); + + // If any channel differs significantly, count as different + if (dr > 2 || dg > 2 || db > 2 || da > 2) { + ++diff_pixels; + } + } + + result.diff_pixels = diff_pixels; + result.diff_percent = static_cast(diff_pixels) / total_pixels; + result.match = result.diff_percent <= threshold; + + if (!result.match) { + result.diff_image = GenerateDiff(actual, expected); + } + + return result; +} + +ImageData VisualCapture::GenerateDiff(const ImageData& actual, const ImageData& expected) const { + ImageData diff; + + if (!actual.IsValid() || !expected.IsValid()) { + return diff; + } + + diff.width = actual.width; + diff.height = actual.height; + diff.pixels.resize(actual.pixels.size()); + + for (uint32_t i = 0; i < actual.pixels.size(); i += 4) { + int dr = std::abs(static_cast(actual.pixels[i]) - static_cast(expected.pixels[i])); + int dg = std::abs(static_cast(actual.pixels[i+1]) - static_cast(expected.pixels[i+1])); + int db = std::abs(static_cast(actual.pixels[i+2]) - static_cast(expected.pixels[i+2])); + + if (dr > 2 || dg > 2 || db > 2) { + // Highlight difference in red + diff.pixels[i] = 255; + diff.pixels[i+1] = 0; + diff.pixels[i+2] = 0; + diff.pixels[i+3] = 255; + } else { + // Dim the matching pixels + diff.pixels[i] = actual.pixels[i] / 3; + diff.pixels[i+1] = actual.pixels[i+1] / 3; + diff.pixels[i+2] = actual.pixels[i+2] / 3; + diff.pixels[i+3] = 255; + } + } + + return diff; +} + +void VisualCapture::FlipVertically(ImageData& image) const { + if (!image.IsValid()) return; + + size_t row_size = image.width * 4; + std::vector temp_row(row_size); + + for (uint32_t y = 0; y < image.height / 2; ++y) { + uint8_t* top = image.pixels.data() + y * row_size; + uint8_t* bottom = image.pixels.data() + (image.height - 1 - y) * row_size; + + memcpy(temp_row.data(), top, row_size); + memcpy(top, bottom, row_size); + memcpy(bottom, temp_row.data(), row_size); + } +} + +} // namespace mosis::testing diff --git a/designer/src/testing/visual_capture.h b/designer/src/testing/visual_capture.h new file mode 100644 index 0000000..e064094 --- /dev/null +++ b/designer/src/testing/visual_capture.h @@ -0,0 +1,51 @@ +// Visual capture for screenshots and image comparison +#pragma once + +#include +#include +#include + +namespace mosis::testing { + +// PNG image data +struct ImageData { + uint32_t width = 0; + uint32_t height = 0; + std::vector pixels; // RGBA format + + bool IsValid() const { return width > 0 && height > 0 && !pixels.empty(); } +}; + +// Result of image comparison +struct CompareResult { + bool match = false; + double diff_percent = 0.0; + uint32_t diff_pixels = 0; + ImageData diff_image; +}; + +// Captures and compares screenshots +class VisualCapture { +public: + VisualCapture() = default; + + // Capture current framebuffer to image + ImageData CaptureFramebuffer(uint32_t width, uint32_t height) const; + + // Save image to PNG file + bool SavePNG(const ImageData& image, const std::string& path) const; + + // Load PNG file to image + ImageData LoadPNG(const std::string& path) const; + + // Compare two images + CompareResult Compare(const ImageData& actual, const ImageData& expected, double threshold = 0.01) const; + + // Generate diff image (highlights differences) + ImageData GenerateDiff(const ImageData& actual, const ImageData& expected) const; + + // Utility: flip image vertically (for OpenGL coordinate conversion) + void FlipVertically(ImageData& image) const; +}; + +} // namespace mosis::testing diff --git a/designer/test/README.md b/designer/test/README.md new file mode 100644 index 0000000..c243f35 --- /dev/null +++ b/designer/test/README.md @@ -0,0 +1,144 @@ +# Mosis Designer UI Testing with AutoHotkey + +## Overview + +This folder contains AutoHotkey v2 scripts for automated UI testing of the Mosis Designer. + +## Testing Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Test Runner (run_tests.ahk) │ +│ - Launches designer process │ +│ - Captures stdout for verification │ +│ - Runs test scripts sequentially │ +│ - Generates test report │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Test Scripts (test_*.ahk) │ +│ - test_navigation.ahk: Click apps, verify screen change │ +│ - test_back_button.ahk: Navigate and go back │ +│ - test_home_button.ahk: Navigate deep, press home │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Utilities (lib/utils.ahk) │ +│ - FindDesignerWindow(): Get window handle │ +│ - ClickPhone(x, y): Click at phone coordinates │ +│ - WaitForScreen(name): Wait for navigation log │ +│ - CaptureScreenshot(path): Save window image │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Window Identification + +- **Window Title**: "Mosis Designer" +- **Window Class**: GLFW window class (varies by version) +- **Size**: 540x960 client area (phone resolution) + +## Coordinate System + +The phone UI uses a 540x960 coordinate system. AHK needs to: + +1. Find the designer window +2. Get the client area position +3. Convert phone coordinates to screen coordinates + +``` +Phone Coordinates (0,0 to 540,960) + │ + ▼ +┌──────────────────┐ +│ Window Title │ ← Window decorations +├──────────────────┤ +│ │ +│ Phone UI │ ← Client area (540x960) +│ (0,0) │ +│ ┌──────┐ │ +│ │ App │ │ ← Click target +│ │ Icon │ │ +│ └──────┘ │ +│ │ +│ (540,960) +└──────────────────┘ +``` + +## App Icon Positions (Home Screen) + +Based on the home.rml layout with 4 columns: + +| Row | App | Approx X | Approx Y | +|-----|-----------|----------|----------| +| 1 | Phone | 67 | 100 | +| 1 | Messages | 202 | 100 | +| 1 | Contacts | 337 | 100 | +| 1 | Browser | 472 | 100 | +| 2 | Gallery | 67 | 200 | +| 2 | Camera | 202 | 200 | +| 2 | Settings | 337 | 200 | +| 2 | Music | 472 | 200 | + +Dock items at bottom (~900 Y): +- Phone: 67 +- Messages: 202 +- Contacts: 337 +- Browser: 472 + +## Verification Methods + +### 1. Console Output Parsing +The designer outputs navigation events to stdout: +``` +navigateTo called with: dialer +Loaded screen: apps/dialer/dialer.rml +Navigated to: dialer (history depth: 1) +goBack called (history depth: 1) +Back to: home +``` + +We can redirect stdout to a file and parse it for verification. + +### 2. Screenshot Comparison +Take screenshots at key points and compare with baselines. + +### 3. Window Title Changes (future) +Could modify designer to include current screen in title. + +## Test Execution Flow + +``` +1. Start designer with stdout redirected to log file +2. Wait for window to appear +3. Wait for "Document loaded successfully" in log +4. Execute test actions (clicks, waits) +5. Parse log for expected navigation events +6. Report pass/fail +7. Close designer +``` + +## Files + +- `lib/utils.ahk` - Shared utility functions +- `run_tests.ahk` - Main test runner +- `test_navigation.ahk` - Navigation test suite +- `test_back_button.ahk` - Back button test suite +- `config.ahk` - Test configuration (paths, timeouts) + +## Usage + +```powershell +# Run all tests +& "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe" run_tests.ahk + +# Run specific test +& "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe" test_navigation.ahk +``` + +## Requirements + +- AutoHotkey v2 (installed at C:\Program Files\AutoHotkey\v2) +- Mosis Designer built (designer\build\Debug\mosis-designer.exe) +- Test assets in place (src\main\assets\) diff --git a/designer/test/click_test.ahk b/designer/test/click_test.ahk new file mode 100644 index 0000000..dbb5cd8 --- /dev/null +++ b/designer/test/click_test.ahk @@ -0,0 +1,102 @@ +; Click test for Mosis Designer +#Requires AutoHotkey v2.0 + +; Configuration +WINDOW_TITLE := "Mosis Designer" +PHONE_WIDTH := 540 +PHONE_HEIGHT := 960 + +; Output file +outFile := A_ScriptDir . "\click_test_result.txt" +if (FileExist(outFile)) + FileDelete(outFile) + +Log(msg) { + global outFile + FileAppend(msg . "`n", outFile, "UTF-8") +} + +Log("=== Click Test ===") +Log("Time: " . FormatTime(, "yyyy-MM-dd HH:mm:ss")) + +; Find window +hwnd := WinExist(WINDOW_TITLE) +if (!hwnd) { + Log("ERROR: Window not found") + ExitApp(1) +} + +Log("Window found: " . hwnd) + +; Get window info +WinGetPos(&winX, &winY, &winW, &winH, hwnd) +Log("Window position: X=" . winX . " Y=" . winY) +Log("Window size: W=" . winW . " H=" . winH) + +; Calculate DPI scale factor +; Expected phone size is 540x960 +; Window has some decorations, estimate ~16 pixels width, ~40 height for title+borders +expectedW := 540 + 16 +expectedH := 960 + 40 +scaleX := winW / expectedW +scaleY := winH / expectedH +Log("Estimated scale X: " . scaleX . " Y: " . scaleY) + +; Use average scale (should be similar for both) +scale := (scaleX + scaleY) / 2 +Log("Using scale factor: " . scale) + +; Calculate client area offset (estimate title bar and borders scaled) +titleBarHeight := Round(31 * scale) +borderWidth := Round(8 * scale) +Log("Estimated title bar: " . titleBarHeight . "px, border: " . borderWidth . "px") + +; Client area start +clientX := winX + borderWidth +clientY := winY + titleBarHeight +Log("Client area start: X=" . clientX . " Y=" . clientY) + +; Function to convert phone coords to screen coords +PhoneToScreen(phoneX, phoneY) { + global clientX, clientY, scale + return { + x: Round(clientX + phoneX * scale), + y: Round(clientY + phoneY * scale) + } +} + +; Activate window +WinActivate(hwnd) +Sleep(200) + +Log("") +Log("=== Performing Click Test ===") + +; Click on Phone app icon (first app in grid) +; Position from home.rml layout: approximately (67, 120) in phone coords +phonePos := PhoneToScreen(67, 120) +Log("Phone app: phone(67,120) -> screen(" . phonePos.x . "," . phonePos.y . ")") + +; Perform click +Click(phonePos.x, phonePos.y) +Log("Click sent!") + +Sleep(1000) +Log("Test complete - check if screen changed to Dialer") + +; Also try clicking messages in dock for comparison +Log("") +Log("=== Second Click Test (Messages Dock) ===") +; Dock is at bottom, approximately y=920 in phone coords +; Messages is second item, approximately x=202 +msgPos := PhoneToScreen(202, 920) +Log("Messages dock: phone(202,920) -> screen(" . msgPos.x . "," . msgPos.y . ")") + +Click(msgPos.x, msgPos.y) +Log("Click sent!") + +Sleep(500) +Log("") +Log("=== Test Complete ===") + +ExitApp(0) diff --git a/designer/test/config.ahk b/designer/test/config.ahk new file mode 100644 index 0000000..b7ac6be --- /dev/null +++ b/designer/test/config.ahk @@ -0,0 +1,45 @@ +; Mosis Designer Test Configuration +; AutoHotkey v2 + +; Paths +global DESIGNER_EXE := A_ScriptDir . "\..\build\Debug\mosis-designer.exe" +global ASSETS_PATH := A_ScriptDir . "\..\..\src\main\assets" +global HOME_RML := ASSETS_PATH . "\apps\home\home.rml" +global LOG_FILE := A_ScriptDir . "\test_output.log" +global SCREENSHOT_DIR := A_ScriptDir . "\screenshots" + +; Window settings +global WINDOW_TITLE := "Mosis Designer" +global PHONE_WIDTH := 540 +global PHONE_HEIGHT := 960 + +; Timeouts (milliseconds) +global STARTUP_TIMEOUT := 10000 ; Wait for window to appear +global NAVIGATION_TIMEOUT := 2000 ; Wait for navigation to complete +global CLICK_DELAY := 100 ; Delay after click + +; App icon positions on home screen (approximate center of each icon) +; Grid is 4 columns, icons are ~135px apart horizontally +; First row starts around Y=100 +global APP_POSITIONS := Map( + "phone", {x: 67, y: 120}, + "messages", {x: 202, y: 120}, + "contacts", {x: 337, y: 120}, + "browser", {x: 472, y: 120}, + "gallery", {x: 67, y: 220}, + "camera", {x: 202, y: 220}, + "settings", {x: 337, y: 220}, + "music", {x: 472, y: 220}, + "calendar", {x: 67, y: 320}, + "clock", {x: 202, y: 320}, + "notes", {x: 337, y: 320}, + "maps", {x: 472, y: 320} +) + +; Dock positions (bottom of screen) +global DOCK_POSITIONS := Map( + "phone", {x: 67, y: 920}, + "messages", {x: 202, y: 920}, + "contacts", {x: 337, y: 920}, + "browser", {x: 472, y: 920} +) diff --git a/designer/test/diagnose.ahk b/designer/test/diagnose.ahk new file mode 100644 index 0000000..0f0e85b --- /dev/null +++ b/designer/test/diagnose.ahk @@ -0,0 +1,124 @@ +; Mosis Designer Diagnostic Script +; AutoHotkey v2 +; Outputs window information without user interaction + +#Requires AutoHotkey v2.0 + +; Simple config (inline to avoid include issues) +WINDOW_TITLE := "Mosis Designer" +PHONE_WIDTH := 540 +PHONE_HEIGHT := 960 + +; Output file +outputFile := A_ScriptDir . "\diagnose_output.txt" + +; Clear previous output +if (FileExist(outputFile)) + FileDelete(outputFile) + +; Helper to write output +WriteOutput(msg) { + FileAppend(msg . "`n", outputFile) +} + +WriteOutput("=== Mosis Designer Diagnostic ===") +WriteOutput("Time: " . A_Now) +WriteOutput("") + +; Find window +hwnd := WinExist(WINDOW_TITLE) + +if (!hwnd) { + WriteOutput("ERROR: Window '" . WINDOW_TITLE . "' not found") + WriteOutput("") + WriteOutput("Looking for any GLFW windows...") + + ; Try to find any GLFW window + windows := WinGetList() + for wnd in windows { + title := WinGetTitle(wnd) + class := WinGetClass(wnd) + if (InStr(class, "GLFW") || InStr(title, "Mosis") || InStr(title, "Designer")) { + WriteOutput("Found candidate: " . wnd . " - Title: " . title . " - Class: " . class) + } + } + ExitApp() +} + +WriteOutput("Window found!") +WriteOutput("HWND: " . hwnd) + +; Get window info +title := WinGetTitle(hwnd) +class := WinGetClass(hwnd) +WinGetPos(&winX, &winY, &winW, &winH, hwnd) + +WriteOutput("Title: " . title) +WriteOutput("Class: " . class) +WriteOutput("Position: X=" . winX . " Y=" . winY) +WriteOutput("Size: W=" . winW . " H=" . winH) + +; Estimate client area +; For GLFW windows, borders are typically small +; Let's try different estimates + +WriteOutput("") +WriteOutput("=== Client Area Estimates ===") + +; Method 1: Standard Windows decorations +borderW := 8 +titleH := 31 +clientX1 := winX + borderW +clientY1 := winY + titleH +WriteOutput("Estimate 1 (standard border): X=" . clientX1 . " Y=" . clientY1) + +; Method 2: Minimal border (GLFW often uses this) +borderW2 := 1 +titleH2 := 1 +clientX2 := winX + borderW2 +clientY2 := winY + titleH2 +WriteOutput("Estimate 2 (minimal border): X=" . clientX2 . " Y=" . clientY2) + +; Method 3: No border (borderless window) +WriteOutput("Estimate 3 (no border): X=" . winX . " Y=" . winY) + +; Calculate expected phone area size vs actual window +WriteOutput("") +WriteOutput("=== Size Analysis ===") +WriteOutput("Expected phone size: " . PHONE_WIDTH . "x" . PHONE_HEIGHT) +WriteOutput("Actual window size: " . winW . "x" . winH) + +expectedW := PHONE_WIDTH + (2 * 8) ; borders +expectedH := PHONE_HEIGHT + 31 + 8 ; title + border +WriteOutput("Expected window size (with decorations): " . expectedW . "x" . expectedH) + +; Check if window is maximized or has different size +if (winW > PHONE_WIDTH + 50 || winH > PHONE_HEIGHT + 100) { + WriteOutput("WARNING: Window seems larger than expected - may be scaled or maximized") +} + +; Try a click test (won't do anything without activation, but log coordinates) +WriteOutput("") +WriteOutput("=== Click Coordinate Test ===") + +; Phone center +phoneX := 270 +phoneY := 480 + +; Convert to screen (using estimate 1) +screenX := clientX1 + phoneX +screenY := clientY1 + phoneY +WriteOutput("Phone center (270, 480) -> Screen (" . screenX . ", " . screenY . ")") + +; Phone app icon position +phoneAppX := 67 +phoneAppY := 120 +screenAppX := clientX1 + phoneAppX +screenAppY := clientY1 + phoneAppY +WriteOutput("Phone app icon (67, 120) -> Screen (" . screenAppX . ", " . screenAppY . ")") + +WriteOutput("") +WriteOutput("=== Diagnostic Complete ===") +WriteOutput("Output written to: " . outputFile) + +ExitApp() diff --git a/designer/test/full_click_test.ahk b/designer/test/full_click_test.ahk new file mode 100644 index 0000000..863ab25 --- /dev/null +++ b/designer/test/full_click_test.ahk @@ -0,0 +1,149 @@ +; Full click test with output verification +#Requires AutoHotkey v2.0 + +; Configuration +DESIGNER_EXE := A_ScriptDir . "\..\build\Debug\mosis-designer.exe" +HOME_RML := A_ScriptDir . "\..\..\src\main\assets\apps\home\home.rml" +WINDOW_TITLE := "Mosis Designer" +PHONE_WIDTH := 540 +PHONE_HEIGHT := 960 + +; Output files +logFile := A_ScriptDir . "\designer_output.log" +resultFile := A_ScriptDir . "\full_test_result.txt" + +if (FileExist(logFile)) + FileDelete(logFile) +if (FileExist(resultFile)) + FileDelete(resultFile) + +Log(msg) { + global resultFile + FileAppend(msg . "`n", resultFile, "UTF-8") +} + +Log("=== Full Click Test with Output Capture ===") +Log("Time: " . FormatTime(, "yyyy-MM-dd HH:mm:ss")) +Log("") + +; Start designer with output redirection +Log("Starting designer...") +cmd := '"' . DESIGNER_EXE . '" "' . HOME_RML . '" > "' . logFile . '" 2>&1' +Log("Command: " . cmd) + +; Run via cmd to handle redirection +Run(A_ComSpec . " /c " . cmd, A_ScriptDir, , &pid) +Log("Started with PID: " . pid) + +; Wait for window +Log("Waiting for window...") +startTime := A_TickCount +hwnd := 0 +while (A_TickCount - startTime < 15000) { + hwnd := WinExist(WINDOW_TITLE) + if (hwnd) + break + Sleep(200) +} + +if (!hwnd) { + Log("ERROR: Window did not appear within 15 seconds") + ExitApp(1) +} +Log("Window appeared: " . hwnd) + +; Wait for document to load +Sleep(2000) + +; Get window dimensions and calculate scale +WinGetPos(&winX, &winY, &winW, &winH, hwnd) +Log("Window: X=" . winX . " Y=" . winY . " W=" . winW . " H=" . winH) + +; Calculate scale +expectedW := 540 + 16 +expectedH := 960 + 40 +scale := (winW / expectedW + winH / expectedH) / 2 +Log("DPI Scale: " . scale) + +; Calculate client offset +titleBar := Round(31 * scale) +border := Round(8 * scale) +clientX := winX + border +clientY := winY + titleBar +Log("Client: X=" . clientX . " Y=" . clientY) + +; Activate window +WinActivate(hwnd) +Sleep(300) + +; Function to click at phone coordinates +ClickAt(phoneX, phoneY, desc) { + global clientX, clientY, scale, hwnd + screenX := Round(clientX + phoneX * scale) + screenY := Round(clientY + phoneY * scale) + Log("Click " . desc . ": phone(" . phoneX . "," . phoneY . ") -> screen(" . screenX . "," . screenY . ")") + + ; Activate and click + WinActivate(hwnd) + Sleep(50) + Click(screenX, screenY) + Sleep(500) +} + +Log("") +Log("=== Test 1: Click Phone App ===") +ClickAt(67, 120, "Phone app") +Sleep(1500) + +; Check log for navigation +Log("Checking log for navigation...") +if (FileExist(logFile)) { + content := FileRead(logFile) + if (InStr(content, "Navigated to: dialer") || InStr(content, "Loaded screen: apps/dialer")) { + Log("SUCCESS: Navigation to dialer detected!") + } else if (InStr(content, "navigateTo called")) { + Log("PARTIAL: navigateTo was called but navigation may have failed") + } else { + Log("FAIL: No navigation detected") + } +} else { + Log("WARNING: Log file not found") +} + +Log("") +Log("=== Test 2: Click Messages Dock ===") +ClickAt(202, 920, "Messages dock") +Sleep(1500) + +if (FileExist(logFile)) { + content := FileRead(logFile) + if (InStr(content, "messages")) { + Log("SUCCESS: Messages-related activity detected") + } else { + Log("FAIL: No messages navigation detected") + } +} + +Log("") +Log("=== Cleaning Up ===") +; Close designer +WinClose(hwnd) +Sleep(500) + +; Dump the full log +Log("") +Log("=== Designer Log Contents ===") +if (FileExist(logFile)) { + content := FileRead(logFile) + ; Only show last part of log (navigation related) + lines := StrSplit(content, "`n") + Log("(Last 30 lines)") + startIdx := lines.Length > 30 ? lines.Length - 30 : 1 + loop lines.Length - startIdx + 1 { + Log(lines[startIdx + A_Index - 1]) + } +} + +Log("") +Log("=== Test Complete ===") +ExitApp(0) diff --git a/designer/test/lib/utils.ahk b/designer/test/lib/utils.ahk new file mode 100644 index 0000000..8009817 --- /dev/null +++ b/designer/test/lib/utils.ahk @@ -0,0 +1,298 @@ +; Mosis Designer Test Utilities +; AutoHotkey v2 + +#Include "..\config.ahk" + +; Find the designer window and return its handle +FindDesignerWindow() { + return WinExist(WINDOW_TITLE) +} + +; Wait for the designer window to appear +; Returns window handle or 0 if timeout +WaitForDesignerWindow(timeout := 0) { + if (timeout = 0) + timeout := STARTUP_TIMEOUT + + startTime := A_TickCount + while (A_TickCount - startTime < timeout) { + hwnd := FindDesignerWindow() + if (hwnd) { + ; Give window time to fully initialize + Sleep(500) + return hwnd + } + Sleep(100) + } + return 0 +} + +; Get the client area position of the designer window +; Returns {x, y, w, h} or 0 if window not found +GetClientArea(hwnd := 0) { + if (hwnd = 0) + hwnd := FindDesignerWindow() + if (!hwnd) + return 0 + + ; Get window position + WinGetPos(&winX, &winY, &winW, &winH, hwnd) + + ; Get client area - for GLFW windows, typically minimal border + ; Client area starts after title bar + ; We'll estimate based on typical Windows decorations + ; Title bar is usually ~30-40 pixels, borders ~8 pixels each side + + ; For a more accurate approach, we could use DllCall to GetClientRect + ; But for GLFW windows, the client area often matches the requested size + + ; Calculate client area assuming standard decorations + borderWidth := 8 + titleHeight := 31 + + return { + x: winX + borderWidth, + y: winY + titleHeight, + w: PHONE_WIDTH, + h: PHONE_HEIGHT + } +} + +; Convert phone coordinates to screen coordinates +PhoneToScreen(phoneX, phoneY, hwnd := 0) { + client := GetClientArea(hwnd) + if (!client) + return 0 + + return { + x: client.x + phoneX, + y: client.y + phoneY + } +} + +; Click at phone coordinates +ClickPhone(phoneX, phoneY, hwnd := 0) { + if (hwnd = 0) + hwnd := FindDesignerWindow() + if (!hwnd) { + LogMessage("ERROR: Designer window not found") + return false + } + + ; Activate window first + WinActivate(hwnd) + Sleep(50) + + screen := PhoneToScreen(phoneX, phoneY, hwnd) + if (!screen) { + LogMessage("ERROR: Could not convert coordinates") + return false + } + + ; Move and click + Click(screen.x, screen.y) + Sleep(CLICK_DELAY) + + LogMessage("Clicked at phone(" . phoneX . "," . phoneY . ") -> screen(" . screen.x . "," . screen.y . ")") + return true +} + +; Click on a named app icon +ClickApp(appName, hwnd := 0) { + if (!APP_POSITIONS.Has(appName)) { + LogMessage("ERROR: Unknown app: " . appName) + return false + } + + pos := APP_POSITIONS[appName] + LogMessage("Clicking app: " . appName) + return ClickPhone(pos.x, pos.y, hwnd) +} + +; Click on a dock item +ClickDock(appName, hwnd := 0) { + if (!DOCK_POSITIONS.Has(appName)) { + LogMessage("ERROR: Unknown dock item: " . appName) + return false + } + + pos := DOCK_POSITIONS[appName] + LogMessage("Clicking dock: " . appName) + return ClickPhone(pos.x, pos.y, hwnd) +} + +; Send keyboard input to the designer +SendToDesigner(keys, hwnd := 0) { + if (hwnd = 0) + hwnd := FindDesignerWindow() + if (!hwnd) + return false + + WinActivate(hwnd) + Sleep(50) + Send(keys) + return true +} + +; Read the log file and check for a pattern +; Returns the matching line or empty string +CheckLogFor(pattern, logFile := 0) { + if (logFile = 0) + logFile := LOG_FILE + + if (!FileExist(logFile)) + return "" + + content := FileRead(logFile) + lines := StrSplit(content, "`n") + + ; Search from end (most recent) + loop lines.Length { + idx := lines.Length - A_Index + 1 + line := lines[idx] + if (InStr(line, pattern)) + return line + } + + return "" +} + +; Wait for a specific pattern to appear in the log +WaitForLog(pattern, timeout := 0, logFile := 0) { + if (timeout = 0) + timeout := NAVIGATION_TIMEOUT + if (logFile = 0) + logFile := LOG_FILE + + startTime := A_TickCount + while (A_TickCount - startTime < timeout) { + result := CheckLogFor(pattern, logFile) + if (result) + return result + Sleep(100) + } + return "" +} + +; Wait for navigation to a specific screen +WaitForNavigation(screenName, timeout := 0) { + pattern := "Navigated to: " . screenName + return WaitForLog(pattern, timeout) +} + +; Wait for back navigation +WaitForBack(timeout := 0) { + return WaitForLog("Back to:", timeout) +} + +; Take a screenshot of the designer window +CaptureScreenshot(filename, hwnd := 0) { + if (hwnd = 0) + hwnd := FindDesignerWindow() + if (!hwnd) + return false + + ; Ensure screenshot directory exists + if (!DirExist(SCREENSHOT_DIR)) + DirCreate(SCREENSHOT_DIR) + + fullPath := SCREENSHOT_DIR . "\" . filename + + ; Use built-in screenshot capability + ; Note: This captures the entire window including decorations + try { + ; Activate and bring to front + WinActivate(hwnd) + Sleep(100) + + ; Get window position + WinGetPos(&x, &y, &w, &h, hwnd) + + ; Use GDI+ or Windows API for screenshot + ; For simplicity, we'll use the Snipping approach + ; In production, you'd use a proper GDI+ screenshot + + LogMessage("Screenshot requested: " . fullPath . " (not implemented)") + return false + } catch as e { + LogMessage("Screenshot error: " . e.Message) + return false + } +} + +; Log a message with timestamp +LogMessage(msg) { + timestamp := FormatTime(, "yyyy-MM-dd HH:mm:ss") + line := "[" . timestamp . "] " . msg . "`n" + + ; Write to console + OutputDebug(line) + + ; Also append to a test log file + testLog := A_ScriptDir . "\test_run.log" + FileAppend(line, testLog) +} + +; Clear test logs +ClearLogs() { + testLog := A_ScriptDir . "\test_run.log" + if (FileExist(testLog)) + FileDelete(testLog) + if (FileExist(LOG_FILE)) + FileDelete(LOG_FILE) +} + +; Close the designer window +CloseDesigner(hwnd := 0) { + if (hwnd = 0) + hwnd := FindDesignerWindow() + if (hwnd) { + WinClose(hwnd) + Sleep(500) + } +} + +; Kill any running designer processes +KillDesigner() { + try { + Run('taskkill /F /IM mosis-designer.exe', , "Hide") + } + Sleep(500) +} + +; Start the designer process with log redirection +; Returns the process ID or 0 on failure +StartDesigner(rmlFile := 0) { + if (rmlFile = 0) + rmlFile := HOME_RML + + ; Build command with output redirection + cmd := '"' . DESIGNER_EXE . '" "' . rmlFile . '"' + + LogMessage("Starting designer: " . cmd) + + ; Start process and capture output + ; We'll redirect to a log file + fullCmd := A_ComSpec . ' /c "' . cmd . '" > "' . LOG_FILE . '" 2>&1' + + Run(fullCmd, A_ScriptDir, "Hide", &pid) + + if (pid) { + LogMessage("Designer started with PID: " . pid) + return pid + } + + LogMessage("ERROR: Failed to start designer") + return 0 +} + +; Assert helper - logs pass/fail and returns result +Assert(condition, testName) { + if (condition) { + LogMessage("PASS: " . testName) + return true + } else { + LogMessage("FAIL: " . testName) + return false + } +} diff --git a/designer/test/run_tests.ahk b/designer/test/run_tests.ahk new file mode 100644 index 0000000..f6f1d37 --- /dev/null +++ b/designer/test/run_tests.ahk @@ -0,0 +1,65 @@ +; Mosis Designer Test Runner +; AutoHotkey v2 +; Runs all test scripts and generates a report + +#Requires AutoHotkey v2.0 +#Include "lib\utils.ahk" + +; Test scripts to run +global testScripts := [ + "test_navigation.ahk" +] + +; Run all tests +RunAllTests() { + LogMessage("========================================") + LogMessage("MOSIS DESIGNER TEST SUITE") + LogMessage("========================================") + + totalPassed := 0 + totalFailed := 0 + + ; Clean up + KillDesigner() + ClearLogs() + + for script in testScripts { + LogMessage("") + LogMessage("Running: " . script) + LogMessage("----------------------------------------") + + scriptPath := A_ScriptDir . "\" . script + + if (!FileExist(scriptPath)) { + LogMessage("ERROR: Script not found: " . scriptPath) + totalFailed++ + continue + } + + ; Run the test script + try { + ahkPath := "C:\Program Files\AutoHotkey\v2\AutoHotkey64.exe" + RunWait('"' . ahkPath . '" "' . scriptPath . '"', A_ScriptDir) + } catch as e { + LogMessage("ERROR running script: " . e.Message) + totalFailed++ + } + + ; Brief pause between tests + Sleep(1000) + } + + ; Final cleanup + KillDesigner() + + ; Generate report + LogMessage("") + LogMessage("========================================") + LogMessage("TEST SUITE COMPLETE") + LogMessage("Check test_run.log for detailed results") + LogMessage("========================================") +} + +; Main +RunAllTests() +ExitApp() diff --git a/designer/test/simple_test.ahk b/designer/test/simple_test.ahk new file mode 100644 index 0000000..bc98b0e --- /dev/null +++ b/designer/test/simple_test.ahk @@ -0,0 +1,21 @@ +; Simple window finder test +#Requires AutoHotkey v2.0 + +; Write to stdout (will show in console) +OutputDebug("Starting simple test`n") + +; Try to find the window +hwnd := WinExist("Mosis Designer") + +if (hwnd) { + WinGetPos(&x, &y, &w, &h, hwnd) + msg := "Found! X:" . x . " Y:" . y . " W:" . w . " H:" . h +} else { + msg := "Window not found" +} + +; Write result to a file +outFile := A_ScriptDir . "\simple_result.txt" +FileAppend(msg . "`n", outFile, "UTF-8") + +ExitApp() diff --git a/designer/test/test_interactive.ahk b/designer/test/test_interactive.ahk new file mode 100644 index 0000000..e67aaee --- /dev/null +++ b/designer/test/test_interactive.ahk @@ -0,0 +1,110 @@ +; Mosis Designer Interactive Test Helper +; AutoHotkey v2 +; Launches designer and shows coordinates as you click + +#Requires AutoHotkey v2.0 +#Include "lib\utils.ahk" + +; Global state +global isRunning := true +global hwnd := 0 + +; Create a simple GUI to show status +CreateStatusGui() { + global statusGui, statusText, coordText + + statusGui := Gui("+AlwaysOnTop", "Mosis Test Helper") + statusGui.SetFont("s10") + statusGui.Add("Text", "w300", "Designer Status:") + statusText := statusGui.Add("Text", "w300 h20", "Not running") + statusGui.Add("Text", "w300", "Mouse Position (Phone Coords):") + coordText := statusGui.Add("Text", "w300 h20", "N/A") + statusGui.Add("Text", "w300", "") + statusGui.Add("Button", "w100", "Refresh").OnEvent("Click", RefreshStatus) + statusGui.Add("Button", "x+10 w100", "Click Test").OnEvent("Click", DoClickTest) + statusGui.Add("Button", "x+10 w80", "Exit").OnEvent("Click", (*) => ExitApp()) + + statusGui.OnEvent("Close", (*) => ExitApp()) + statusGui.Show("x10 y10") +} + +; Refresh status display +RefreshStatus(*) { + global hwnd, statusText + + hwnd := FindDesignerWindow() + if (hwnd) { + statusText.Value := "Running (hwnd: " . hwnd . ")" + } else { + statusText.Value := "Not running" + } +} + +; Perform a test click at center of phone +DoClickTest(*) { + global hwnd + + hwnd := FindDesignerWindow() + if (!hwnd) { + MsgBox("Designer window not found!") + return + } + + ; Click center of phone + ClickPhone(270, 480, hwnd) + MsgBox("Clicked at phone center (270, 480)") +} + +; Update coordinate display based on mouse position +UpdateCoordinates() { + global hwnd, coordText + + if (!hwnd) { + hwnd := FindDesignerWindow() + if (!hwnd) { + coordText.Value := "Window not found" + return + } + } + + ; Get mouse position + CoordMode("Mouse", "Screen") + MouseGetPos(&mouseX, &mouseY) + + ; Get client area + client := GetClientArea(hwnd) + if (!client) { + coordText.Value := "Could not get client area" + return + } + + ; Calculate phone coordinates + phoneX := mouseX - client.x + phoneY := mouseY - client.y + + ; Check if within phone bounds + if (phoneX >= 0 && phoneX < PHONE_WIDTH && phoneY >= 0 && phoneY < PHONE_HEIGHT) { + coordText.Value := "X: " . phoneX . " Y: " . phoneY + } else { + coordText.Value := "Outside phone area" + } +} + +; Timer to update coordinates +SetTimer(UpdateCoordinates, 100) + +; Main +LogMessage("Starting interactive test helper") + +; Start designer if not running +if (!FindDesignerWindow()) { + LogMessage("Starting designer...") + StartDesigner() + Sleep(3000) +} + +CreateStatusGui() +RefreshStatus() + +; Keep running +return diff --git a/designer/test/test_manual_clicks.ahk b/designer/test/test_manual_clicks.ahk new file mode 100644 index 0000000..fe2d73c --- /dev/null +++ b/designer/test/test_manual_clicks.ahk @@ -0,0 +1,77 @@ +; Mosis Designer Manual Click Test +; AutoHotkey v2 +; Tests clicking on specific app icons with an already-running designer + +#Requires AutoHotkey v2.0 +#Include "lib\utils.ahk" + +; Wait for designer to be running +LogMessage("Looking for designer window...") +LogMessage("Please start the designer manually if not running:") +LogMessage(" cd designer && build\\Debug\\mosis-designer.exe ..\\src\\main\\assets\\apps\\home\\home.rml") + +hwnd := WaitForDesignerWindow(30000) ; 30 second timeout + +if (!hwnd) { + MsgBox("Designer window not found after 30 seconds. Exiting.") + ExitApp() +} + +LogMessage("Found designer window: " . hwnd) + +; Get and display client area info +client := GetClientArea(hwnd) +if (client) { + LogMessage("Client area: x=" . client.x . " y=" . client.y . " w=" . client.w . " h=" . client.h) +} + +; Interactive test loop +MsgBox("Designer found! Click OK to start click tests.`n`nWe will click on the Phone app icon.") + +; Test 1: Click Phone app +LogMessage("Test 1: Clicking Phone app at (67, 120)") +ClickPhone(67, 120, hwnd) +Sleep(500) + +result := MsgBox("Did the screen change to the Dialer?", "Verify", "YesNo") +if (result = "Yes") { + LogMessage("Test 1 PASSED: Phone app click worked") +} else { + LogMessage("Test 1 FAILED: Phone app click did not work") +} + +; Give user time to see result +Sleep(1000) + +; Test 2: Click Messages dock +MsgBox("Now testing Messages dock item. Click OK to continue.") +LogMessage("Test 2: Clicking Messages dock at (202, 920)") +ClickPhone(202, 920, hwnd) +Sleep(500) + +result := MsgBox("Did the screen change to Messages?", "Verify", "YesNo") +if (result = "Yes") { + LogMessage("Test 2 PASSED: Messages dock click worked") +} else { + LogMessage("Test 2 FAILED: Messages dock click did not work") +} + +Sleep(1000) + +; Test 3: Different coordinates for Messages (in case dock is higher) +MsgBox("Testing alternative dock position. Click OK to continue.") +LogMessage("Test 3: Clicking Messages at alternative position (202, 880)") +ClickPhone(202, 880, hwnd) +Sleep(500) + +result := MsgBox("Did anything happen?", "Verify", "YesNo") +if (result = "Yes") { + LogMessage("Test 3: Alternative position worked better") +} else { + LogMessage("Test 3: Alternative position also failed") +} + +; Summary +MsgBox("Tests complete! Check test_run.log for results.`n`nPath: " . A_ScriptDir . "\test_run.log") + +ExitApp() diff --git a/designer/test/test_navigation.ahk b/designer/test/test_navigation.ahk new file mode 100644 index 0000000..ce17c7e --- /dev/null +++ b/designer/test/test_navigation.ahk @@ -0,0 +1,194 @@ +; Mosis Designer Navigation Test +; AutoHotkey v2 +; Tests: Click on app icons and verify navigation occurs + +#Requires AutoHotkey v2.0 +#Include "lib\utils.ahk" + +; Test configuration +testsPassed := 0 +testsFailed := 0 + +; Main test function +RunNavigationTests() { + global testsPassed, testsFailed + + LogMessage("========================================") + LogMessage("Starting Navigation Tests") + LogMessage("========================================") + + ; Clean up any previous runs + KillDesigner() + ClearLogs() + + ; Start the designer + pid := StartDesigner() + if (!pid) { + LogMessage("FATAL: Could not start designer") + return false + } + + ; Wait for window to appear + LogMessage("Waiting for designer window...") + hwnd := WaitForDesignerWindow() + if (!hwnd) { + LogMessage("FATAL: Designer window did not appear") + KillDesigner() + return false + } + LogMessage("Designer window found: " . hwnd) + + ; Wait for document to load + Sleep(2000) ; Give time for initial document load + + ; Check log for successful load + if (!WaitForLog("Document loaded successfully", 5000)) { + LogMessage("WARNING: Did not detect document load confirmation") + } + + ; Run individual tests + TestClickDialer(hwnd) + TestClickMessages(hwnd) + TestClickContacts(hwnd) + TestClickSettings(hwnd) + + ; Clean up + LogMessage("Closing designer...") + CloseDesigner(hwnd) + Sleep(500) + + ; Report results + LogMessage("========================================") + LogMessage("Navigation Tests Complete") + LogMessage("Passed: " . testsPassed) + LogMessage("Failed: " . testsFailed) + LogMessage("========================================") + + return (testsFailed = 0) +} + +; Test: Click on Phone/Dialer app +TestClickDialer(hwnd) { + global testsPassed, testsFailed + + LogMessage("--- Test: Click Dialer ---") + + ; First go home to ensure clean state + ; (In future, could send Home key) + Sleep(500) + + ; Click the phone app + if (!ClickApp("phone", hwnd)) { + testsFailed++ + return + } + + Sleep(1000) + + ; Check log for navigation + ; Note: The log might say "dialer" because that's the screen name + result := CheckLogFor("Navigated to: dialer") + if (result) { + LogMessage("Navigation confirmed: " . result) + testsPassed++ + } else { + ; Also check if screen was loaded + result := CheckLogFor("Loaded screen: apps/dialer") + if (result) { + LogMessage("Screen load confirmed: " . result) + testsPassed++ + } else { + LogMessage("Navigation not detected in log") + testsFailed++ + } + } +} + +; Test: Click on Messages app +TestClickMessages(hwnd) { + global testsPassed, testsFailed + + LogMessage("--- Test: Click Messages ---") + + ; We're likely on dialer now, need to go back first + ; For now, just click messages dock item + Sleep(500) + + if (!ClickDock("messages", hwnd)) { + testsFailed++ + return + } + + Sleep(1000) + + result := CheckLogFor("Loaded screen: apps/messages") + if (result) { + LogMessage("Screen load confirmed: " . result) + testsPassed++ + } else { + LogMessage("Navigation not detected in log") + testsFailed++ + } +} + +; Test: Click on Contacts app +TestClickContacts(hwnd) { + global testsPassed, testsFailed + + LogMessage("--- Test: Click Contacts ---") + Sleep(500) + + if (!ClickDock("contacts", hwnd)) { + testsFailed++ + return + } + + Sleep(1000) + + result := CheckLogFor("Loaded screen: apps/contacts") + if (result) { + LogMessage("Screen load confirmed: " . result) + testsPassed++ + } else { + LogMessage("Navigation not detected in log") + testsFailed++ + } +} + +; Test: Click on Settings app +TestClickSettings(hwnd) { + global testsPassed, testsFailed + + LogMessage("--- Test: Click Settings ---") + Sleep(500) + + ; Settings is on the main grid, not dock + ; But we're on contacts now, so we need to navigate + ; For this test, we'll just verify the click mechanics work + + if (!ClickApp("settings", hwnd)) { + ; This will likely fail since we're not on home screen + ; But let's see what happens + LogMessage("Click attempted (may fail if not on home)") + testsFailed++ + return + } + + Sleep(1000) + + result := CheckLogFor("Loaded screen: apps/settings") + if (result) { + LogMessage("Screen load confirmed: " . result) + testsPassed++ + } else { + LogMessage("Navigation not detected (expected if not on home screen)") + testsFailed++ + } +} + +; Run the tests +RunNavigationTests() + +; Keep script alive briefly for logging +Sleep(1000) +ExitApp() diff --git a/designer/test/visual_click_test.ahk b/designer/test/visual_click_test.ahk new file mode 100644 index 0000000..c3f0e78 --- /dev/null +++ b/designer/test/visual_click_test.ahk @@ -0,0 +1,124 @@ +; Visual click test - verifies clicks by window interaction +#Requires AutoHotkey v2.0 + +; Configuration +DESIGNER_EXE := A_ScriptDir . "\..\build\Debug\mosis-designer.exe" +HOME_RML := A_ScriptDir . "\..\..\src\main\assets\apps\home\home.rml" +WINDOW_TITLE := "Mosis Designer" + +resultFile := A_ScriptDir . "\visual_test_result.txt" +if (FileExist(resultFile)) + FileDelete(resultFile) + +Log(msg) { + global resultFile + FileAppend(msg . "`n", resultFile, "UTF-8") +} + +Log("=== Visual Click Test ===") +Log("Time: " . FormatTime(, "yyyy-MM-dd HH:mm:ss")) + +; Check if designer is running, if not start it +hwnd := WinExist(WINDOW_TITLE) +if (!hwnd) { + Log("Starting designer...") + Run(DESIGNER_EXE . ' "' . HOME_RML . '"', A_ScriptDir) + + ; Wait for window + startTime := A_TickCount + while (A_TickCount - startTime < 15000) { + hwnd := WinExist(WINDOW_TITLE) + if (hwnd) + break + Sleep(200) + } + + if (!hwnd) { + Log("ERROR: Window did not appear") + ExitApp(1) + } + Log("Designer started") + Sleep(3000) ; Wait for full initialization +} else { + Log("Using existing designer window") +} + +Log("Window HWND: " . hwnd) + +; Get window info +WinGetPos(&winX, &winY, &winW, &winH, hwnd) +Log("Window: " . winW . "x" . winH . " at (" . winX . "," . winY . ")") + +; Calculate scale and client area +scale := (winW / 556 + winH / 1000) / 2 +titleBar := Round(31 * scale) +border := Round(8 * scale) +clientX := winX + border +clientY := winY + titleBar +Log("Scale: " . scale . ", Client offset: (" . border . "," . titleBar . ")") + +; Activate window +WinActivate(hwnd) +WinWaitActive(hwnd, , 3) +Sleep(500) + +Log("") +Log("=== Click Sequence ===") + +; Function to click +DoClick(phoneX, phoneY, name) { + global clientX, clientY, scale, hwnd + + screenX := Round(clientX + phoneX * scale) + screenY := Round(clientY + phoneY * scale) + + Log("Clicking " . name . " at (" . phoneX . "," . phoneY . ") -> screen(" . screenX . "," . screenY . ")") + + ; Make sure window is active + WinActivate(hwnd) + Sleep(100) + + ; Move mouse first, then click + MouseMove(screenX, screenY) + Sleep(50) + Click() + Sleep(100) + + Log("Click sent") +} + +; Test sequence: +; 1. Click on Phone app (should navigate to dialer) +Log("") +Log("Step 1: Click Phone app icon") +DoClick(67, 120, "Phone") +Sleep(2000) + +; 2. Click somewhere to go back (simulate back button or click home dock) +Log("") +Log("Step 2: Wait and observe...") +Sleep(1000) + +; 3. Click on Messages dock +Log("") +Log("Step 3: Click Messages dock") +DoClick(202, 920, "Messages Dock") +Sleep(2000) + +; 4. Click on Contacts dock +Log("") +Log("Step 4: Click Contacts dock") +DoClick(337, 920, "Contacts Dock") +Sleep(2000) + +Log("") +Log("=== Test Sequence Complete ===") +Log("Please verify visually that navigation occurred:") +Log("- After Phone click: Should show Dialer screen") +Log("- After Messages click: Should show Messages screen") +Log("- After Contacts click: Should show Contacts screen") +Log("") +Log("Leaving designer running for manual inspection.") +Log("Close it manually when done.") + +ExitApp(0) diff --git a/designer/vcpkg.json b/designer/vcpkg.json new file mode 100644 index 0000000..19be15f --- /dev/null +++ b/designer/vcpkg.json @@ -0,0 +1,12 @@ +{ + "name": "mosis-designer", + "version-string": "0.1.0", + "description": "Desktop designer and testing tool for Mosis virtual smartphone UI", + "dependencies": [ + "glfw3", + "freetype", + "lua", + "libpng", + "nlohmann-json" + ] +} diff --git a/src/main/assets/LatoLatin-Bold.ttf b/src/main/assets/LatoLatin-Bold.ttf index c598c24..d4eebf8 100644 Binary files a/src/main/assets/LatoLatin-Bold.ttf and b/src/main/assets/LatoLatin-Bold.ttf differ diff --git a/src/main/assets/LatoLatin-BoldItalic.ttf b/src/main/assets/LatoLatin-BoldItalic.ttf index c1f225a..e08a8c5 100644 Binary files a/src/main/assets/LatoLatin-BoldItalic.ttf and b/src/main/assets/LatoLatin-BoldItalic.ttf differ diff --git a/src/main/assets/LatoLatin-Italic.ttf b/src/main/assets/LatoLatin-Italic.ttf index c61fc07..9e652d2 100644 Binary files a/src/main/assets/LatoLatin-Italic.ttf and b/src/main/assets/LatoLatin-Italic.ttf differ diff --git a/src/main/assets/LatoLatin-Regular.ttf b/src/main/assets/LatoLatin-Regular.ttf index bcc5778..98f07a0 100644 Binary files a/src/main/assets/LatoLatin-Regular.ttf and b/src/main/assets/LatoLatin-Regular.ttf differ diff --git a/src/main/assets/NotoEmoji-Regular.ttf b/src/main/assets/NotoEmoji-Regular.ttf index 19b7bad..ba617d8 100644 Binary files a/src/main/assets/NotoEmoji-Regular.ttf and b/src/main/assets/NotoEmoji-Regular.ttf differ diff --git a/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf b/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf index 978e53a..f595df0 100644 Binary files a/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf and b/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf differ diff --git a/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf b/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf index bba55f6..e714699 100644 Binary files a/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf and b/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Black.ttf b/src/main/assets/Roboto/static/Roboto-Black.ttf index d51221a..b8f4705 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Black.ttf and b/src/main/assets/Roboto/static/Roboto-Black.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf b/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf index c71c549..bb7cbde 100644 Binary files a/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf and b/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Bold.ttf b/src/main/assets/Roboto/static/Roboto-Bold.ttf index 4658f9a..7f05e62 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Bold.ttf and b/src/main/assets/Roboto/static/Roboto-Bold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf b/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf index 2ee0765..042943e 100644 Binary files a/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf b/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf index 7092a88..439ed0a 100644 Binary files a/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf and b/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf index a5536f5..6ecd6a2 100644 Binary files a/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf b/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf index 75608c6..e547e08 100644 Binary files a/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf and b/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf b/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf index 23dbbef..0778536 100644 Binary files a/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf and b/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Italic.ttf b/src/main/assets/Roboto/static/Roboto-Italic.ttf index c3abaef..47fa324 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Italic.ttf and b/src/main/assets/Roboto/static/Roboto-Italic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Light.ttf b/src/main/assets/Roboto/static/Roboto-Light.ttf index 6fcd5f9..fba2960 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Light.ttf and b/src/main/assets/Roboto/static/Roboto-Light.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-LightItalic.ttf b/src/main/assets/Roboto/static/Roboto-LightItalic.ttf index a6e5047..ff88d91 100644 Binary files a/src/main/assets/Roboto/static/Roboto-LightItalic.ttf and b/src/main/assets/Roboto/static/Roboto-LightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Medium.ttf b/src/main/assets/Roboto/static/Roboto-Medium.ttf index d629e98..d6d58d8 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Medium.ttf and b/src/main/assets/Roboto/static/Roboto-Medium.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf b/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf index ef9ed1b..0370bd7 100644 Binary files a/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf and b/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Regular.ttf b/src/main/assets/Roboto/static/Roboto-Regular.ttf index 7e3bb2f..58d136d 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Regular.ttf and b/src/main/assets/Roboto/static/Roboto-Regular.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-SemiBold.ttf b/src/main/assets/Roboto/static/Roboto-SemiBold.ttf index 3f34834..682d94f 100644 Binary files a/src/main/assets/Roboto/static/Roboto-SemiBold.ttf and b/src/main/assets/Roboto/static/Roboto-SemiBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf index 132cca1..44b003e 100644 Binary files a/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Thin.ttf b/src/main/assets/Roboto/static/Roboto-Thin.ttf index 6ee97b8..03e4834 100644 Binary files a/src/main/assets/Roboto/static/Roboto-Thin.ttf and b/src/main/assets/Roboto/static/Roboto-Thin.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf b/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf index 0381198..346e8ad 100644 Binary files a/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf and b/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf index 7529d1b..fdf814b 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf index 0c31e9f..16ead45 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf index a7c3cdf..440da9b 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf index 24513a5..80a79cd 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf index 782442a..2522023 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf index aeff7c2..3c525aa 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf index 16a1560..7698db9 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf index 0f6fe70..b4ce283 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf index 3b387eb..3d8348d 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf index e70c357..7ec4cc3 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf index 9f623e0..4c9df44 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf index dd2842b..6040f8f 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf index 80ff64e..4b45e96 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf index 5af42d4..f0e146e 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf index 4297f17..e5935da 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf index 6cb4656..9dc7237 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf index 1ccebcc..d47a419 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf index e58e966..c1eb656 100644 Binary files a/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf and b/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf index 8eedb64..5b0f577 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf index 19a5096..2497283 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf index 47d0afa..1b13a19 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf index 319f62e..e3aef53 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf index 36423c3..0720df2 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf index b40ce77..2a04c95 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf index e1c25a0..cdc967f 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf index 929a093..1d609b1 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf index 23454ff..671d00b 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf index b9aedcd..8278241 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf index c096473..35140d5 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf index e9c34d6..a0dedd3 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf index ab34b70..fe4ce03 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf index 36109ba..865cfd4 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf index 6d10b33..8f37536 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf index e88bc4a..4be2247 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf index 8ed8d79..7ee5ba4 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf index 81afeea..1bb36f6 100644 Binary files a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf differ diff --git a/src/main/assets/alien_small.tga b/src/main/assets/alien_small.tga index 6d7c53d..a223a28 100644 Binary files a/src/main/assets/alien_small.tga and b/src/main/assets/alien_small.tga differ diff --git a/src/main/assets/apps/browser/browser.rml b/src/main/assets/apps/browser/browser.rml new file mode 100644 index 0000000..220bb94 --- /dev/null +++ b/src/main/assets/apps/browser/browser.rml @@ -0,0 +1,256 @@ + + + + + + + Browser + + + + +
+ 12:30 +
+ * + + + | +
+
+ + +
+
+ +
+
+ +
+
+ 🔒 + +
+
+ +
+
+ +
+
+ + +
+
+
{{ page_title }}
+
+ {{ page_content }} +
+
+ More information... +
+ +
+
Related Links
+
+
IANA — IANA-managed Reserved Domains
+
www.iana.org > domains > reserved
+
Certain domains are set aside and unavailable for registration. Learn about reserved top-level domains.
+
+
+
RFC 2606 - Reserved Top Level DNS Names
+
tools.ietf.org > html > rfc2606
+
This document describes some domain names that are reserved for documentation purposes.
+
+
+
+
+ + +
+
+ + Home +
+
+ {{ tabs.size }} + Tabs +
+
+ + New Tab +
+
+ + Menu +
+
+ +
diff --git a/src/main/assets/apps/contacts/contact_detail.rml b/src/main/assets/apps/contacts/contact_detail.rml new file mode 100644 index 0000000..944a4c8 --- /dev/null +++ b/src/main/assets/apps/contacts/contact_detail.rml @@ -0,0 +1,161 @@ + + + + + + + + + Contact + + + + +
+
+ Contact +
+
+ + +
+
?
+
Contact Name
+ +
+
+
+ +
+ Call +
+
+
+ +
+ Message +
+
+
+ + +
+
+ +
+
Mobile
+
+1 (555) 000-0000
+
+
+
+ +
+
Email
+
email@example.com
+
+
+
+ +
diff --git a/src/main/assets/apps/contacts/contacts.rml b/src/main/assets/apps/contacts/contacts.rml new file mode 100644 index 0000000..ca9d726 --- /dev/null +++ b/src/main/assets/apps/contacts/contacts.rml @@ -0,0 +1,141 @@ + + + + + + + Contacts + + + + +
+
+ Contacts +
+
+ + + + + +
+
+
+
{{ contact.initial }}
+
+
{{ contact.name }}
+
{{ contact.phone }}
+
+
+ +
+
+
+
+ + +
+ + +
+
+ + Keypad +
+
+ + Recent +
+
+ + Contacts +
+
+ +
diff --git a/src/main/assets/apps/dialer/calling.rml b/src/main/assets/apps/dialer/calling.rml new file mode 100644 index 0000000..edda099 --- /dev/null +++ b/src/main/assets/apps/dialer/calling.rml @@ -0,0 +1,126 @@ + + + + + + + + Calling + + + +
Calling...
+
Unknown
+
+ +
?
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
diff --git a/src/main/assets/apps/dialer/dialer.rml b/src/main/assets/apps/dialer/dialer.rml new file mode 100644 index 0000000..2809840 --- /dev/null +++ b/src/main/assets/apps/dialer/dialer.rml @@ -0,0 +1,199 @@ + + + + + + + Phone + + + + +
+
+ Phone +
+ + +
+
Keypad
+
Recent
+
Contacts
+
+ + +
{{ dial_number }}
+ + +
+
+ 1 + +
+
+ 2 + ABC +
+
+ 3 + DEF +
+
+ 4 + GHI +
+
+ 5 + JKL +
+
+ 6 + MNO +
+
+ 7 + PQRS +
+
+ 8 + TUV +
+
+ 9 + WXYZ +
+
+ * + +
+
+ 0 + + +
+
+ # + +
+
+ + +
+
+
+
+
+ + +
+
+ + Keypad +
+
+ + Recent +
+
+ + Contacts +
+
+ +
diff --git a/src/main/assets/apps/home/home.rml b/src/main/assets/apps/home/home.rml new file mode 100644 index 0000000..f7237cd --- /dev/null +++ b/src/main/assets/apps/home/home.rml @@ -0,0 +1,135 @@ + + + + + + + Virtual Smartphone - Home + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+
+ Phone +
+
+
+ Messages +
+
+
+ Contacts +
+
+
+ Browser +
+ + +
+
+ Gallery +
+
+
+ Camera +
+
+
+ Settings +
+
+
+ Music +
+ + +
+
+ Calendar +
+
+
+ Clock +
+
+
+ Notes +
+
+
+ Maps +
+ + +
+
+ Store +
+
+
+ Files +
+
+
+ Calculator +
+
+
+ Weather +
+
+
+ + +
+
+
+
+
+
+ +
diff --git a/src/main/assets/apps/home/lock.rml b/src/main/assets/apps/home/lock.rml new file mode 100644 index 0000000..62c4609 --- /dev/null +++ b/src/main/assets/apps/home/lock.rml @@ -0,0 +1,176 @@ + + + + + + + Lock Screen + + + + +
+ 12:30 +
+ * + + + | +
+
+ + +
+
12:30
+
Wednesday, January 15
+ + +
+
+
M
+
+
Messages
+
John: Hey, are you coming to...
+
+
+
+
P
+
+
Missed Call
+
Mom - 10 minutes ago
+
+
+
+
+ + +
+
^
+
Swipe up to unlock
+
+ + +
+
+
+
+ +
diff --git a/src/main/assets/apps/messages/chat.rml b/src/main/assets/apps/messages/chat.rml new file mode 100644 index 0000000..e6786b0 --- /dev/null +++ b/src/main/assets/apps/messages/chat.rml @@ -0,0 +1,164 @@ + + + + + + + + Chat + + + + +
+
+
J
+
+
John Wilson
+
Online
+
+
+
+
+ + +
+
Hey!
+
What are you up to?
+
Not much, just working
+
Cool! There's a party at Mike's tonight
+
Hey, are you coming to the party tonight?
+
+ + +
+
+ +
+ +
+
+ +
diff --git a/src/main/assets/apps/messages/messages.rml b/src/main/assets/apps/messages/messages.rml new file mode 100644 index 0000000..8020bc9 --- /dev/null +++ b/src/main/assets/apps/messages/messages.rml @@ -0,0 +1,117 @@ + + + + + + + Messages + + + + +
+
+ Messages +
+
+ + +
+
+
{{ conv.name | slice(0, 1) }}
+
+
+ {{ conv.name }} + {{ conv.time }} +
+
{{ conv.last_message }}
+
+
{{ conv.unread }}
+
+
+ + +
+ +
diff --git a/src/main/assets/apps/settings/settings.rml b/src/main/assets/apps/settings/settings.rml new file mode 100644 index 0000000..93fdadf --- /dev/null +++ b/src/main/assets/apps/settings/settings.rml @@ -0,0 +1,298 @@ + + + + + + + Settings + + + + +
+
+ Settings +
+
+ + +
+ +
+
U
+ + > +
+ + +
+
Network
+
+
W
+
+
Wi-Fi
+
Connected to {{ wifi_network }}
+
Off
+
+
+
+
+
+
+
B
+
+
Bluetooth
+
On
+
Off
+
+
+
+
+
+
+
A
+
+
Airplane Mode
+
+
+
+
+
+
+ + +
+
Device
+
+
D
+
+
Display
+
Brightness, wallpaper, sleep
+
+ > +
+
+
S
+
+
Sound
+
Volume, ringtone, vibration
+
+ > +
+
+
N
+
+
Notifications
+
App notifications, Do not disturb
+
+ > +
+
+
B
+
+
Battery
+
{{ battery_percent }}% - {{ battery_remaining }}
+
+ > +
+
+
S
+
+
Storage
+
{{ storage_used }}
+
+ > +
+
+ + +
+
Privacy & Security
+
+
L
+
+
Lock Screen
+
PIN, pattern, fingerprint
+
+ > +
+
+
P
+
+
Privacy
+
Permissions, account activity
+
+ > +
+
+
L
+
+
Location
+
On - High accuracy
+
Off
+
+
+
+
+
+
+ + +
+
About
+
+
I
+
+
About Phone
+
Model, software version
+
+ > +
+
+
+ +
diff --git a/src/main/assets/fonts/LatoLatin-Bold.ttf b/src/main/assets/fonts/LatoLatin-Bold.ttf new file mode 100644 index 0000000..d4eebf8 --- /dev/null +++ b/src/main/assets/fonts/LatoLatin-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74dc638cf436cce77a0217a2a55ce7906a4cc9e4c595c6f1dcd9dd858078c1b6 +size 146156 diff --git a/src/main/assets/fonts/LatoLatin-BoldItalic.ttf b/src/main/assets/fonts/LatoLatin-BoldItalic.ttf new file mode 100644 index 0000000..e08a8c5 --- /dev/null +++ b/src/main/assets/fonts/LatoLatin-BoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:116bd3f939070d9df8c73acb76faa8f6b582bcce3c391eb6b3695f40e5ae404a +size 149756 diff --git a/src/main/assets/fonts/LatoLatin-Italic.ttf b/src/main/assets/fonts/LatoLatin-Italic.ttf new file mode 100644 index 0000000..9e652d2 --- /dev/null +++ b/src/main/assets/fonts/LatoLatin-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1121daa4b0f0510074f54b046b2a25016614cca419974b992e2b8a2b2231f5da +size 153688 diff --git a/src/main/assets/fonts/LatoLatin-Regular.ttf b/src/main/assets/fonts/LatoLatin-Regular.ttf new file mode 100644 index 0000000..98f07a0 --- /dev/null +++ b/src/main/assets/fonts/LatoLatin-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d785334ac4e7810f571def986bbad41161f68ac385db8813f798bf04d71478e1 +size 148540 diff --git a/src/main/assets/fonts/NotoEmoji-Regular.ttf b/src/main/assets/fonts/NotoEmoji-Regular.ttf new file mode 100644 index 0000000..ba617d8 --- /dev/null +++ b/src/main/assets/fonts/NotoEmoji-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:415dc6290378574135b64c808dc640c1df7531973290c4970c51fdeb849cb0c5 +size 418804 diff --git a/src/main/assets/fonts/Roboto/OFL.txt b/src/main/assets/fonts/Roboto/OFL.txt new file mode 100644 index 0000000..9c48e05 --- /dev/null +++ b/src/main/assets/fonts/Roboto/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/assets/fonts/Roboto/README.txt b/src/main/assets/fonts/Roboto/README.txt new file mode 100644 index 0000000..26368fa --- /dev/null +++ b/src/main/assets/fonts/Roboto/README.txt @@ -0,0 +1,118 @@ +Roboto Variable Font +==================== + +This download contains Roboto as both variable fonts and static fonts. + +Roboto is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + Roboto-VariableFont_wdth,wght.ttf + Roboto-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Roboto: + static/Roboto_Condensed-Thin.ttf + static/Roboto_Condensed-ExtraLight.ttf + static/Roboto_Condensed-Light.ttf + static/Roboto_Condensed-Regular.ttf + static/Roboto_Condensed-Medium.ttf + static/Roboto_Condensed-SemiBold.ttf + static/Roboto_Condensed-Bold.ttf + static/Roboto_Condensed-ExtraBold.ttf + static/Roboto_Condensed-Black.ttf + static/Roboto_SemiCondensed-Thin.ttf + static/Roboto_SemiCondensed-ExtraLight.ttf + static/Roboto_SemiCondensed-Light.ttf + static/Roboto_SemiCondensed-Regular.ttf + static/Roboto_SemiCondensed-Medium.ttf + static/Roboto_SemiCondensed-SemiBold.ttf + static/Roboto_SemiCondensed-Bold.ttf + static/Roboto_SemiCondensed-ExtraBold.ttf + static/Roboto_SemiCondensed-Black.ttf + static/Roboto-Thin.ttf + static/Roboto-ExtraLight.ttf + static/Roboto-Light.ttf + static/Roboto-Regular.ttf + static/Roboto-Medium.ttf + static/Roboto-SemiBold.ttf + static/Roboto-Bold.ttf + static/Roboto-ExtraBold.ttf + static/Roboto-Black.ttf + static/Roboto_Condensed-ThinItalic.ttf + static/Roboto_Condensed-ExtraLightItalic.ttf + static/Roboto_Condensed-LightItalic.ttf + static/Roboto_Condensed-Italic.ttf + static/Roboto_Condensed-MediumItalic.ttf + static/Roboto_Condensed-SemiBoldItalic.ttf + static/Roboto_Condensed-BoldItalic.ttf + static/Roboto_Condensed-ExtraBoldItalic.ttf + static/Roboto_Condensed-BlackItalic.ttf + static/Roboto_SemiCondensed-ThinItalic.ttf + static/Roboto_SemiCondensed-ExtraLightItalic.ttf + static/Roboto_SemiCondensed-LightItalic.ttf + static/Roboto_SemiCondensed-Italic.ttf + static/Roboto_SemiCondensed-MediumItalic.ttf + static/Roboto_SemiCondensed-SemiBoldItalic.ttf + static/Roboto_SemiCondensed-BoldItalic.ttf + static/Roboto_SemiCondensed-ExtraBoldItalic.ttf + static/Roboto_SemiCondensed-BlackItalic.ttf + static/Roboto-ThinItalic.ttf + static/Roboto-ExtraLightItalic.ttf + static/Roboto-LightItalic.ttf + static/Roboto-Italic.ttf + static/Roboto-MediumItalic.ttf + static/Roboto-SemiBoldItalic.ttf + static/Roboto-BoldItalic.ttf + static/Roboto-ExtraBoldItalic.ttf + static/Roboto-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/src/main/assets/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf b/src/main/assets/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..f595df0 --- /dev/null +++ b/src/main/assets/fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d3f965ee5c68c007dcf55fd712267d8025f92b6698e8bc13f76f83b9b7842e7 +size 497124 diff --git a/src/main/assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf b/src/main/assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..e714699 --- /dev/null +++ b/src/main/assets/fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b25986d18730960c7b27384ab2bc500856ae7fe9e71c9850019195ff9019f0b2 +size 468308 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Black.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Black.ttf new file mode 100644 index 0000000..b8f4705 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59cc48c96047bb30b250bb287f06906e468e49f92832bd473447d13a602aee61 +size 147424 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-BlackItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..bb7cbde --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-BlackItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54ec9e403016d8add6f84377952fbbde854ab102b9c2656b8ccc9eef9eee84a3 +size 153780 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Bold.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Bold.ttf new file mode 100644 index 0000000..7f05e62 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27467020febce4c0ac8482ea8e8d6f32bf8c5721fb9c301344d0e3f4d93a0119 +size 146768 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-BoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..042943e --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-BoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8234add45022e40d0ad293c8a2854a8f387e53db5c433698fe6d89c8d1b5362e +size 153104 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-ExtraBold.ttf b/src/main/assets/fonts/Roboto/static/Roboto-ExtraBold.ttf new file mode 100644 index 0000000..439ed0a --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f00b24deffcf55e888f9a9428313a761acc2c4d65df47d80a097a85e7a82ed9c +size 146900 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-ExtraBoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-ExtraBoldItalic.ttf new file mode 100644 index 0000000..6ecd6a2 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-ExtraBoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4845408266b07efd4345888c1dba3306b2963db25ca5c8d1edc9e70a63b2ec99 +size 153292 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-ExtraLight.ttf b/src/main/assets/fonts/Roboto/static/Roboto-ExtraLight.ttf new file mode 100644 index 0000000..e547e08 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2d88c093fcfab7dcc906a665e5bcb4c3b13858632fa3fd2c4380dfcf9aff3db +size 146044 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-ExtraLightItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-ExtraLightItalic.ttf new file mode 100644 index 0000000..0778536 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-ExtraLightItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:231a6f292ed37c341720923422d72bd566c1b1517e9885c78ea665ff6bba944c +size 151664 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Italic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Italic.ttf new file mode 100644 index 0000000..47fa324 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a2f18bddbe3ac07f14d38817d1fd22df4e8e3b74f661f7ae7b29cc8140092bc +size 152208 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Light.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Light.ttf new file mode 100644 index 0000000..fba2960 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b2e824beab63c438c2849bd6e1d4f6708cba5a5c5f3dbe8d5af7e9682ed9373 +size 146056 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-LightItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-LightItalic.ttf new file mode 100644 index 0000000..ff88d91 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-LightItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92ee7da87e6b5f389549914e0eb2cce4ec8f5c090947743957764436043d77d9 +size 151944 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Medium.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Medium.ttf new file mode 100644 index 0000000..d6d58d8 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:616eee3f9b60ec0196e09700ebca48a375da3323c4d5872e2b982170969ac90e +size 146132 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-MediumItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..0370bd7 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-MediumItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db79d54441d407613d583145f0637a5e614fc92d75fa26bda23a4fbf2fd596c8 +size 152416 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Regular.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Regular.ttf new file mode 100644 index 0000000..58d136d --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbd285b518e398832f6f4a736109c355ce25a49546bfce41bab256c9ef7e56eb +size 146004 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-SemiBold.ttf b/src/main/assets/fonts/Roboto/static/Roboto-SemiBold.ttf new file mode 100644 index 0000000..682d94f --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57b40c38d6cc65f07c23ec0319b82904fe65e05dc40b5a13ac60a18fb5943bf1 +size 146760 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-SemiBoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-SemiBoldItalic.ttf new file mode 100644 index 0000000..44b003e --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-SemiBoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64a7d4c4aff3fd6a56e77dbc4c8f2cfecd7ab4091eda6360f1c1a92cd1994b62 +size 153036 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-Thin.ttf b/src/main/assets/fonts/Roboto/static/Roboto-Thin.ttf new file mode 100644 index 0000000..03e4834 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0655f84356b7168bb342cf5aadc560524daf8e26091159faf92daeb33fc1bb0d +size 145936 diff --git a/src/main/assets/fonts/Roboto/static/Roboto-ThinItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..346e8ad --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto-ThinItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:400458c3b9558c9e8453d2728d3f684c053ca9773fba25ea15e3d889290b491c +size 151148 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Black.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Black.ttf new file mode 100644 index 0000000..fdf814b --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd04dbc794359e71ade6076c14843628f97e03d6e5f2b0dffaae8dccbfe0dede +size 147148 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-BlackItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-BlackItalic.ttf new file mode 100644 index 0000000..16ead45 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-BlackItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f81c8c439e58e77c3375075280e9a32b8b9d0a86bec8c25fae1299b91bd3448e +size 153680 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Bold.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Bold.ttf new file mode 100644 index 0000000..440da9b --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12cf15436ba8fcd04ea33eeb8a8f9d3ed71d2b820a6ed1a4eec0391ba22bcb3c +size 146580 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-BoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-BoldItalic.ttf new file mode 100644 index 0000000..80a79cd --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-BoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d25c6db6128aa2568d6c78e233f4de42c6dae6eec323eed6cddac87c87f56ee +size 152976 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraBold.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraBold.ttf new file mode 100644 index 0000000..2522023 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df00895eed1c8557090d159e206b7ec39b5a55e86f6a5347558986ea11fe79fe +size 146812 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..3c525aa --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:664703908ec0adbefc88e8e99eaedfd5e86ddf7c0d0ec3725b5483144a73619b +size 153324 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraLight.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraLight.ttf new file mode 100644 index 0000000..7698db9 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b29d3aca72fcc9a32f55d305ca9e38764fd5650f3aad0e1fa5f03408b262b21 +size 146004 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..b4ce283 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:545dd68ecb623b09df2b3fe3a7227923f31b3f06ac492f8cad65963563aebef0 +size 152120 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Italic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Italic.ttf new file mode 100644 index 0000000..3d8348d --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fe1c2390bbce960ebba517eec37659fdd1c7a59dd256da6274b5b4d88509575 +size 152248 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Light.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Light.ttf new file mode 100644 index 0000000..7ec4cc3 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f1a3f85852fafc3a198904f6efc1fce5ae1013775ac40da35bd52ec82d38e41 +size 145952 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-LightItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-LightItalic.ttf new file mode 100644 index 0000000..4c9df44 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-LightItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95dc4340eb0c35afa168bf57a15784ab923012319795cc9fc194e7f0d85cacb3 +size 152392 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Medium.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Medium.ttf new file mode 100644 index 0000000..6040f8f --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99e31250ef90f7aa3ed92f4d7719938ac243cbd5b2e67aa6e8646800769aed39 +size 145916 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-MediumItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-MediumItalic.ttf new file mode 100644 index 0000000..4b45e96 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-MediumItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:701adaa8c2a8e0993743c43ab757712655c0583bbcb6e7a439ba5279e7c08d81 +size 152424 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Regular.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Regular.ttf new file mode 100644 index 0000000..f0e146e --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8e6f7c5a1356d41dd82b0c00219febd21c791cc5cd1ecf8a0323cbeafe6496c +size 145908 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-SemiBold.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-SemiBold.ttf new file mode 100644 index 0000000..e5935da --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3e44e52c6b7273ec62c8342b204646c0cdb9b51117745d9fb0bbca163619571 +size 146516 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..9dc7237 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7dc7e6003a0fc6a727c050b1afda82043206cf4d94104963d99e47cd1a61c33 +size 152660 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Thin.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Thin.ttf new file mode 100644 index 0000000..d47a419 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ac880f31e4b0bf6fab2b9bf74d90f129b06b8bd40a214175d19b9d1f2ec6a59 +size 145920 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ThinItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ThinItalic.ttf new file mode 100644 index 0000000..c1eb656 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_Condensed-ThinItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6ce5cab994b9cf2b36949d288e1618be951b99282c17ae2948a4812e5ceed38 +size 151588 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Black.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Black.ttf new file mode 100644 index 0000000..5b0f577 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Black.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d108eb5133bd90edd338b26493ea1dd7f724e33b3e2114ea5d2fc7108da9493a +size 147372 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf new file mode 100644 index 0000000..2497283 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:531aaa47ff2cb438f5fc846c62a8872f6afd12a9f6e0f608407cfc6a3d28ea13 +size 153832 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Bold.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Bold.ttf new file mode 100644 index 0000000..1b13a19 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Bold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d95a77a4d72be0805d9c9e8ce42e87fe8bf413a83f8adc208c254f7a2bdd409c +size 146752 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf new file mode 100644 index 0000000..e3aef53 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5458a8abbf92fa33aa9fd1152d6be02721fdeb8336ace93d8ece68431085189 +size 153148 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf new file mode 100644 index 0000000..0720df2 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca88620e3e45de4db56cba2f44cd48f4960227451aa31a76f1d23d89ce51512e +size 146860 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..2a04c95 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:983974db0058760a5b3cdca579e257c9ff50daff2da335f3477dcb159d4d48ca +size 153360 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf new file mode 100644 index 0000000..cdc967f --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c9aaa2737614f22516205319db3f202f1f2837f3020e9a73d33bb34c656ddfe +size 146100 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..1d609b1 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96680331eb5207b276191efdde7f1c3d33ef43ff90a5745a5bd01a205d348588 +size 152012 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Italic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Italic.ttf new file mode 100644 index 0000000..671d00b --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Italic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0ef52e30e52e7bb30f8c55a8da717bee4a20eb1e38cac10906df1499dbe73e4 +size 152304 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Light.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Light.ttf new file mode 100644 index 0000000..8278241 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Light.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27ee75c1ef1c7746884750ff174eb7029fb2c0fd22b29f8b1c4fce4183515161 +size 146064 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf new file mode 100644 index 0000000..35140d5 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e8e5fccea6dbe315e9bdc0f7bcfe6a0c547c31b3119fe7d8a87a072d9bbe4ad +size 152304 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Medium.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Medium.ttf new file mode 100644 index 0000000..a0dedd3 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Medium.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8af0cea735d90c5ebfd941968eafbe62aba299464f4a987d30db8979b9a18f35 +size 146124 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf new file mode 100644 index 0000000..fe4ce03 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ee23f13eaed0e78528bd35541e7a01de39af55fefadfaf849d261edb8f7a437 +size 152492 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Regular.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Regular.ttf new file mode 100644 index 0000000..865cfd4 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Regular.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33eede8cfc71f9b58002b9e4c057f28abb0ea7f64553314733e39c34baeaad85 +size 146048 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..8f37536 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:013260f6d44ac5a432f2c1687810575506a2957e7d08718529480c03840bf725 +size 146720 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..4be2247 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33671e3531f3a7dd38a9555fdfaa41bb8e55bef97b1bfeb3c4729c8d671e386e +size 152836 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Thin.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Thin.ttf new file mode 100644 index 0000000..7ee5ba4 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-Thin.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b6b1728ee68e587d131d360a719d062c53c52fcd73bb50c416da4874627de06 +size 146068 diff --git a/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf new file mode 100644 index 0000000..1bb36f6 --- /dev/null +++ b/src/main/assets/fonts/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:860cfb9481630b0259aa76fbd7614bd92f505db996bf29967dd9f4ac5251e642 +size 151496 diff --git a/src/main/assets/high_scores_alien_1.tga b/src/main/assets/high_scores_alien_1.tga index 7225577..b9d1202 100644 Binary files a/src/main/assets/high_scores_alien_1.tga and b/src/main/assets/high_scores_alien_1.tga differ diff --git a/src/main/assets/high_scores_alien_2.tga b/src/main/assets/high_scores_alien_2.tga index 025cc33..ee6abc4 100644 Binary files a/src/main/assets/high_scores_alien_2.tga and b/src/main/assets/high_scores_alien_2.tga differ diff --git a/src/main/assets/high_scores_alien_3.tga b/src/main/assets/high_scores_alien_3.tga index 9f832c3..513f397 100644 Binary files a/src/main/assets/high_scores_alien_3.tga and b/src/main/assets/high_scores_alien_3.tga differ diff --git a/src/main/assets/high_scores_defender.tga b/src/main/assets/high_scores_defender.tga index e10f946..41ecb8d 100644 Binary files a/src/main/assets/high_scores_defender.tga and b/src/main/assets/high_scores_defender.tga differ diff --git a/src/main/assets/icons/add.tga b/src/main/assets/icons/add.tga new file mode 100644 index 0000000..23a46f2 --- /dev/null +++ b/src/main/assets/icons/add.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:161ad9d1947acdad2be332a7f1c760318554c3587aa51074aacde69e5e248e53 +size 9234 diff --git a/src/main/assets/icons/back.tga b/src/main/assets/icons/back.tga new file mode 100644 index 0000000..2f95b91 --- /dev/null +++ b/src/main/assets/icons/back.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dddba952a0ab45e5e10b9a12ed9010ee9ea42ca4880499a31ed9bb1491ade89 +size 9234 diff --git a/src/main/assets/icons/backspace.tga b/src/main/assets/icons/backspace.tga new file mode 100644 index 0000000..f7d4a34 --- /dev/null +++ b/src/main/assets/icons/backspace.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d185f970b478e764760d2b02733bdad4563eb14e0feecd5fea5594bde8cc530 +size 9234 diff --git a/src/main/assets/icons/battery.tga b/src/main/assets/icons/battery.tga new file mode 100644 index 0000000..9dda0c2 --- /dev/null +++ b/src/main/assets/icons/battery.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4d4ae61df2a0a020276a7bbd1e07d3951e1a336a4d0827a6b7723d61c1621cd +size 9234 diff --git a/src/main/assets/icons/browser.tga b/src/main/assets/icons/browser.tga new file mode 100644 index 0000000..8e24c73 --- /dev/null +++ b/src/main/assets/icons/browser.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c197f9ea2b46366ce93921277914e61c88d464eec5114645d029f8922ad13ed9 +size 36882 diff --git a/src/main/assets/icons/calculator.tga b/src/main/assets/icons/calculator.tga new file mode 100644 index 0000000..8f3cd5a --- /dev/null +++ b/src/main/assets/icons/calculator.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b26dd55dd906721eeb6aa1013f351245f14419d588b81c0f56aad77c8baeccd6 +size 36882 diff --git a/src/main/assets/icons/calendar.tga b/src/main/assets/icons/calendar.tga new file mode 100644 index 0000000..2e1b16f --- /dev/null +++ b/src/main/assets/icons/calendar.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:496cd2a82e219fe451c0e709ec84c7fa80eb1f9c3f5c6c8718e9bffe6c93753b +size 36882 diff --git a/src/main/assets/icons/call_small.tga b/src/main/assets/icons/call_small.tga new file mode 100644 index 0000000..0e87324 --- /dev/null +++ b/src/main/assets/icons/call_small.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97e1cd5ab2e73c8bd21cd9e0e9f01611a9ab17f369a54ef6b3ad45e9243c5b63 +size 9234 diff --git a/src/main/assets/icons/camera.tga b/src/main/assets/icons/camera.tga new file mode 100644 index 0000000..94eff75 --- /dev/null +++ b/src/main/assets/icons/camera.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bb3513d1cbb13bf808f4bb155f7d2f823307c0d12e100d367b23cfea0da1e5b +size 36882 diff --git a/src/main/assets/icons/clock.tga b/src/main/assets/icons/clock.tga new file mode 100644 index 0000000..69fa686 --- /dev/null +++ b/src/main/assets/icons/clock.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f227c1b4c7fe680576cd2fb89060f758812948bdafa0cc3ca13b768d4ebed6ed +size 36882 diff --git a/src/main/assets/icons/close.tga b/src/main/assets/icons/close.tga new file mode 100644 index 0000000..ccb2ac2 --- /dev/null +++ b/src/main/assets/icons/close.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f858a08e9d05a621484d556045ab5ce829f7c487f082d136d788bd97861aec +size 9234 diff --git a/src/main/assets/icons/contact_phone.tga b/src/main/assets/icons/contact_phone.tga new file mode 100644 index 0000000..d6fa4d5 --- /dev/null +++ b/src/main/assets/icons/contact_phone.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a8b0cd6f02dd2a1d2231acfaeba6e46ab556b4f07123c0cfcee965c46c7b888 +size 9234 diff --git a/src/main/assets/icons/contacts.tga b/src/main/assets/icons/contacts.tga new file mode 100644 index 0000000..d6dd4f0 --- /dev/null +++ b/src/main/assets/icons/contacts.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac88f3429e34ebdcc44feeeb9474ca98dc01a0e6dd37d986a8daaf773efaa434 +size 36882 diff --git a/src/main/assets/icons/dialpad.tga b/src/main/assets/icons/dialpad.tga new file mode 100644 index 0000000..9694906 --- /dev/null +++ b/src/main/assets/icons/dialpad.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce0e370125001136be86837642b237ba27394c78b01b9c9ce6d1e4f85320ef1b +size 9234 diff --git a/src/main/assets/icons/files.tga b/src/main/assets/icons/files.tga new file mode 100644 index 0000000..18d4517 --- /dev/null +++ b/src/main/assets/icons/files.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e66c03ebea19367a92190b6703e8745b7060fe7ee97f4accbaafde84d2879e0 +size 36882 diff --git a/src/main/assets/icons/forward.tga b/src/main/assets/icons/forward.tga new file mode 100644 index 0000000..b8e6f89 --- /dev/null +++ b/src/main/assets/icons/forward.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1a8b3c8a754a0b43fa5738b534dec0245d73c8c381341cda9381a7b25f62fb +size 9234 diff --git a/src/main/assets/icons/gallery.tga b/src/main/assets/icons/gallery.tga new file mode 100644 index 0000000..8637963 --- /dev/null +++ b/src/main/assets/icons/gallery.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c27fab4bc2ed6f757a22d1be78ddce0fcd8ffa6f4e854e0d5dfddb78a55718d6 +size 36882 diff --git a/src/main/assets/icons/history.tga b/src/main/assets/icons/history.tga new file mode 100644 index 0000000..202b8d5 --- /dev/null +++ b/src/main/assets/icons/history.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67e44ad050f6da50a833dab72a3aff58c3b8372e8e00a7315676b33c4c9e054f +size 9234 diff --git a/src/main/assets/icons/home.tga b/src/main/assets/icons/home.tga new file mode 100644 index 0000000..3ff78f0 --- /dev/null +++ b/src/main/assets/icons/home.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54c6a2b46e41b8e195c606ed96f02313574ff14c45e87fa076359494d1a6cd85 +size 9234 diff --git a/src/main/assets/icons/maps.tga b/src/main/assets/icons/maps.tga new file mode 100644 index 0000000..4450877 --- /dev/null +++ b/src/main/assets/icons/maps.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:010619d697b90a634a43fd9c011a4f6d7e47fb8bd3f753d320d16373d59664c0 +size 36882 diff --git a/src/main/assets/icons/menu.tga b/src/main/assets/icons/menu.tga new file mode 100644 index 0000000..f97f6ca --- /dev/null +++ b/src/main/assets/icons/menu.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:affe25fb084638acff5e30b512144f42dadcca90080952a62b275f0b508d8f18 +size 9234 diff --git a/src/main/assets/icons/message.tga b/src/main/assets/icons/message.tga new file mode 100644 index 0000000..65a816c --- /dev/null +++ b/src/main/assets/icons/message.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbae2f2835d8788c7669256afb7c959fbfcdab75c4010efd19cb00e6ef88b983 +size 36882 diff --git a/src/main/assets/icons/more.tga b/src/main/assets/icons/more.tga new file mode 100644 index 0000000..ad2db04 --- /dev/null +++ b/src/main/assets/icons/more.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85e174b65a12dadbae3db34c58e6c4b88b946a074ead948008fdbdf91960be8e +size 9234 diff --git a/src/main/assets/icons/music.tga b/src/main/assets/icons/music.tga new file mode 100644 index 0000000..3f01189 --- /dev/null +++ b/src/main/assets/icons/music.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:107e15abf240a074709daa6b1b029c91c79e4f1a37164c12e597e3fa5681d878 +size 36882 diff --git a/src/main/assets/icons/notes.tga b/src/main/assets/icons/notes.tga new file mode 100644 index 0000000..ab1dcdb --- /dev/null +++ b/src/main/assets/icons/notes.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9418a3c71ac8d5725181df6cbbef15068ab6b2da31686949492ee7c0ec5b5e67 +size 36882 diff --git a/src/main/assets/icons/phone.tga b/src/main/assets/icons/phone.tga new file mode 100644 index 0000000..95800ba --- /dev/null +++ b/src/main/assets/icons/phone.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faf243f7dfc3ea8c18c8dc46069857f977b638fd8f0f1f9bd6c1a0a6a7b68c52 +size 36882 diff --git a/src/main/assets/icons/refresh.tga b/src/main/assets/icons/refresh.tga new file mode 100644 index 0000000..26840c5 --- /dev/null +++ b/src/main/assets/icons/refresh.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6971909669c9b4885d6d7a47bb0d39578e9dd623d8db3a76a5471fa17471da89 +size 9234 diff --git a/src/main/assets/icons/search.tga b/src/main/assets/icons/search.tga new file mode 100644 index 0000000..fb2cd76 --- /dev/null +++ b/src/main/assets/icons/search.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa9168b66c351d986788748d9773d12d271b756e49854e109d609d8d333e946 +size 9234 diff --git a/src/main/assets/icons/send.tga b/src/main/assets/icons/send.tga new file mode 100644 index 0000000..b98ac0b --- /dev/null +++ b/src/main/assets/icons/send.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1e30362c46f5b6933959f9810b99264f82a20f9ed26b02a0d28433af5befe01 +size 9234 diff --git a/src/main/assets/icons/settings.tga b/src/main/assets/icons/settings.tga new file mode 100644 index 0000000..92d123e --- /dev/null +++ b/src/main/assets/icons/settings.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc46b65a5192de576b3ae018887eebd2f529fc7c4719c120c73dd7c780f63e2 +size 36882 diff --git a/src/main/assets/icons/signal.tga b/src/main/assets/icons/signal.tga new file mode 100644 index 0000000..ed6c007 --- /dev/null +++ b/src/main/assets/icons/signal.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23b5eee9afda355a4ef3166cb1e9ca582e161e7b4041bd0833ed24a8e1375276 +size 9234 diff --git a/src/main/assets/icons/store.tga b/src/main/assets/icons/store.tga new file mode 100644 index 0000000..234b90d --- /dev/null +++ b/src/main/assets/icons/store.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19bc9956deed8379041a48fa0847b3b57c3627dcc5784c74e2af6a6a00b2ce73 +size 36882 diff --git a/src/main/assets/icons/weather.tga b/src/main/assets/icons/weather.tga new file mode 100644 index 0000000..3c153b2 --- /dev/null +++ b/src/main/assets/icons/weather.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4def86b6b9b913a65eb9573fe5f2929ff2aca76d2faf88a3542f90ed4136ab1a +size 36882 diff --git a/src/main/assets/icons/wifi.tga b/src/main/assets/icons/wifi.tga new file mode 100644 index 0000000..c7cbdb6 --- /dev/null +++ b/src/main/assets/icons/wifi.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0953efd8935e443e52e77efe38b20c4a4baaf5087bcf470a220cd12ee90b825 +size 9234 diff --git a/src/main/assets/invader.tga b/src/main/assets/invader.tga index 3223e72..4ee4775 100644 Binary files a/src/main/assets/invader.tga and b/src/main/assets/invader.tga differ diff --git a/src/main/assets/present.tga b/src/main/assets/present.tga index f2c8b1b..bb8b07c 100644 Binary files a/src/main/assets/present.tga and b/src/main/assets/present.tga differ diff --git a/src/main/assets/scripts/navigation.lua b/src/main/assets/scripts/navigation.lua new file mode 100644 index 0000000..e004ed7 --- /dev/null +++ b/src/main/assets/scripts/navigation.lua @@ -0,0 +1,142 @@ +-- Navigation System for Virtual Smartphone +-- Handles screen transitions and state management + +-- Screen registry - maps screen names to RML file paths +local screens = { + home = "apps/home/home.rml", + lock = "apps/home/lock.rml", + dialer = "apps/dialer/dialer.rml", + calling = "apps/dialer/calling.rml", + contacts = "apps/contacts/contacts.rml", + contact_detail = "apps/contacts/contact_detail.rml", + messages = "apps/messages/messages.rml", + chat = "apps/messages/chat.rml", + settings = "apps/settings/settings.rml", + browser = "apps/browser/browser.rml" +} + +-- Use global state to persist across document loads +-- Initialize only if not already set +if not _G.nav_state then + _G.nav_state = { + history = {}, + current_screen = "home", + nav_direction = "none" -- "forward", "back", "home", "none" + } +end + +-- Local references for convenience +local history = _G.nav_state.history +local function get_current() return _G.nav_state.current_screen end +local function set_current(s) _G.nav_state.current_screen = s end +local function get_direction() return _G.nav_state.nav_direction end +local function set_direction(d) _G.nav_state.nav_direction = d end + +-- Apply animation class based on navigation direction +local function applyNavAnimation() + local dir = get_direction() + print("Applying animation, direction: " .. dir) + if dir ~= "none" and document then + -- In RmlUi Lua, get body element + local body = document.body + if body then + print("Found body element, setting class nav-" .. dir) + -- Set the appropriate animation class + if dir == "forward" then + body:SetClass("nav-forward", true) + elseif dir == "back" then + body:SetClass("nav-back", true) + elseif dir == "home" then + body:SetClass("nav-home", true) + else + body:SetClass("nav-default", true) + end + else + print("Body element not found!") + end + end +end + +-- Navigate to a screen by name +function navigateTo(screen_name) + print("navigateTo called with: " .. tostring(screen_name)) + local path = screens[screen_name] + if path then + -- Push current screen to history before navigating + table.insert(history, get_current()) + set_current(screen_name) + set_direction("forward") + + -- Load the new screen using C++ function + local success = loadScreen(path) + if success then + applyNavAnimation() + print("Navigated to: " .. screen_name .. " (history depth: " .. #history .. ")") + else + -- Restore previous state on failure + set_current(table.remove(history)) + print("Failed to navigate to: " .. screen_name) + end + return success + else + print("Unknown screen: " .. screen_name) + return false + end +end + +-- Go back to previous screen +function goBack() + print("goBack called (history depth: " .. #history .. ")") + if #history > 0 then + local previous = table.remove(history) + local path = screens[previous] + if path then + set_current(previous) + set_direction("back") + loadScreen(path) + applyNavAnimation() + print("Back to: " .. previous) + return true + end + else + print("No history to go back to") + end + return false +end + +-- Go to home screen (clear history) +function goHome() + -- Clear the history table + for i = #history, 1, -1 do + history[i] = nil + end + set_current("home") + set_direction("home") + loadScreen(screens.home) + applyNavAnimation() + print("Navigated to home") +end + +-- Get current screen name +function getCurrentScreen() + return get_current() +end + +-- Check if we can go back +function canGoBack() + return #history > 0 +end + +-- Clear navigation history +function clearHistory() + for i = #history, 1, -1 do + history[i] = nil + end +end + +-- Get history depth +function getHistoryDepth() + return #history +end + +print("Navigation system initialized (current: " .. get_current() .. ", history: " .. #history .. ")") diff --git a/src/main/assets/ui/components.rcss b/src/main/assets/ui/components.rcss new file mode 100644 index 0000000..80dfaa8 --- /dev/null +++ b/src/main/assets/ui/components.rcss @@ -0,0 +1,704 @@ +/* ============================================== + Components: Material UI Components + ============================================== */ + +/* ============== Status Bar ============== */ + +.status-bar { + height: 24px; + padding: 0 12px; + background-color: transparent; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: #FFFFFF; +} + +.status-bar-time { + font-weight: 500; +} + +.status-bar-icons { + display: flex; + gap: 4px; +} + +/* ============== App Bar ============== */ + +.app-bar { + height: 56px; + padding: 0 16px; + background-color: #1E1E1E; + display: flex; + align-items: center; +} + +.app-bar-nav { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 8px; +} + +.app-bar-title { + font-size: 20px; + font-weight: 500; + flex: 1; + color: #FFFFFF; +} + +.app-bar-actions { + display: flex; +} + +/* ============== Buttons ============== */ + +.btn { + padding: 12px 24px; + font-size: 14px; + font-weight: 500; + font-family: LatoLatin; + text-align: center; + border-radius: 4px; + cursor: pointer; +} + +.btn-primary { + background-color: #BB86FC; + color: #000000; +} + +.btn-primary:hover { + background-color: #D4A5FF; +} + +.btn-primary:active { + background-color: #9A67EA; +} + +.btn-secondary { + background-color: #03DAC6; + color: #000000; +} + +.btn-secondary:hover { + background-color: #4AEADB; +} + +.btn-outlined { + background-color: transparent; + border: 1px solid #BB86FC; + color: #BB86FC; +} + +.btn-outlined:hover { + background-color: rgba(187, 134, 252, 0.1); +} + +.btn-text { + background-color: transparent; + color: #BB86FC; + padding: 8px 16px; +} + +.btn-text:hover { + background-color: rgba(187, 134, 252, 0.1); +} + +/* Icon Button */ +.btn-icon { + width: 48px; + height: 48px; + padding: 0; + border-radius: 24px; + background-color: transparent; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; +} + +.btn-icon:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +/* Floating Action Button */ +.btn-fab { + width: 56px; + height: 56px; + border-radius: 28px; + background-color: #BB86FC; + color: #000000; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + position: absolute; + bottom: 72px; + right: 16px; +} + +.btn-fab:hover { + background-color: #D4A5FF; +} + +/* ============== Input Fields ============== */ + +.input-container { + margin-bottom: 16px; +} + +.input-label { + font-size: 12px; + color: #B3B3B3; + margin-bottom: 4px; +} + +.input-field { + width: 100%; + padding: 12px 0; + font-size: 16px; + font-family: LatoLatin; + color: #FFFFFF; + background-color: transparent; + border: none; + border-bottom: 1px solid #333333; +} + +.input-field:focus { + border-bottom-color: #BB86FC; +} + +.input-field:hover { + border-bottom-color: #666666; +} + +/* Search Input */ +.search-bar { + display: flex; + align-items: center; + padding: 8px 16px; + background-color: #2D2D2D; + border-radius: 24px; + margin: 8px 16px; +} + +.search-icon { + font-size: 20px; + color: #B3B3B3; + margin-right: 12px; +} + +.search-input { + flex: 1; + background-color: transparent; + border: none; + font-size: 16px; + color: #FFFFFF; +} + +/* ============== Cards ============== */ + +.card { + background-color: #1E1E1E; + border-radius: 8px; + padding: 16px; + margin: 8px; +} + +.card-elevated { + background-color: #2D2D2D; +} + +.card-header { + font-size: 20px; + font-weight: 500; + margin-bottom: 8px; + color: #FFFFFF; +} + +.card-subtitle { + font-size: 14px; + color: #B3B3B3; + margin-bottom: 12px; +} + +.card-content { + font-size: 14px; + color: #B3B3B3; +} + +.card-actions { + display: flex; + justify-content: flex-end; + margin-top: 16px; + gap: 8px; +} + +/* ============== Lists ============== */ + +.list { + display: flex; + flex-direction: column; +} + +.list-item { + display: flex; + align-items: center; + padding: 12px 16px; + min-height: 48px; + cursor: pointer; +} + +.list-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.list-item:active { + background-color: rgba(255, 255, 255, 0.1); +} + +.list-item-avatar { + width: 40px; + height: 40px; + border-radius: 20px; + background-color: #BB86FC; + margin-right: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + color: #000000; +} + +.list-item-icon { + width: 40px; + height: 40px; + margin-right: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #B3B3B3; +} + +.list-item-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.list-item-title { + font-size: 16px; + color: #FFFFFF; +} + +.list-item-subtitle { + font-size: 14px; + color: #B3B3B3; + margin-top: 2px; +} + +.list-item-action { + font-size: 24px; + color: #666666; +} + +.list-divider { + height: 1px; + background-color: #333333; + margin-left: 72px; +} + +.list-header { + padding: 16px 16px 8px 16px; + font-size: 14px; + font-weight: 500; + color: #BB86FC; +} + +/* ============== Bottom Navigation ============== */ + +.bottom-nav { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 56px; + background-color: #1E1E1E; + display: flex; + border-top: 1px solid #333333; +} + +.bottom-nav-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + color: #666666; +} + +.bottom-nav-item:hover { + color: #B3B3B3; +} + +.bottom-nav-item.active { + color: #BB86FC; +} + +.bottom-nav-icon { + font-size: 24px; + margin-bottom: 4px; +} + +.bottom-nav-label { + font-size: 12px; +} + +/* ============== Dialogs/Modals ============== */ + +.modal-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; +} + +.modal-dialog { + background-color: #2D2D2D; + border-radius: 8px; + padding: 24px; + width: 280px; + max-width: 90%; +} + +.modal-title { + font-size: 20px; + font-weight: 500; + color: #FFFFFF; + margin-bottom: 16px; +} + +.modal-content { + font-size: 14px; + color: #B3B3B3; + margin-bottom: 24px; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +/* ============== Toast/Snackbar ============== */ + +.toast { + position: absolute; + bottom: 72px; + left: 16px; + right: 16px; + background-color: #323232; + color: #FFFFFF; + padding: 14px 16px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 14px; +} + +.toast-action { + color: #BB86FC; + font-weight: 500; + margin-left: 16px; +} + +/* ============== Toggle/Switch ============== */ + +.toggle { + width: 36px; + height: 20px; + border-radius: 10px; + background-color: #666666; + position: relative; + cursor: pointer; +} + +.toggle.active { + background-color: rgba(187, 134, 252, 0.5); +} + +.toggle-thumb { + width: 16px; + height: 16px; + border-radius: 8px; + background-color: #B3B3B3; + position: absolute; + top: 2px; + left: 2px; +} + +.toggle.active .toggle-thumb { + background-color: #BB86FC; + left: 18px; +} + +/* ============== Chips/Tags ============== */ + +.chip { + display: inline-flex; + align-items: center; + padding: 6px 12px; + background-color: #2D2D2D; + border-radius: 16px; + font-size: 14px; + color: #FFFFFF; + margin: 4px; +} + +.chip-icon { + margin-right: 8px; +} + +.chip-delete { + margin-left: 8px; + font-size: 18px; + color: #B3B3B3; +} + +/* ============== Dividers ============== */ + +.divider { + height: 1px; + background-color: #333333; +} + +.divider-inset { + margin-left: 72px; +} + +/* ============== App Grid (Home Screen) ============== */ + +.app-grid { + display: flex; + flex-wrap: wrap; + padding: 16px; +} + +.app-icon { + width: 25%; + display: flex; + flex-direction: column; + align-items: center; + padding: 12px 0; + cursor: pointer; +} + +.app-icon:hover { + background-color: rgba(255, 255, 255, 0.05); + border-radius: 8px; +} + +.app-icon-image { + width: 48px; + height: 48px; + border-radius: 12px; + background-color: #BB86FC; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #000000; + margin-bottom: 8px; +} + +.app-icon-label { + font-size: 12px; + color: #FFFFFF; + text-align: center; +} + +/* ============== Dock ============== */ + +.dock { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 80px; + background-color: rgba(30, 30, 30, 0.9); + display: flex; + justify-content: space-around; + align-items: center; + padding: 0 32px; +} + +.dock-item { + width: 56px; + height: 56px; + border-radius: 14px; + background-color: #BB86FC; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #000000; + cursor: pointer; +} + +.dock-item:hover { + transform: scale(1.1); +} + +/* ============== Dial Pad ============== */ + +.dial-pad { + display: flex; + flex-wrap: wrap; + padding: 16px; +} + +.dial-key { + width: 33.33%; + height: 72px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 36px; +} + +.dial-key:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.dial-key:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.dial-key-number { + font-size: 32px; + font-weight: 300; + color: #FFFFFF; +} + +.dial-key-letters { + font-size: 10px; + color: #B3B3B3; + letter-spacing: 2px; + margin-top: 2px; +} + +.dial-display { + font-size: 36px; + font-weight: 300; + color: #FFFFFF; + text-align: center; + padding: 24px 16px; + letter-spacing: 2px; +} + +.dial-actions { + display: flex; + justify-content: center; + padding: 16px; + gap: 48px; +} + +.dial-call-btn { + width: 64px; + height: 64px; + border-radius: 32px; + background-color: #4CAF50; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #FFFFFF; + cursor: pointer; +} + +.dial-call-btn:hover { + background-color: #66BB6A; +} + +/* ============== Chat Bubbles ============== */ + +.chat-container { + display: flex; + flex-direction: column; + padding: 16px; + gap: 8px; +} + +.chat-bubble { + max-width: 75%; + padding: 10px 14px; + border-radius: 18px; + font-size: 14px; +} + +.chat-bubble-sent { + align-self: flex-end; + background-color: #BB86FC; + color: #000000; + border-bottom-right-radius: 4px; +} + +.chat-bubble-received { + align-self: flex-start; + background-color: #2D2D2D; + color: #FFFFFF; + border-bottom-left-radius: 4px; +} + +.chat-timestamp { + font-size: 11px; + color: #666666; + text-align: center; + margin: 8px 0; +} + +.chat-input-container { + display: flex; + align-items: center; + padding: 8px 16px; + background-color: #1E1E1E; + border-top: 1px solid #333333; +} + +.chat-input { + flex: 1; + padding: 10px 16px; + background-color: #2D2D2D; + border-radius: 24px; + border: none; + font-size: 16px; + color: #FFFFFF; + margin-right: 8px; +} + +.chat-send-btn { + width: 40px; + height: 40px; + border-radius: 20px; + background-color: #BB86FC; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: #000000; + cursor: pointer; +} diff --git a/src/main/assets/ui/html.rcss b/src/main/assets/ui/html.rcss new file mode 100644 index 0000000..ac19961 --- /dev/null +++ b/src/main/assets/ui/html.rcss @@ -0,0 +1,93 @@ +body, div, +h1, h2, h3, h4, +h5, h6, p, +hr, pre, +tabset tabs +{ + display: block; +} + +h1 +{ + font-size: 2em; + margin: .67em 0; +} + +h2 +{ + font-size: 1.5em; + margin: .75em 0; +} + +h3 +{ + font-size: 1.17em; + margin: .83em 0; +} + +h4, p +{ + margin: 1.12em 0; +} + +h5 +{ + font-size: .83em; + margin: 1.5em 0; +} + +h6 +{ + font-size: .75em; + margin: 1.67em 0; +} + +h1, h2, h3, h4, +h5, h6, strong +{ + font-weight: bold; +} + +em +{ + font-style: italic; +} + +pre +{ + white-space: pre; +} + +hr +{ + border-width: 1px; +} + +table +{ + box-sizing: border-box; + display: table; +} +tr +{ + box-sizing: border-box; + display: table-row; +} +td +{ + box-sizing: border-box; + display: table-cell; +} +col +{ + box-sizing: border-box; + display: table-column; +} +colgroup +{ + display: table-column-group; +} +thead, tbody, tfoot +{ + display: table-row-group; +} diff --git a/src/main/assets/ui/theme.rcss b/src/main/assets/ui/theme.rcss new file mode 100644 index 0000000..6df89ac --- /dev/null +++ b/src/main/assets/ui/theme.rcss @@ -0,0 +1,319 @@ +/* ============================================== + Theme: Material Dark for Virtual Smartphone + ============================================== */ + +/* Base body styling */ +body { + font-family: LatoLatin; + background-color: #121212; + color: #FFFFFF; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + animation: 0.2s cubic-out fade-in; +} + +/* ============== Typography ============== */ + +.text-h1 { + font-size: 96px; + font-weight: 300; +} + +.text-h2 { + font-size: 60px; + font-weight: 300; +} + +.text-h3 { + font-size: 48px; + font-weight: 400; +} + +.text-h4 { + font-size: 34px; + font-weight: 400; +} + +.text-h5 { + font-size: 24px; + font-weight: 400; +} + +.text-h6 { + font-size: 20px; + font-weight: 500; +} + +.text-body1 { + font-size: 16px; + font-weight: 400; +} + +.text-body2 { + font-size: 14px; + font-weight: 400; +} + +.text-caption { + font-size: 12px; + font-weight: 400; +} + +.text-overline { + font-size: 10px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 1.5px; +} + +/* ============== Text Colors ============== */ + +.text-primary { + color: #FFFFFF; +} + +.text-secondary { + color: #B3B3B3; +} + +.text-disabled { + color: #666666; +} + +.text-accent { + color: #BB86FC; +} + +.text-accent-secondary { + color: #03DAC6; +} + +.text-error { + color: #CF6679; +} + +/* ============== Background Colors ============== */ + +.bg-primary { + background-color: #121212; +} + +.bg-surface { + background-color: #1E1E1E; +} + +.bg-surface-variant { + background-color: #2D2D2D; +} + +.bg-accent { + background-color: #BB86FC; +} + +.bg-accent-secondary { + background-color: #03DAC6; +} + +.bg-error { + background-color: #CF6679; +} + +/* ============== Spacing Utilities ============== */ + +.p-0 { padding: 0; } +.p-1 { padding: 4px; } +.p-2 { padding: 8px; } +.p-3 { padding: 12px; } +.p-4 { padding: 16px; } +.p-5 { padding: 24px; } +.p-6 { padding: 32px; } +.p-8 { padding: 48px; } + +.m-0 { margin: 0; } +.m-1 { margin: 4px; } +.m-2 { margin: 8px; } +.m-3 { margin: 12px; } +.m-4 { margin: 16px; } +.m-5 { margin: 24px; } +.m-6 { margin: 32px; } +.m-8 { margin: 48px; } + +.mt-1 { margin-top: 4px; } +.mt-2 { margin-top: 8px; } +.mt-3 { margin-top: 12px; } +.mt-4 { margin-top: 16px; } + +.mb-1 { margin-bottom: 4px; } +.mb-2 { margin-bottom: 8px; } +.mb-3 { margin-bottom: 12px; } +.mb-4 { margin-bottom: 16px; } + +/* ============== Layout Utilities ============== */ + +.flex { + display: flex; +} + +.flex-row { + display: flex; + flex-direction: row; +} + +.flex-col { + display: flex; + flex-direction: column; +} + +.flex-center { + display: flex; + justify-content: center; + align-items: center; +} + +.flex-between { + display: flex; + justify-content: space-between; +} + +.flex-around { + display: flex; + justify-content: space-around; +} + +.flex-1 { + flex: 1; +} + +.w-full { + width: 100%; +} + +.h-full { + height: 100%; +} + +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +/* ============== Border Utilities ============== */ + +.rounded { + border-radius: 4px; +} + +.rounded-lg { + border-radius: 8px; +} + +.rounded-xl { + border-radius: 16px; +} + +.rounded-full { + border-radius: 50%; +} + +.border { + border: 1px solid #333333; +} + +.border-bottom { + border-bottom: 1px solid #333333; +} + +/* ============== Screen Structure ============== */ + +.screen { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: #121212; +} + +.screen-content { + flex: 1; + overflow: auto; +} + +/* ============== Animations ============== */ +/* RmlUi syntax: animation: ? ? ? alternate? paused? */ + +/* Fade in animation */ +@keyframes fade-in { + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +/* Slide in from right */ +@keyframes slide-in-right { + 0% { transform: translateX(100px); opacity: 0; } + 100% { transform: translateX(0px); opacity: 1; } +} + +/* Slide in from left */ +@keyframes slide-in-left { + 0% { transform: translateX(-100px); opacity: 0; } + 100% { transform: translateX(0px); opacity: 1; } +} + +/* Slide up animation */ +@keyframes slide-up { + 0% { transform: translateY(50px); opacity: 0; } + 100% { transform: translateY(0px); opacity: 1; } +} + +/* Scale in animation */ +@keyframes scale-in { + 0% { transform: scale(0.9); opacity: 0; } + 100% { transform: scale(1.0); opacity: 1; } +} + +/* Screen transition classes */ +.nav-forward { + animation: 0.2s cubic-out slide-in-right; +} + +.nav-back { + animation: 0.2s cubic-out slide-in-left; +} + +.nav-home { + animation: 0.2s back-out scale-in; +} + +.nav-default { + animation: 0.15s cubic-out fade-in; +} + +/* Animation utility classes */ +.animate-fade-in { + animation: 0.2s cubic-out fade-in; +} + +.animate-slide-right { + animation: 0.25s cubic-out slide-in-right; +} + +.animate-slide-left { + animation: 0.25s cubic-out slide-in-left; +} + +.animate-slide-up { + animation: 0.2s cubic-out slide-up; +} + +.animate-scale-in { + animation: 0.2s back-out scale-in; +} diff --git a/src/main/cpp/kernel.cpp b/src/main/cpp/kernel.cpp index 6a75ed2..55b8005 100644 --- a/src/main/cpp/kernel.cpp +++ b/src/main/cpp/kernel.cpp @@ -8,12 +8,17 @@ #include #include #include +#include #include "RmlUi_Renderer_GL3.h" #include #include #include #include +// Global state for Lua access +static Rml::Context* g_context = nullptr; +static Rml::ElementDocument* g_document = nullptr; + using namespace aidl::com::omixlab::mosis; using namespace aidl::android::hardware; @@ -88,6 +93,62 @@ public: } }; +// Lua function to load a screen document +static int LuaLoadScreen(lua_State* L) +{ + if (!g_context) + { + lua_pushboolean(L, false); + return 1; + } + + const char* path = luaL_checkstring(L, 1); + Logger::Log(std::format("Loading screen: {}", path)); + + // Check if asset exists + AAssetManager* am = AssetsManager::Native(); + AAsset* asset = AAssetManager_open(am, path, AASSET_MODE_BUFFER); + if (!asset) + { + Logger::Log(std::format("Screen not found: {}", path)); + lua_pushboolean(L, false); + return 1; + } + AAsset_close(asset); + + // Unload current document + if (g_document) + { + g_context->UnloadDocument(g_document); + g_document = nullptr; + } + + // Load new document + g_document = g_context->LoadDocument(path); + if (g_document) + { + g_document->Show(); + Logger::Log(std::format("Loaded screen: {}", path)); + lua_pushboolean(L, true); + } + else + { + Logger::Log(std::format("Failed to load screen: {}", path)); + lua_pushboolean(L, false); + } + + return 1; +} + +// Register Lua functions for navigation +static void RegisterLuaFunctions() +{ + lua_State* L = Rml::Lua::Interpreter::GetLuaState(); + lua_pushcfunction(L, LuaLoadScreen); + lua_setglobal(L, "loadScreen"); + Logger::Log("Registered Lua loadScreen function"); +} + void Kernel::main_loop() { m_egl_context = std::make_unique(); @@ -122,28 +183,35 @@ void Kernel::main_loop() Rml::Initialise(); Rml::Lua::Initialise(); Logger::Log("RmlUi Lua bindings initialized"); - Rml::Context* context = Rml::CreateContext("default", Rml::Vector2i(540, 960)); - if (!context) + + // Register navigation functions with Lua + RegisterLuaFunctions(); + + g_context = Rml::CreateContext("default", Rml::Vector2i(540, 960)); + if (!g_context) { Logger::Log("RMLUI failed to create a context"); Rml::Shutdown(); return; } - Rml::LoadFontFace("Roboto/static/Roboto_Condensed-Regular.ttf"); - Rml::LoadFontFace("LatoLatin-Regular.ttf"); - Rml::LoadFontFace("NotoEmoji-Regular.ttf", true); + // Load fonts from assets/fonts/ + Rml::LoadFontFace("fonts/LatoLatin-Bold.ttf"); + Rml::LoadFontFace("fonts/LatoLatin-BoldItalic.ttf"); + Rml::LoadFontFace("fonts/LatoLatin-Italic.ttf"); + Rml::LoadFontFace("fonts/LatoLatin-Regular.ttf"); + Rml::LoadFontFace("fonts/NotoEmoji-Regular.ttf", true); + Rml::LoadFontFace("fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf"); + Rml::LoadFontFace("fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf"); - std::string status_time = "0"; - Rml::DataModelHandle status_time_handle; - if (Rml::DataModelConstructor status_data_ctor = context->CreateDataModel("status-data")) + // Load home screen document + g_document = g_context->LoadDocument("apps/home/home.rml"); + if (!g_document) { - status_data_ctor.Bind("time", &status_time); - status_time_handle = status_data_ctor.GetModelHandle(); + Logger::Log("Failed to load home.rml document"); + Rml::Shutdown(); + return; } - - // Now we are ready to load our document. - Rml::ElementDocument* document = context->LoadDocument("demo.rml"); - document->Show(); + g_document->Show(); while (true) { @@ -151,7 +219,7 @@ void Kernel::main_loop() { std::lock_guard _lock(m_mutex); for (const auto &task: m_tasks) - task(context); + task(g_context); m_tasks.clear(); } @@ -160,14 +228,10 @@ void Kernel::main_loop() glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, 540, 960); - status_time = std::format("{:%H:%M:%S}", - std::chrono::floor(std::chrono::system_clock::now())); - status_time_handle.DirtyAllVariables(); - - context->Update(); + g_context->Update(); rmlui_render_interface.SetViewport(540, 960); rmlui_render_interface.BeginFrame(); - context->Render(); + g_context->Render(); rmlui_render_interface.EndFrame(m_render_target->framebuffer()); glFinish(); @@ -176,7 +240,7 @@ void Kernel::main_loop() for (const auto& [pid, l] : m_listeners) l->onFrameAvailable(); } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 FPS } Rml::Shutdown(); } diff --git a/src/main/java/com/omixlab/mosis/MainActivity.kt b/src/main/java/com/omixlab/mosis/MainActivity.kt index a94c637..46e9cf8 100644 --- a/src/main/java/com/omixlab/mosis/MainActivity.kt +++ b/src/main/java/com/omixlab/mosis/MainActivity.kt @@ -1,8 +1,11 @@ package com.omixlab.mosis import android.annotation.SuppressLint +import android.content.BroadcastReceiver import android.content.ComponentName +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.content.ServiceConnection import android.content.res.AssetManager import android.os.Bundle @@ -30,6 +33,20 @@ class MainActivity : ComponentActivity() { init { System.loadLibrary("mosis-test") } + + companion object { + // Singleton for test access + @Volatile + var instance: MainActivity? = null + private set + + // Intent actions for event injection via broadcast + const val ACTION_INJECT_TOUCH = "com.omixlab.mosis.INJECT_TOUCH" + const val EXTRA_TOUCH_TYPE = "touch_type" // "down", "move", "up", "click" + const val EXTRA_X = "x" // Normalized 0.0-1.0 + const val EXTRA_Y = "y" // Normalized 0.0-1.0 + } + var remote_service: IMosisService? = null var statusText = mutableStateOf("Status: idle") var buttonText = mutableStateOf("Init OS") @@ -79,11 +96,35 @@ class MainActivity : ComponentActivity() { Log.e("ServiceTester", "Bind failed", e) } } + // Broadcast receiver for event injection (for testing) + private val touchInjectionReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == ACTION_INJECT_TOUCH) { + val touchType = intent.getStringExtra(EXTRA_TOUCH_TYPE) ?: return + val x = intent.getFloatExtra(EXTRA_X, 0.5f) + val y = intent.getFloatExtra(EXTRA_Y, 0.5f) + Log.d("MosisTest", "Injecting touch: $touchType at ($x, $y)") + when (touchType) { + "down" -> injectTouchDown(x, y) + "move" -> injectTouchMove(x, y) + "up" -> injectTouchUp(x, y) + "click" -> injectClick(x, y) + } + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + instance = this setAssetManager(assets) startRemoteService() enableEdgeToEdge() + + // Register broadcast receiver for touch injection (testing) + val filter = IntentFilter(ACTION_INJECT_TOUCH) + registerReceiver(touchInjectionReceiver, filter, RECEIVER_EXPORTED) + setContent { Column( modifier = Modifier.fillMaxSize(), @@ -102,6 +143,51 @@ class MainActivity : ComponentActivity() { } } } + + override fun onDestroy() { + super.onDestroy() + instance = null + try { + unregisterReceiver(touchInjectionReceiver) + } catch (e: Exception) { + Log.w("MosisTest", "Receiver not registered", e) + } + } + + /** + * Inject a touch down event at normalized coordinates (0.0-1.0) + */ + fun injectTouchDown(x: Float, y: Float) { + Log.d("MosisTest", "Inject touch down: ($x, $y)") + remote_service?.onTouchDown(x, y) + } + + /** + * Inject a touch move event at normalized coordinates (0.0-1.0) + */ + fun injectTouchMove(x: Float, y: Float) { + Log.d("MosisTest", "Inject touch move: ($x, $y)") + remote_service?.onTouchMove(x, y) + } + + /** + * Inject a touch up event at normalized coordinates (0.0-1.0) + */ + fun injectTouchUp(x: Float, y: Float) { + Log.d("MosisTest", "Inject touch up: ($x, $y)") + remote_service?.onTouchUp(x, y) + } + + /** + * Inject a complete click (down + up) at normalized coordinates + */ + fun injectClick(x: Float, y: Float, delayMs: Long = 50) { + Log.d("MosisTest", "Inject click: ($x, $y)") + remote_service?.onTouchDown(x, y) + android.os.Handler(mainLooper).postDelayed({ + remote_service?.onTouchUp(x, y) + }, delayMs) + } @SuppressLint("ClickableViewAccessibility") @Composable fun NativeViewport(modifier: Modifier = Modifier) { diff --git a/src/main/kernel/include/file_interface.h b/src/main/kernel/include/file_interface.h new file mode 100644 index 0000000..61edf6a --- /dev/null +++ b/src/main/kernel/include/file_interface.h @@ -0,0 +1,68 @@ +// File interface abstraction - extends RmlUi FileInterface for cross-platform +#pragma once + +#include +#include +#include +#include +#include + +namespace mosis { + +// Extends RmlUi's FileInterface with additional utility methods +class IFileInterface : public Rml::FileInterface { +public: + virtual ~IFileInterface() = default; + + // Additional utility methods + virtual std::vector ReadAll(const std::string& path) = 0; + virtual bool FileExists(const std::string& path) = 0; + virtual std::vector ListDirectory(const std::string& path) = 0; + + // Asset path management + virtual void SetAssetsPath(const std::string& path) = 0; + virtual std::string GetAssetsPath() const = 0; + + // Resolve a relative path to full path + virtual std::string ResolvePath(const std::string& relative_path) = 0; + + // RmlUi FileInterface methods (must implement) + // Rml::FileHandle Open(const Rml::String& path) override; + // void Close(Rml::FileHandle file) override; + // size_t Read(void* buffer, size_t size, Rml::FileHandle file) override; + // bool Seek(Rml::FileHandle file, long offset, int origin) override; + // size_t Tell(Rml::FileHandle file) override; + // size_t Length(Rml::FileHandle file) override; + // bool LoadFile(const Rml::String& path, Rml::String& out_data) override; +}; + +// Desktop file interface implementation +class DesktopFileInterface : public IFileInterface { + std::filesystem::path m_assets_path; + +public: + DesktopFileInterface() = default; + explicit DesktopFileInterface(const std::string& assets_path); + + // RmlUi FileInterface implementation + Rml::FileHandle Open(const Rml::String& path) override; + void Close(Rml::FileHandle file) override; + size_t Read(void* buffer, size_t size, Rml::FileHandle file) override; + bool Seek(Rml::FileHandle file, long offset, int origin) override; + size_t Tell(Rml::FileHandle file) override; + size_t Length(Rml::FileHandle file) override; + bool LoadFile(const Rml::String& path, Rml::String& out_data) override; + + // Extended interface + std::vector ReadAll(const std::string& path) override; + bool FileExists(const std::string& path) override; + std::vector ListDirectory(const std::string& path) override; + void SetAssetsPath(const std::string& path) override; + std::string GetAssetsPath() const override; + std::string ResolvePath(const std::string& relative_path) override; + +private: + std::filesystem::path ResolveFilePath(const std::string& path); +}; + +} // namespace mosis diff --git a/src/main/kernel/include/platform.h b/src/main/kernel/include/platform.h new file mode 100644 index 0000000..c7331f2 --- /dev/null +++ b/src/main/kernel/include/platform.h @@ -0,0 +1,91 @@ +// Platform abstraction layer for cross-platform Mosis kernel +#pragma once + +#include +#include +#include +#include +#include + +namespace mosis { + +// Forward declarations +class IGraphicsContext; +class IRenderTarget; +class IFileInterface; + +// Resolution preset structure +struct Resolution { + uint32_t width; + uint32_t height; + const char* name; + + static constexpr Resolution PhoneSD() { return {540, 960, "Phone SD"}; } + static constexpr Resolution PhoneHD() { return {720, 1280, "Phone HD"}; } + static constexpr Resolution PhoneFHD() { return {1080, 1920, "Phone FHD"}; } + static constexpr Resolution Tablet() { return {800, 1280, "Tablet"}; } +}; + +// Platform abstraction - implemented differently per platform +class IPlatform { +public: + virtual ~IPlatform() = default; + + // Factory methods + virtual std::unique_ptr CreateGraphicsContext() = 0; + virtual std::unique_ptr CreateRenderTarget(uint32_t width, uint32_t height) = 0; + virtual IFileInterface& GetFileInterface() = 0; + + // Logging + virtual void Log(const std::string& message) = 0; + virtual void LogError(const std::string& message) = 0; + + // Platform-specific windowing (desktop only, no-op on Android) + virtual bool PollEvents() { return true; } + virtual void SwapBuffers() {} + virtual bool ShouldClose() const { return false; } + + // Resolution management + virtual uint32_t GetWidth() const = 0; + virtual uint32_t GetHeight() const = 0; + virtual void SetResolution(uint32_t width, uint32_t height) = 0; + virtual float GetDpiScale() const { return 1.0f; } + + // Time + virtual double GetElapsedTime() const = 0; +}; + +// Graphics context abstraction +class IGraphicsContext { +public: + virtual ~IGraphicsContext() = default; + virtual bool Create() = 0; + virtual void Destroy() = 0; + virtual void MakeCurrent() = 0; + virtual void SwapBuffers() = 0; + virtual bool IsValid() const = 0; +}; + +// Render target abstraction (framebuffer) +class IRenderTarget { +public: + virtual ~IRenderTarget() = default; + virtual bool Create(uint32_t width, uint32_t height) = 0; + virtual void Bind() = 0; + virtual void Unbind() = 0; + virtual void Destroy() = 0; + virtual uint32_t GetFramebuffer() const = 0; + virtual uint32_t GetTexture() const = 0; + virtual uint32_t GetWidth() const = 0; + virtual uint32_t GetHeight() const = 0; + + // Read pixels for screenshot capture + virtual std::vector ReadPixels() const = 0; +}; + +// Global platform accessor +IPlatform& GetPlatform(); +void SetPlatform(std::unique_ptr platform); +bool HasPlatform(); + +} // namespace mosis diff --git a/src/main/kernel/include/service_interface.h b/src/main/kernel/include/service_interface.h new file mode 100644 index 0000000..ed69e2a --- /dev/null +++ b/src/main/kernel/include/service_interface.h @@ -0,0 +1,73 @@ +// Service interface abstraction - mirrors Android Binder for cross-platform +#pragma once + +#include +#include +#include +#include +#include + +namespace mosis { + +// Abstract listener interface - mirrors IMosisListener.aidl +class IServiceListener { +public: + virtual ~IServiceListener() = default; + + // Called when kernel initialization completes + virtual void OnServiceInitialized(bool success) = 0; + + // Called when a new render buffer is available (with optional shared buffer handle) + virtual void OnBufferAvailable(void* buffer_handle) = 0; + + // Called when a new frame has been rendered + virtual void OnFrameAvailable() = 0; +}; + +// Kernel interface - the main service API +class IKernel { +public: + virtual ~IKernel() = default; + + // Listener management + virtual void AddListener(std::shared_ptr listener) = 0; + virtual void RemoveListener(std::shared_ptr listener) = 0; + + // Touch input (normalized 0.0-1.0 coordinates) + virtual void OnTouchDown(float x, float y) = 0; + virtual void OnTouchMove(float x, float y) = 0; + virtual void OnTouchUp(float x, float y) = 0; + + // Hardware button simulation + virtual void OnBackButton() = 0; + virtual void OnHomeButton() = 0; + virtual void OnRecentsButton() = 0; + + // Hot-reload support (desktop) + virtual void RequestReload() = 0; + + // Document loading + virtual void LoadDocument(const std::string& path) = 0; + + // Lifecycle + virtual void Start() = 0; + virtual void Stop() = 0; + virtual bool IsRunning() const = 0; + + // Single frame update (for non-threaded mode) + virtual void Update() = 0; + virtual void Render() = 0; +}; + +// Configuration for kernel creation +struct KernelConfig { + uint32_t width = 540; + uint32_t height = 960; + std::string initial_document = "apps/home/home.rml"; + bool threaded = true; // false for desktop single-threaded mode +}; + +// Factory for creating kernel implementation +std::unique_ptr CreateKernel(const KernelConfig& config); + +} // namespace mosis diff --git a/src/main/kernel/src/file_interface.cpp b/src/main/kernel/src/file_interface.cpp new file mode 100644 index 0000000..6fb1550 --- /dev/null +++ b/src/main/kernel/src/file_interface.cpp @@ -0,0 +1,164 @@ +// Desktop file interface implementation +#include "file_interface.h" +#include +#include + +namespace mosis { + +DesktopFileInterface::DesktopFileInterface(const std::string& assets_path) + : m_assets_path(assets_path) +{} + +std::filesystem::path DesktopFileInterface::ResolveFilePath(const std::string& path) { + std::string clean_path = path; + + // RmlUi uses URL-style paths where D: becomes D| - convert back + // Check for pattern like "X|" at start (Windows drive letter) +#ifdef _WIN32 + if (clean_path.length() >= 2 && clean_path[1] == '|' && + ((clean_path[0] >= 'A' && clean_path[0] <= 'Z') || + (clean_path[0] >= 'a' && clean_path[0] <= 'z'))) { + clean_path[1] = ':'; + } +#endif + + std::filesystem::path file_path(clean_path); + + // If absolute path, use as-is + if (file_path.is_absolute()) { + return file_path; + } + + // Otherwise resolve relative to assets path + if (!m_assets_path.empty()) { + return m_assets_path / file_path; + } + + return file_path; +} + +Rml::FileHandle DesktopFileInterface::Open(const Rml::String& path) { + auto resolved = ResolveFilePath(path); + auto* file = new std::ifstream(resolved, std::ios::binary); + if (!file->is_open()) { + delete file; + return 0; + } + return reinterpret_cast(file); +} + +void DesktopFileInterface::Close(Rml::FileHandle file) { + auto* stream = reinterpret_cast(file); + if (stream) { + stream->close(); + delete stream; + } +} + +size_t DesktopFileInterface::Read(void* buffer, size_t size, Rml::FileHandle file) { + auto* stream = reinterpret_cast(file); + if (!stream) return 0; + stream->read(static_cast(buffer), size); + return stream->gcount(); +} + +bool DesktopFileInterface::Seek(Rml::FileHandle file, long offset, int origin) { + auto* stream = reinterpret_cast(file); + if (!stream) return false; + + std::ios_base::seekdir dir; + switch (origin) { + case SEEK_SET: dir = std::ios::beg; break; + case SEEK_CUR: dir = std::ios::cur; break; + case SEEK_END: dir = std::ios::end; break; + default: return false; + } + + stream->seekg(offset, dir); + return !stream->fail(); +} + +size_t DesktopFileInterface::Tell(Rml::FileHandle file) { + auto* stream = reinterpret_cast(file); + if (!stream) return 0; + return static_cast(stream->tellg()); +} + +size_t DesktopFileInterface::Length(Rml::FileHandle file) { + auto* stream = reinterpret_cast(file); + if (!stream) return 0; + + auto current = stream->tellg(); + stream->seekg(0, std::ios::end); + auto length = stream->tellg(); + stream->seekg(current); + + return static_cast(length); +} + +bool DesktopFileInterface::LoadFile(const Rml::String& path, Rml::String& out_data) { + auto resolved = ResolveFilePath(path); + std::ifstream file(resolved, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + return false; + } + + auto size = file.tellg(); + file.seekg(0, std::ios::beg); + + out_data.resize(static_cast(size)); + file.read(out_data.data(), size); + + return true; +} + +std::vector DesktopFileInterface::ReadAll(const std::string& path) { + auto resolved = ResolveFilePath(path); + std::ifstream file(resolved, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + return {}; + } + + auto size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector data(static_cast(size)); + file.read(reinterpret_cast(data.data()), size); + + return data; +} + +bool DesktopFileInterface::FileExists(const std::string& path) { + auto resolved = ResolveFilePath(path); + return std::filesystem::exists(resolved); +} + +std::vector DesktopFileInterface::ListDirectory(const std::string& path) { + auto resolved = ResolveFilePath(path); + std::vector entries; + + if (!std::filesystem::exists(resolved) || !std::filesystem::is_directory(resolved)) { + return entries; + } + + for (const auto& entry : std::filesystem::directory_iterator(resolved)) { + entries.push_back(entry.path().filename().string()); + } + + std::sort(entries.begin(), entries.end()); + return entries; +} + +void DesktopFileInterface::SetAssetsPath(const std::string& path) { + m_assets_path = path; +} + +std::string DesktopFileInterface::GetAssetsPath() const { + return m_assets_path.string(); +} + +std::string DesktopFileInterface::ResolvePath(const std::string& relative_path) { + return ResolveFilePath(relative_path).string(); +} + +} // namespace mosis diff --git a/src/main/kernel/src/platform.cpp b/src/main/kernel/src/platform.cpp new file mode 100644 index 0000000..5a8650e --- /dev/null +++ b/src/main/kernel/src/platform.cpp @@ -0,0 +1,24 @@ +// Platform global accessor implementation +#include "platform.h" +#include + +namespace mosis { + +static std::unique_ptr g_platform; + +IPlatform& GetPlatform() { + if (!g_platform) { + throw std::runtime_error("Platform not initialized. Call SetPlatform() first."); + } + return *g_platform; +} + +void SetPlatform(std::unique_ptr platform) { + g_platform = std::move(platform); +} + +bool HasPlatform() { + return g_platform != nullptr; +} + +} // namespace mosis