diff --git a/designer/main.cpp b/designer/main.cpp index 5393fa9..240b790 100644 --- a/designer/main.cpp +++ b/designer/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -65,8 +66,10 @@ static std::ofstream g_log_file; // Simulator mode static bool g_simulator_mode = false; +static bool g_shell_mode = false; // Use persistent shell instead of direct documents static std::string g_test_apps_path; // Path to test-apps directory static std::string g_simulator_home_path; // Path to simulator home.rml +static std::string g_shell_path; // Path to shell.rml for shell mode static std::string g_main_assets_path; // Path to main assets (for goHome) static std::vector g_discovered_apps; static std::string g_current_app_id; // Currently running app (empty = home) @@ -284,7 +287,8 @@ static void PrintUsage() { << " --log FILE Write all log messages to file\n" << " --hierarchy FILE Continuously dump UI hierarchy to JSON\n" << "\nSimulator mode:\n" - << " --simulator Run in simulator mode (app launcher)\n" + << " --simulator Run in simulator mode (uses shell by default)\n" + << " --no-shell Disable shell (use direct document loading)\n" << " --test-apps PATH Path to test-apps directory (default: ./test-apps)\n" << "\nTest modes:\n" << " --record FILE Record user actions to JSON file\n" @@ -343,6 +347,9 @@ int main(int argc, char* argv[]) { g_test_output_path = argv[++i]; } else if (arg == "--simulator") { g_simulator_mode = true; + g_shell_mode = true; // Shell mode is implicit in simulator mode + } else if (arg == "--no-shell") { + g_shell_mode = false; // Disable shell (use direct document loading) } else if (arg == "--test-apps" && i + 1 < argc) { g_test_apps_path = argv[++i]; } else if (arg[0] != '-') { @@ -393,11 +400,27 @@ int main(int argc, char* argv[]) { } } - // Override document path to main home screen + // Override document path to main home screen or shell if (!g_simulator_home_path.empty()) { - document_path = g_simulator_home_path; // Also set the main assets path for proper resource loading g_main_assets_path = fs::path(g_simulator_home_path).parent_path().parent_path().parent_path().string(); + + // If shell mode, use shell instead of home + if (g_shell_mode) { + g_shell_path = (fs::path(g_main_assets_path) / "apps" / "shell" / "shell.rml").string(); + if (fs::exists(g_shell_path)) { + document_path = g_shell_path; + std::cout << "Shell mode enabled" << std::endl; + std::cout << "Shell: " << g_shell_path << std::endl; + } else { + std::cerr << "Warning: Could not find shell.rml, falling back to direct mode" << std::endl; + document_path = g_simulator_home_path; + g_shell_mode = false; + } + } else { + document_path = g_simulator_home_path; + } + std::cout << "Simulator mode enabled" << std::endl; std::cout << "Test apps path: " << g_test_apps_path << std::endl; std::cout << "Home screen: " << g_simulator_home_path << std::endl; @@ -896,7 +919,50 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height return 1; }); lua_setglobal(L, "switchAppSandbox"); - std::cout << "Registered Lua loadScreen, goHome, and switchAppSandbox functions" << std::endl; + + // Register loadAppContent function for shell-based app loading + // loadAppContent(element, path) - loads RML content into an element's inner_rml + lua_pushcfunction(L, [](lua_State* L) -> int { + // Get element from first argument (RmlUi element userdata) + Rml::Element* element = Rml::Lua::LuaType::check(L, 1); + const char* path = luaL_checkstring(L, 2); + + if (!element) { + std::cerr << "loadAppContent: Invalid element" << std::endl; + lua_pushboolean(L, false); + return 1; + } + + // Resolve path relative to assets directory + std::string full_path; + if (fs::path(path).is_absolute()) { + full_path = path; + } else { + full_path = (fs::path(g_main_assets_path) / path).string(); + } + + // Read file content + std::ifstream file(full_path); + if (!file.is_open()) { + std::cerr << "loadAppContent: Cannot open file: " << full_path << std::endl; + lua_pushboolean(L, false); + return 1; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + // Set as inner_rml + element->SetInnerRML(content); + + Rml::Log::Message(Rml::Log::LT_INFO, "Loaded app content from: %s", path); + lua_pushboolean(L, true); + return 1; + }); + lua_setglobal(L, "loadAppContent"); + + std::cout << "Registered Lua loadScreen, goHome, switchAppSandbox, and loadAppContent functions" << std::endl; // Register simulator API (if in simulator mode) if (g_simulator_mode) { diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 97c69f4..2ec8f85 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -23,7 +23,7 @@ Mosis is a **virtual smartphone OS** for VR games and applications. It provides | MosisService | ✅ Working | RmlUi rendering, touch input, navigation | | App Management | ✅ Working | Install/uninstall apps, sandbox integration | | Lua Sandbox | ✅ Working | 149 security tests passing | -| Desktop Designer | ✅ Working | Hot-reload, hierarchy dump, recording | +| Desktop Designer | ✅ Working | Hot-reload, shell mode, hierarchy dump, recording | | Designer Tests | ✅ 5/5 Passing | Navigation tests automated | | MosisVR (Unity) | ✅ Building | OpenGL backend working, Vulkan in progress | | MosisUnreal | ✅ Working | Vulkan texture import via UE5 RHI, phone actor with mesh | @@ -35,6 +35,7 @@ Mosis is a **virtual smartphone OS** for VR games and applications. It provides | Android Service | `src/main/` | Native service running RmlUi renderer | | App Management | `src/main/cpp/apps/` | App install/uninstall/launch with sandbox | | Lua Sandbox | `src/main/cpp/sandbox/` | Per-app Lua isolation (22 modules) | +| System Shell | `src/main/assets/apps/shell/` | Persistent status bar, nav bar, overlays | | Desktop Designer | `designer/` | UI development with hot-reload | | Designer Tests | `designer-test/` | Automated UI testing framework | | Sandbox Tests | `sandbox-test/` | Lua sandbox security tests (149 tests) | @@ -48,7 +49,7 @@ All detailed documentation is in `docs/`: |----------|-------------| | [BUILD-COMMANDS.md](BUILD-COMMANDS.md) | Android, Desktop Designer, and test build commands | | [ARCHITECTURE.md](ARCHITECTURE.md) | Native libraries, IPC flow, code structure | -| [DESKTOP-DESIGNER.md](DESKTOP-DESIGNER.md) | Hot-reload, recording, key files | +| [DESKTOP-DESIGNER.md](DESKTOP-DESIGNER.md) | Shell architecture, hot-reload, recording | | [TESTING-FRAMEWORK.md](TESTING-FRAMEWORK.md) | Automated UI testing, writing tests | | [UI-ASSETS.md](UI-ASSETS.md) | Asset structure, navigation system, element IDs | | [MATERIAL-DESIGN.md](MATERIAL-DESIGN.md) | Icons, MDL components, usage guide | diff --git a/docs/DESKTOP-DESIGNER.md b/docs/DESKTOP-DESIGNER.md index 582b155..a688453 100644 --- a/docs/DESKTOP-DESIGNER.md +++ b/docs/DESKTOP-DESIGNER.md @@ -3,6 +3,7 @@ The desktop designer (`designer/`) provides rapid UI development with: - **Hot-reload**: Automatically reloads when RML/RCSS/Lua files change +- **Shell Mode**: Persistent system UI (status bar, nav bar) with apps loading into container - **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 @@ -13,44 +14,57 @@ The desktop designer (`designer/`) provides rapid UI development with: | File | Purpose | |------|---------| -| `designer/src/main.cpp` | Main entry point, GLFW window, event loop | -| `designer/src/desktop_kernel.cpp` | RmlUi context management, rendering | +| `designer/main.cpp` | Main entry point, GLFW window, event loop | +| `designer/src/desktop_sandbox.cpp` | Per-app sandbox isolation | +| `designer/src/app_discovery.cpp` | Scans directories for app manifests | | `designer/src/testing/ui_inspector.cpp` | UI hierarchy JSON export | -| `designer/src/testing/visual_capture.cpp` | PNG screenshot capture and comparison | -| `designer/src/testing/action_recorder.cpp` | Record user interactions to JSON | +| `designer/src/testing/visual_capture.cpp` | PNG screenshot capture | +| `designer/src/testing/action_recorder.cpp` | Record user interactions | | `designer/src/testing/action_player.cpp` | Playback recorded actions | -| `designer/src/backend/RmlUi_Backend_GLFW_GL3.cpp` | GLFW backend with input hooks | ## Command Line Options ``` ---simulator Enable simulator mode (shows home screen with third-party apps) ---test-apps Path to test-apps directory (default: auto-detect) ---assets Set assets directory (default: derived from document) ---log Write logs to file ---hierarchy Dump UI hierarchy JSON each frame ---dump Single-shot dump mode (screenshot + hierarchy) ---record Enable recording mode (F5 to start/stop) ---playback Play back recorded actions from JSON ---resolution WxH Set window resolution (default: 540x960) +General: + --resolution WxH Set window resolution (default: 540x960) + --assets PATH Set assets directory (default: derived from document) + --log FILE Write all log messages to file + --hierarchy FILE Continuously dump UI hierarchy to JSON + +Simulator mode: + --simulator Run in simulator mode (uses shell by default) + --no-shell Disable shell (use direct document loading) + --test-apps PATH Path to test-apps directory (default: ./base-apps) + +Test modes: + --record FILE Record user actions to JSON file + --playback FILE Playback actions from JSON file + --screenshot FILE Take screenshot and exit + --dump-hierarchy FILE Dump UI hierarchy to JSON and exit ``` ## Running the Designer -### Simulator Mode (Recommended for Testing Apps) - -To run the designer with third-party test apps visible: +### Simulator Mode with Shell (Recommended) ```bash cd MosisService -./designer/build/Release/mosis-designer.exe --simulator --test-apps test-apps +./designer/build/Release/mosis-designer.exe --simulator --test-apps base-apps ``` This will: -- Load the home screen from `src/main/assets/apps/home/home.rml` -- Scan `test-apps/` for apps with valid `manifest.json` -- Display discovered apps in the home screen grid -- Enable the `mosis.apps` Lua API for app launching +- Load the **system shell** with persistent status bar and navigation bar +- Load home screen content into the shell's app container +- Scan `base-apps/` for apps with valid `manifest.json` +- Enable app navigation with proper back button support + +### Simulator Mode without Shell + +To test the old direct-document approach: + +```bash +./designer/build/Release/mosis-designer.exe --simulator --test-apps base-apps --no-shell +``` ### Direct Document Mode @@ -64,6 +78,144 @@ To load a specific RML document directly: | Key | Function | |-----|----------| -| F5 | Start/stop recording (when --record is enabled) | -| F6 | Pause/resume playback (when --playback is enabled) | -| F12 | Take screenshot | +| F5 | Reload document / Start-stop recording | +| F12 | Toggle RmlUi debugger | +| ESC | Back navigation | +| R | Start/Stop recording (in interactive mode) | + +--- + +## Shell Architecture + +The shell (`apps/shell/`) provides a persistent system UI layer that prevents apps from taking over the full screen. + +### Shell Components + +``` +┌──────────────────────────────┐ +│ Status Bar (36px) │ ← Time, WiFi, Signal, Battery +├──────────────────────────────┤ +│ │ +│ App Container │ ← App content loads here +│ (flex: 1) │ +│ │ +├──────────────────────────────┤ +│ Navigation Bar (56px) │ ← Back, Home, Recents +└──────────────────────────────┘ + +Overlay Layers (z-index order): +- Dialog overlay (z: 600) ← Modal dialogs +- Toast container (z: 500) ← Toast notifications +- Notification panel (z: 400) ← Pull-down notifications +- Loading overlay (z: 300) ← App loading spinner +``` + +### Shell Files + +| File | Purpose | +|------|---------| +| `apps/shell/shell.rml` | Shell document with status bar, nav bar, overlays | +| `apps/shell/shell.lua` | Navigation, toasts, dialogs, notifications | + +### App Content Fragments + +Apps in shell mode use **content fragments** (`*_content.rml`) instead of full documents: + +```rml + + + +
+ +
+``` + +### Shell Lua API + +Functions available to apps via shell: + +```lua +-- Navigation +shellNavigateTo("settings") -- Navigate to system app +shellLaunchApp(id, path, entry) -- Launch external app +shellGoBack() -- Go to previous app +shellGoHome() -- Go to home (clear history) + +-- UI Feedback +showToast("Message") -- Show toast notification +showToast("Error!", "error") -- Toast types: default, success, error, warning +showDialog("Title", "Message", onConfirm, onCancel) + +-- Notifications +addNotification("Title", "Text", icon, app_id) +toggleNotifications() -- Show/hide notification panel +clearNotifications() +``` + +### Creating Content Fragments + +1. Create `appname_content.rml` (not a full document) +2. Include styles via ` + +
+
+ WiFi +
+
+ Bluetooth +
+
+``` + +### Content Fragment Locations + +| App | Content Fragment Path | +|-----|----------------------| +| Home | `apps/home/home_content.rml` | +| Camera | `apps/camera/camera_content.rml` | +| Dialer | `apps/dialer/dialer_content.rml` | +| Messages | `apps/messages/messages_content.rml` | +| Browser | `apps/browser/browser_content.rml` | +| Settings | `apps/settings/settings_content.rml` | +| Music | `apps/music/music_content.rml` | +| Store | `apps/store/store_content.rml` | +| Contacts | `apps/contacts/contacts_content.rml` | + +--- + +## Hot Reload + +The designer watches for file changes in the assets directory: + +- **RML files**: Reloads document +- **RCSS files**: Reloads stylesheets +- **Lua files**: Reloads scripts +- **TGA/PNG files**: Reloads textures + +Press F5 to force reload. + +## UI Hierarchy Dump + +Use `--hierarchy` to continuously dump the UI element tree: + +```bash +./designer/build/Release/mosis-designer.exe --simulator --hierarchy ui_tree.json +``` + +The JSON contains element bounds, classes, IDs, and visibility - useful for automated testing. + +## Action Recording and Playback + +See [TESTING-FRAMEWORK.md](TESTING-FRAMEWORK.md) for details on recording and playing back UI interactions. diff --git a/run-simulator.bat b/run-simulator.bat new file mode 100644 index 0000000..2d18708 --- /dev/null +++ b/run-simulator.bat @@ -0,0 +1,2 @@ +@echo off +designer\build\Release\mosis-designer.exe --simulator --test-apps base-apps %* diff --git a/src/main/assets/apps/browser/browser_content.rml b/src/main/assets/apps/browser/browser_content.rml new file mode 100644 index 0000000..a1bf81f --- /dev/null +++ b/src/main/assets/apps/browser/browser_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Browser +
+
+ Browser App +
+
diff --git a/src/main/assets/apps/camera/camera_content.rml b/src/main/assets/apps/camera/camera_content.rml new file mode 100644 index 0000000..8ff9617 --- /dev/null +++ b/src/main/assets/apps/camera/camera_content.rml @@ -0,0 +1,44 @@ + + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ Camera Preview +
+
+
+ + +
+ Video + Photo + Portrait +
+ + +
+ +
+
+
+
+ +
+
+
diff --git a/src/main/assets/apps/contacts/contacts_content.rml b/src/main/assets/apps/contacts/contacts_content.rml new file mode 100644 index 0000000..b95e8bc --- /dev/null +++ b/src/main/assets/apps/contacts/contacts_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Contacts +
+
+ Contacts App +
+
diff --git a/src/main/assets/apps/dialer/dialer_content.rml b/src/main/assets/apps/dialer/dialer_content.rml new file mode 100644 index 0000000..6b04fd7 --- /dev/null +++ b/src/main/assets/apps/dialer/dialer_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Phone +
+
+ Dialer App +
+
diff --git a/src/main/assets/apps/home/home_content.rml b/src/main/assets/apps/home/home_content.rml new file mode 100644 index 0000000..095056b --- /dev/null +++ b/src/main/assets/apps/home/home_content.rml @@ -0,0 +1,76 @@ + + + + +
+ +
+
+ +
+
+ +
+ Browser +
+
+
+ +
+ Camera +
+
+
+ +
+ Contacts +
+
+
+ +
+ Phone +
+
+
+ +
+ Messages +
+
+
+ +
+ Music +
+
+
+ +
+ Settings +
+
+
+ +
+ Store +
+
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
diff --git a/src/main/assets/apps/messages/messages_content.rml b/src/main/assets/apps/messages/messages_content.rml new file mode 100644 index 0000000..110e6c5 --- /dev/null +++ b/src/main/assets/apps/messages/messages_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Messages +
+
+ Messages App +
+
diff --git a/src/main/assets/apps/music/music_content.rml b/src/main/assets/apps/music/music_content.rml new file mode 100644 index 0000000..4b28c36 --- /dev/null +++ b/src/main/assets/apps/music/music_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Music +
+
+ Music App +
+
diff --git a/src/main/assets/apps/settings/settings_content.rml b/src/main/assets/apps/settings/settings_content.rml new file mode 100644 index 0000000..5ec6bec --- /dev/null +++ b/src/main/assets/apps/settings/settings_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Settings +
+
+ Settings App +
+
diff --git a/src/main/assets/apps/shell/shell.lua b/src/main/assets/apps/shell/shell.lua new file mode 100644 index 0000000..ead13c1 --- /dev/null +++ b/src/main/assets/apps/shell/shell.lua @@ -0,0 +1,357 @@ +-- System Shell for Mosis +-- Provides persistent navigation, notifications, toasts, and dialogs + +local shell_document = nil +local app_container = nil + +-- Navigation state (persistent in shell) +local nav_history = {} +local current_app = "home" +local current_app_path = nil + +-- Dialog callback +local dialog_callback = nil + +-- Notification state +local notifications_expanded = false + +-- ===== INITIALIZATION ===== + +function initShell(doc) + print("[Shell] Initializing system shell...") + shell_document = doc + app_container = doc:GetElementById("app-container") + + if not app_container then + print("[Shell] ERROR: app-container not found!") + return + end + + -- Update time display + updateTime() + + -- Load home screen by default + loadAppContent_internal("home", "apps/home/home_content.rml") + + print("[Shell] Shell initialized") +end + +-- Update status bar time +function updateTime() + local time_elem = shell_document:GetElementById("shell-time") + if time_elem then + time_elem.inner_rml = "12:30" + end +end + +-- ===== APP LOADING ===== + +-- Internal function to load app content +function loadAppContent_internal(app_id, app_path) + if not app_container then + print("[Shell] ERROR: No app container") + return false + end + + print("[Shell] Loading app: " .. app_id .. " from " .. app_path) + + -- Show loading overlay + showLoading(true) + + -- Load app content using C++ function + if loadAppContent then + local success = loadAppContent(app_container, app_path) + showLoading(false) + + if success then + current_app = app_id + current_app_path = app_path + print("[Shell] App loaded: " .. app_id) + return true + else + print("[Shell] Failed to load app: " .. app_id) + showToast("Failed to load app", "error") + return false + end + else + showLoading(false) + print("[Shell] ERROR: loadAppContent not available, using fallback") + + -- Fallback: try to load via inner_rml if we can read the file + -- This won't work without C++ support, but shows the intent + return false + end +end + +-- Navigate to a system app by ID +function shellNavigateTo(app_id) + local app_paths = { + home = "apps/home/home_content.rml", + dialer = "apps/dialer/dialer_content.rml", + calling = "apps/dialer/calling_content.rml", + contacts = "apps/contacts/contacts_content.rml", + contact_detail = "apps/contacts/contact_detail_content.rml", + messages = "apps/messages/messages_content.rml", + chat = "apps/messages/chat_content.rml", + settings = "apps/settings/settings_content.rml", + browser = "apps/browser/browser_content.rml", + store = "apps/store/store_content.rml", + camera = "apps/camera/camera_content.rml", + music = "apps/music/music_content.rml" + } + + local path = app_paths[app_id] + if path then + -- Push current app to history + if current_app and current_app ~= app_id then + table.insert(nav_history, { + app_id = current_app, + app_path = current_app_path + }) + print("[Shell] Pushed to history: " .. current_app .. " (depth: " .. #nav_history .. ")") + end + return loadAppContent_internal(app_id, path) + else + print("[Shell] Unknown app: " .. tostring(app_id)) + return false + end +end + +-- ===== NAVIGATION ===== + +-- Go back to previous app +function shellGoBack() + print("[Shell] goBack called (history depth: " .. #nav_history .. ")") + + -- Hide notifications if open + if notifications_expanded then + toggleNotifications() + end + + if #nav_history > 0 then + local previous = table.remove(nav_history) + print("[Shell] Going back to: " .. previous.app_id) + + -- Don't push to history when going back + local temp_app = current_app + current_app = nil + current_app_path = nil + + local success = loadAppContent_internal(previous.app_id, previous.app_path) + if not success then + -- Restore state on failure + current_app = temp_app + end + return success + else + print("[Shell] No history - already at root") + if current_app ~= "home" then + shellGoHome() + end + end + return false +end + +-- Go to home screen (clear history) +function shellGoHome() + print("[Shell] goHome called") + + -- Hide notifications if open + if notifications_expanded then + toggleNotifications() + end + + -- Clear history + nav_history = {} + + -- Load home + current_app = nil + current_app_path = nil + loadAppContent_internal("home", "apps/home/home_content.rml") +end + +-- Show recents (placeholder) +function shellShowRecents() + print("[Shell] showRecents called") + showToast("Recent apps not implemented", "warning") +end + +-- Launch external app (from base-apps) +function shellLaunchApp(package_id, app_path, entry_point) + print("[Shell] Launching external app: " .. package_id) + + -- Push current to history + if current_app then + table.insert(nav_history, { + app_id = current_app, + app_path = current_app_path + }) + end + + -- For external apps, we need content version of the entry + -- Convert "app.rml" to "app_content.rml" or load directly + local content_path = app_path .. "/" .. entry_point + + -- Switch sandbox context if available + if switchAppSandbox then + switchAppSandbox(package_id, app_path) + end + + return loadAppContent_internal(package_id, content_path) +end + +-- ===== TOASTS ===== + +function showToast(message, type) + type = type or "default" + + local container = shell_document:GetElementById("toast-container") + if not container then + print("[Shell] Toast container not found") + return + end + + local class = "toast" + if type == "success" then + class = "toast toast-success" + elseif type == "error" then + class = "toast toast-error" + elseif type == "warning" then + class = "toast toast-warning" + end + + -- Add toast element + local toast_html = '
' .. message .. '
' + container.inner_rml = container.inner_rml .. toast_html + + -- Auto-remove after 3 seconds (would need timer API) + if mosis and mosis.timer then + mosis.timer.setTimeout(function() + -- Remove first toast + container.inner_rml = "" + end, 3000) + end + + print("[Shell] Toast: " .. message .. " (" .. type .. ")") +end + +-- ===== DIALOGS ===== + +function showDialog(title, message, on_confirm, on_cancel) + local overlay = shell_document:GetElementById("dialog-overlay") + local title_elem = shell_document:GetElementById("dialog-title") + local message_elem = shell_document:GetElementById("dialog-message") + + if overlay and title_elem and message_elem then + title_elem.inner_rml = title + message_elem.inner_rml = message + overlay:SetClass("visible", true) + + dialog_callback = { + confirm = on_confirm, + cancel = on_cancel + } + + print("[Shell] Dialog shown: " .. title) + end +end + +function dialogConfirm() + local overlay = shell_document:GetElementById("dialog-overlay") + if overlay then + overlay:SetClass("visible", false) + end + + if dialog_callback and dialog_callback.confirm then + dialog_callback.confirm() + end + dialog_callback = nil +end + +function dialogCancel() + local overlay = shell_document:GetElementById("dialog-overlay") + if overlay then + overlay:SetClass("visible", false) + end + + if dialog_callback and dialog_callback.cancel then + dialog_callback.cancel() + end + dialog_callback = nil +end + +-- ===== NOTIFICATIONS ===== + +function toggleNotifications() + local panel = shell_document:GetElementById("notification-panel") + if panel then + notifications_expanded = not notifications_expanded + panel:SetClass("expanded", notifications_expanded) + print("[Shell] Notifications " .. (notifications_expanded and "expanded" or "collapsed")) + end +end + +function addNotification(title, text, icon, app_id) + local list = shell_document:GetElementById("notification-list") + if not list then return end + + icon = icon or "../../icons/message.tga" + local notification_html = [[ +
+
+ +
+
+
]] .. title .. [[
+
]] .. text .. [[
+
+ now +
+ ]] + + list.inner_rml = notification_html .. list.inner_rml + print("[Shell] Notification added: " .. title) +end + +function onNotificationClick(app_id) + toggleNotifications() + if app_id and app_id ~= "" then + shellNavigateTo(app_id) + end +end + +function clearNotifications() + local list = shell_document:GetElementById("notification-list") + if list then + list.inner_rml = "" + print("[Shell] Notifications cleared") + end +end + +-- ===== LOADING OVERLAY ===== + +function showLoading(visible) + local overlay = shell_document:GetElementById("loading-overlay") + if overlay then + overlay:SetClass("visible", visible) + end +end + +-- ===== UTILITY ===== + +function shellGetCurrentApp() + return current_app +end + +function shellCanGoBack() + return #nav_history > 0 +end + +-- Make shell functions globally available for apps +_G.showToast = showToast +_G.showDialog = showDialog +_G.addNotification = addNotification +_G.shellNavigateTo = shellNavigateTo +_G.shellLaunchApp = shellLaunchApp + +print("[Shell] Shell script loaded") diff --git a/src/main/assets/apps/shell/shell.rml b/src/main/assets/apps/shell/shell.rml new file mode 100644 index 0000000..35f8366 --- /dev/null +++ b/src/main/assets/apps/shell/shell.rml @@ -0,0 +1,650 @@ + + + + + + + Mosis Shell + + + + +
+ 12:30 +
+ + + +
+
+ + +
+ +
+ + +
+
+ +
+
+
+
+
+ +
+
+ + + + +
+
+ +
+
+ + +
+ +
+ + +
+
+
+ + +
+
+
Title
+
Message
+
+
Cancel
+
OK
+
+
+
+ +
diff --git a/src/main/assets/apps/store/store_content.rml b/src/main/assets/apps/store/store_content.rml new file mode 100644 index 0000000..2ec727b --- /dev/null +++ b/src/main/assets/apps/store/store_content.rml @@ -0,0 +1,11 @@ + + + +
+
+ Store +
+
+ Store App +
+