From 1f91d7508e0ea8efde09d87465f554d6bbfcc456 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 20 Jan 2026 09:14:05 +0100 Subject: [PATCH] add base-apps with manifests, layout system, and testing documentation - Rename test-apps to base-apps with proper manifest.json for each app - Add is_system_app flag to app discovery and Lua API - Fix icon path resolution for /system/icons/ paths - Add layout.lua and layout.rcss for reusable UI components - Update home screen to dynamically load all apps from manifests - Update all app RML files to use layout components - Comprehensive testing framework documentation with JSON action format - Add tests/ directory structure for automated UI testing --- base-apps/com.mosis.browser/browser.rml | 247 ++ base-apps/com.mosis.browser/manifest.json | 18 + base-apps/com.mosis.camera/camera.rml | 368 +++ base-apps/com.mosis.camera/manifest.json | 19 + .../com.mosis.contacts/contact_detail.rml | 161 + base-apps/com.mosis.contacts/contacts.rml | 321 ++ base-apps/com.mosis.contacts/manifest.json | 18 + base-apps/com.mosis.dialer/calling.rml | 126 + base-apps/com.mosis.dialer/dialer.rml | 157 + base-apps/com.mosis.dialer/manifest.json | 18 + base-apps/com.mosis.home/home.lua | 200 ++ base-apps/com.mosis.home/home.rml | 176 ++ base-apps/com.mosis.home/lock.rml | 176 ++ base-apps/com.mosis.home/manifest.json | 16 + base-apps/com.mosis.messages/chat.rml | 164 ++ base-apps/com.mosis.messages/manifest.json | 18 + base-apps/com.mosis.messages/messages.rml | 220 ++ base-apps/com.mosis.music/manifest.json | 18 + base-apps/com.mosis.music/music.rml | 415 +++ .../com.mosis.sandbox-test.mosis | Bin .../com.mosis.sandbox-test/app.lua | 0 .../com.mosis.sandbox-test/icon.tga | 0 .../com.mosis.sandbox-test/main.rml | 0 .../com.mosis.sandbox-test/manifest.json | 0 .../com.mosis.sandbox-test/styles.rcss | 0 base-apps/com.mosis.settings/manifest.json | 16 + base-apps/com.mosis.settings/settings.rml | 360 +++ base-apps/com.mosis.store/manifest.json | 19 + base-apps/com.mosis.store/store.lua | 394 +++ base-apps/com.mosis.store/store.rml | 416 +++ base-apps/icons/account.tga | 3 + base-apps/icons/add.tga | 3 + base-apps/icons/back.tga | 3 + base-apps/icons/backspace.tga | 3 + base-apps/icons/battery.tga | 3 + base-apps/icons/browser.tga | 3 + base-apps/icons/calculator.tga | 3 + base-apps/icons/calendar.tga | 3 + base-apps/icons/call_small.tga | 3 + base-apps/icons/camera.tga | 3 + base-apps/icons/clock.tga | 3 + base-apps/icons/close.tga | 3 + base-apps/icons/contact_phone.tga | 3 + base-apps/icons/contacts.tga | 3 + base-apps/icons/dialpad.tga | 3 + base-apps/icons/download.tga | 3 + base-apps/icons/files.tga | 3 + base-apps/icons/flash.tga | 3 + base-apps/icons/forward.tga | 3 + base-apps/icons/gallery.tga | 3 + base-apps/icons/game.tga | 3 + base-apps/icons/heart.tga | 3 + base-apps/icons/history.tga | 3 + base-apps/icons/home.tga | 3 + base-apps/icons/library.tga | 3 + base-apps/icons/maps.tga | 3 + base-apps/icons/menu.tga | 3 + base-apps/icons/message.tga | 3 + base-apps/icons/more.tga | 3 + base-apps/icons/music.tga | 3 + base-apps/icons/notes.tga | 3 + base-apps/icons/phone.tga | 3 + base-apps/icons/play.tga | 3 + base-apps/icons/refresh.tga | 3 + base-apps/icons/search.tga | 3 + base-apps/icons/send.tga | 3 + base-apps/icons/settings.tga | 3 + base-apps/icons/signal.tga | 3 + base-apps/icons/store.tga | 3 + base-apps/icons/switch-camera.tga | 3 + base-apps/icons/timer.tga | 3 + base-apps/icons/weather.tga | 3 + base-apps/icons/wifi.tga | 3 + {test-apps => base-apps}/package.bat | 0 base-apps/scripts/layout.lua | 103 + base-apps/scripts/navigation.lua | 145 + base-apps/ui/components.rcss | 1485 ++++++++++ base-apps/ui/html.rcss | 93 + base-apps/ui/layout.rcss | 270 ++ base-apps/ui/theme.rcss | 333 +++ designer/main.cpp | 2 +- designer/src/app_discovery.cpp | 1 + designer/src/app_discovery.h | 11 +- docs/BASE_APPS.md | 386 +++ docs/TESTING-FRAMEWORK.md | 336 ++- src/main/assets/apps/browser/browser.rml | 125 +- src/main/assets/apps/camera/camera.rml | 105 +- src/main/assets/apps/contacts/contacts.rml | 248 +- src/main/assets/apps/dialer/dialer.rml | 277 +- src/main/assets/apps/home/home.lua | 66 +- src/main/assets/apps/home/home.rml | 85 +- src/main/assets/apps/messages/messages.rml | 159 +- src/main/assets/apps/music/music.rml | 109 +- src/main/assets/apps/settings/settings.rml | 347 ++- src/main/assets/apps/store/store.rml | 338 +-- src/main/assets/scripts/layout.lua | 103 + src/main/assets/ui/layout.rcss | 270 ++ tests/README.md | 55 + tests/hierarchy_dump.json | 2583 +++++++++++++++++ tests/hierarchy_fresh.json | 1844 ++++++++++++ tests/screenshots/test_result.png | Bin 0 -> 23729 bytes 101 files changed, 13103 insertions(+), 966 deletions(-) create mode 100644 base-apps/com.mosis.browser/browser.rml create mode 100644 base-apps/com.mosis.browser/manifest.json create mode 100644 base-apps/com.mosis.camera/camera.rml create mode 100644 base-apps/com.mosis.camera/manifest.json create mode 100644 base-apps/com.mosis.contacts/contact_detail.rml create mode 100644 base-apps/com.mosis.contacts/contacts.rml create mode 100644 base-apps/com.mosis.contacts/manifest.json create mode 100644 base-apps/com.mosis.dialer/calling.rml create mode 100644 base-apps/com.mosis.dialer/dialer.rml create mode 100644 base-apps/com.mosis.dialer/manifest.json create mode 100644 base-apps/com.mosis.home/home.lua create mode 100644 base-apps/com.mosis.home/home.rml create mode 100644 base-apps/com.mosis.home/lock.rml create mode 100644 base-apps/com.mosis.home/manifest.json create mode 100644 base-apps/com.mosis.messages/chat.rml create mode 100644 base-apps/com.mosis.messages/manifest.json create mode 100644 base-apps/com.mosis.messages/messages.rml create mode 100644 base-apps/com.mosis.music/manifest.json create mode 100644 base-apps/com.mosis.music/music.rml rename {test-apps => base-apps}/com.mosis.sandbox-test.mosis (100%) rename {test-apps => base-apps}/com.mosis.sandbox-test/app.lua (100%) rename {test-apps => base-apps}/com.mosis.sandbox-test/icon.tga (100%) rename {test-apps => base-apps}/com.mosis.sandbox-test/main.rml (100%) rename {test-apps => base-apps}/com.mosis.sandbox-test/manifest.json (100%) rename {test-apps => base-apps}/com.mosis.sandbox-test/styles.rcss (100%) create mode 100644 base-apps/com.mosis.settings/manifest.json create mode 100644 base-apps/com.mosis.settings/settings.rml create mode 100644 base-apps/com.mosis.store/manifest.json create mode 100644 base-apps/com.mosis.store/store.lua create mode 100644 base-apps/com.mosis.store/store.rml create mode 100644 base-apps/icons/account.tga create mode 100644 base-apps/icons/add.tga create mode 100644 base-apps/icons/back.tga create mode 100644 base-apps/icons/backspace.tga create mode 100644 base-apps/icons/battery.tga create mode 100644 base-apps/icons/browser.tga create mode 100644 base-apps/icons/calculator.tga create mode 100644 base-apps/icons/calendar.tga create mode 100644 base-apps/icons/call_small.tga create mode 100644 base-apps/icons/camera.tga create mode 100644 base-apps/icons/clock.tga create mode 100644 base-apps/icons/close.tga create mode 100644 base-apps/icons/contact_phone.tga create mode 100644 base-apps/icons/contacts.tga create mode 100644 base-apps/icons/dialpad.tga create mode 100644 base-apps/icons/download.tga create mode 100644 base-apps/icons/files.tga create mode 100644 base-apps/icons/flash.tga create mode 100644 base-apps/icons/forward.tga create mode 100644 base-apps/icons/gallery.tga create mode 100644 base-apps/icons/game.tga create mode 100644 base-apps/icons/heart.tga create mode 100644 base-apps/icons/history.tga create mode 100644 base-apps/icons/home.tga create mode 100644 base-apps/icons/library.tga create mode 100644 base-apps/icons/maps.tga create mode 100644 base-apps/icons/menu.tga create mode 100644 base-apps/icons/message.tga create mode 100644 base-apps/icons/more.tga create mode 100644 base-apps/icons/music.tga create mode 100644 base-apps/icons/notes.tga create mode 100644 base-apps/icons/phone.tga create mode 100644 base-apps/icons/play.tga create mode 100644 base-apps/icons/refresh.tga create mode 100644 base-apps/icons/search.tga create mode 100644 base-apps/icons/send.tga create mode 100644 base-apps/icons/settings.tga create mode 100644 base-apps/icons/signal.tga create mode 100644 base-apps/icons/store.tga create mode 100644 base-apps/icons/switch-camera.tga create mode 100644 base-apps/icons/timer.tga create mode 100644 base-apps/icons/weather.tga create mode 100644 base-apps/icons/wifi.tga rename {test-apps => base-apps}/package.bat (100%) create mode 100644 base-apps/scripts/layout.lua create mode 100644 base-apps/scripts/navigation.lua create mode 100644 base-apps/ui/components.rcss create mode 100644 base-apps/ui/html.rcss create mode 100644 base-apps/ui/layout.rcss create mode 100644 base-apps/ui/theme.rcss create mode 100644 docs/BASE_APPS.md create mode 100644 src/main/assets/scripts/layout.lua create mode 100644 src/main/assets/ui/layout.rcss create mode 100644 tests/README.md create mode 100644 tests/hierarchy_dump.json create mode 100644 tests/hierarchy_fresh.json create mode 100644 tests/screenshots/test_result.png diff --git a/base-apps/com.mosis.browser/browser.rml b/base-apps/com.mosis.browser/browser.rml new file mode 100644 index 0000000..bfbba21 --- /dev/null +++ b/base-apps/com.mosis.browser/browser.rml @@ -0,0 +1,247 @@ + + + + + + + + + Browser + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+
+ +
+
+ S + +
+
+ +
+
+ +
+
+ + +
+
+
Example Domain
+
+ This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission. +
+
+ More information... +
+ +
+
Related Links
+
+
IANA - IANA-managed Reserved Domains
+
www.iana.org > domains > reserved
+
Certain domains are set aside and unavailable for registration.
+
+
+
RFC 2606 - Reserved Top Level DNS Names
+
tools.ietf.org > html > rfc2606
+
This document describes domain names reserved for documentation.
+
+
+
+
+ + +
+
+ + Home +
+
+ 1 + Tabs +
+
+ + New Tab +
+
+ + Menu +
+
+ +
diff --git a/base-apps/com.mosis.browser/manifest.json b/base-apps/com.mosis.browser/manifest.json new file mode 100644 index 0000000..5e22046 --- /dev/null +++ b/base-apps/com.mosis.browser/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "com.mosis.browser", + "name": "Browser", + "version": "1.0.0", + "version_code": 1, + "entry": "browser.rml", + "icon": "/system/icons/browser.tga", + "description": "Web browser application", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "network" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.camera/camera.rml b/base-apps/com.mosis.camera/camera.rml new file mode 100644 index 0000000..8e8b1dc --- /dev/null +++ b/base-apps/com.mosis.camera/camera.rml @@ -0,0 +1,368 @@ + + + + + + + + + Camera + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
C
+
Camera Preview
+
+ Tap to focus +
+
+ + +
+
+
+
+
+
+ + +
+
+ + +
Flash: Auto
+
Timer: Off
+ + +
+
-
+ 1.0x +
+
+
+
+ + +
+ Night + Portrait + Photo + Video + More +
+ + +
+ +
+
+
+
+ +
+
+ +
diff --git a/base-apps/com.mosis.camera/manifest.json b/base-apps/com.mosis.camera/manifest.json new file mode 100644 index 0000000..487f1c3 --- /dev/null +++ b/base-apps/com.mosis.camera/manifest.json @@ -0,0 +1,19 @@ +{ + "id": "com.mosis.camera", + "name": "Camera", + "version": "1.0.0", + "version_code": 1, + "entry": "camera.rml", + "icon": "/system/icons/camera.tga", + "description": "Camera and photo capture", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "camera", + "storage" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.contacts/contact_detail.rml b/base-apps/com.mosis.contacts/contact_detail.rml new file mode 100644 index 0000000..348366a --- /dev/null +++ b/base-apps/com.mosis.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/base-apps/com.mosis.contacts/contacts.rml b/base-apps/com.mosis.contacts/contacts.rml new file mode 100644 index 0000000..2901d77 --- /dev/null +++ b/base-apps/com.mosis.contacts/contacts.rml @@ -0,0 +1,321 @@ + + + + + + + + + Contacts + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ Contacts +
+
+ +
+
+ +
+
+
+ + + + + +
+
+ +
A
+
+
A
+
+
Alice Johnson
+
+1 555-0101
+
+
+ +
+
+
+
A
+
+
Andrew Smith
+
+1 555-0102
+
+
+ +
+
+ + +
B
+
+
B
+
+
Bob Williams
+
+1 555-0201
+
+
+ +
+
+ + +
C
+
+
C
+
+
Carol Davis
+
+1 555-0301
+
+
+ +
+
+
+
C
+
+
Chris Miller
+
+1 555-0302
+
+
+ +
+
+ + +
D
+
+
D
+
+
David Brown
+
+1 555-0401
+
+
+ +
+
+ + +
E
+
+
E
+
+
Emma Wilson
+
+1 555-0501
+
+
+ +
+
+ + +
J
+
+
J
+
+
John Doe
+
+1 555-1234
+
+
+ +
+
+ + +
M
+
+
M
+
+
Mary Taylor
+
+1 555-0601
+
+
+ +
+
+
+
M
+
+
Michael Lee
+
+1 555-0602
+
+
+ +
+
+ + +
S
+
+
S
+
+
Sarah Anderson
+
+1 555-0701
+
+
+ +
+
+
+
+ + +
+ +
+ + +
+
+ + Keypad +
+
+ + Recent +
+
+ + Contacts +
+
+ +
diff --git a/base-apps/com.mosis.contacts/manifest.json b/base-apps/com.mosis.contacts/manifest.json new file mode 100644 index 0000000..525d1da --- /dev/null +++ b/base-apps/com.mosis.contacts/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "com.mosis.contacts", + "name": "Contacts", + "version": "1.0.0", + "version_code": 1, + "entry": "contacts.rml", + "icon": "/system/icons/contacts.tga", + "description": "Contact list and management", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "contacts" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.dialer/calling.rml b/base-apps/com.mosis.dialer/calling.rml new file mode 100644 index 0000000..adc1e8c --- /dev/null +++ b/base-apps/com.mosis.dialer/calling.rml @@ -0,0 +1,126 @@ + + + + + + + + Calling + + + +
Calling...
+
Unknown
+
+ +
?
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
diff --git a/base-apps/com.mosis.dialer/dialer.rml b/base-apps/com.mosis.dialer/dialer.rml new file mode 100644 index 0000000..c5bd8c9 --- /dev/null +++ b/base-apps/com.mosis.dialer/dialer.rml @@ -0,0 +1,157 @@ + + + + + + + + + Phone + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ Phone +
+ + +
+ +
{{ 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/base-apps/com.mosis.dialer/manifest.json b/base-apps/com.mosis.dialer/manifest.json new file mode 100644 index 0000000..8a4b769 --- /dev/null +++ b/base-apps/com.mosis.dialer/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "com.mosis.dialer", + "name": "Phone", + "version": "1.0.0", + "version_code": 1, + "entry": "dialer.rml", + "icon": "/system/icons/phone.tga", + "description": "Phone dialer and call interface", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "phone" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.home/home.lua b/base-apps/com.mosis.home/home.lua new file mode 100644 index 0000000..bbdadca --- /dev/null +++ b/base-apps/com.mosis.home/home.lua @@ -0,0 +1,200 @@ +-- home.lua - Home screen dynamic app rendering +-- Handles system apps and discovered third-party apps + +-- System apps with their navigation keys and colors +local system_apps = { + -- Row 1 + {name = "Phone", icon = "phone", color = "#4CAF50", nav = "dialer"}, + {name = "Messages", icon = "message", color = "#2196F3", nav = "messages"}, + {name = "Contacts", icon = "contacts", color = "#FF9800", nav = "contacts"}, + {name = "Browser", icon = "browser", color = "#F44336", nav = "browser"}, + -- Row 2 + {name = "Gallery", icon = "gallery", color = "#9C27B0", nav = nil}, + {name = "Camera", icon = "camera", color = "#00BCD4", nav = "camera"}, + {name = "Settings", icon = "settings", color = "#607D8B", nav = "settings"}, + {name = "Music", icon = "music", color = "#E91E63", nav = "music"}, + -- Row 3 + {name = "Calendar", icon = "calendar", color = "#3F51B5", nav = nil}, + {name = "Clock", icon = "clock", color = "#009688", nav = nil}, + {name = "Notes", icon = "notes", color = "#795548", nav = nil}, + {name = "Maps", icon = "maps", color = "#FF5722", nav = nil}, + -- Row 4 + {name = "Store", icon = "store", color = "#8BC34A", nav = "store"}, + {name = "Files", icon = "files", color = "#CDDC39", nav = nil}, + {name = "Calculator", icon = "calculator", color = "#FFC107", nav = nil}, + {name = "Weather", icon = "weather", color = "#673AB7", nav = nil}, +} + +-- State +local installed_apps = {} +local home_document = nil -- Store document reference + +-- Initialize on load (receives document from onload event) +function initHome(doc) + print("[Home] Initializing home screen...") + home_document = doc + + -- Get installed third-party apps + if mosis and mosis.apps then + installed_apps = mosis.apps.getInstalled() or {} + print("[Home] Found " .. #installed_apps .. " installed apps") + + -- Filter to only third-party (non-system) apps + local third_party = {} + for _, app in ipairs(installed_apps) do + if not app.is_system_app then + table.insert(third_party, app) + print("[Home] Third-party app: " .. app.name .. " (" .. app.package_id .. ")") + end + end + installed_apps = third_party + else + print("[Home] Warning: mosis.apps API not available") + installed_apps = {} + end + + -- Render dynamic apps + renderThirdPartyApps() +end + +-- Generate a color based on package_id +function getAppColor(package_id) + local colors = { + "#BB86FC", "#03DAC6", "#FF9800", "#2196F3", + "#4CAF50", "#F44336", "#E91E63", "#3F51B5", + "#009688", "#795548", "#FF5722", "#673AB7" + } + + -- Simple hash of package_id to pick a color + local hash = 0 + for i = 1, #package_id do + hash = hash + package_id:byte(i) + end + + return colors[(hash % #colors) + 1] +end + +-- Get first letter for placeholder icon +function getAppInitial(name) + return name:sub(1, 1):upper() +end + +-- Render third-party apps into the grid +function renderThirdPartyApps() + -- Use stored document reference + if not home_document then + print("[Home] Could not get document reference") + return + end + + local grid = home_document:GetElementById("third-party-apps") + if not grid then + print("[Home] third-party-apps container not found") + return + end + + -- Clear existing content + grid.inner_rml = "" + + if #installed_apps == 0 then + print("[Home] No third-party apps to display") + return + end + + -- Build HTML for each app + local html = "" + for _, app in ipairs(installed_apps) do + local color = getAppColor(app.package_id) + local initial = getAppInitial(app.name) + local icon_html + + -- Check if app has an icon + if app.icon and app.icon ~= "" then + local icon_path + -- Check if icon is already a full path (starts with / or contains :/) + if app.icon:sub(1, 1) == "/" or app.icon:find(":/") then + -- Already a full path + icon_path = app.icon + elseif app.install_path and app.install_path ~= "" then + -- Relative filename - construct full path from install_path + icon_path = app.install_path .. "/" .. app.icon + else + icon_path = app.icon + end + -- Use file:// prefix for absolute paths to prevent RmlUi URL resolution + local src_path = icon_path + if icon_path:sub(1, 1) == "/" then + src_path = "file://" .. icon_path + end + -- Use img tag for actual icon + icon_html = '' + print("[Home] Loading icon: " .. src_path) + else + -- Fallback to initial letter + icon_html = '' .. initial .. '' + end + + html = html .. [[ +
+
+ ]] .. icon_html .. [[ +
+ ]] .. app.name .. [[ +
+ ]] + end + + grid.inner_rml = html + print("[Home] Rendered " .. #installed_apps .. " third-party apps") +end + +-- Get app info by package_id +function getAppInfo(package_id) + for _, app in ipairs(installed_apps) do + if app.package_id == package_id then + return app + end + end + return nil +end + +-- Launch a third-party app +function launchThirdPartyApp(package_id) + print("[Home] Launching app: " .. package_id) + + if mosis and mosis.apps then + local success = mosis.apps.launch(package_id) + if success then + print("[Home] App sandbox started: " .. package_id) + + -- Get app info for sandbox switching and UI loading + local app_info = getAppInfo(package_id) + if app_info and app_info.install_path and app_info.entry_point then + -- Switch sandbox context to this app (registers timer, fs, json, crypto APIs) + if switchAppSandbox then + switchAppSandbox(package_id, app_info.install_path) + print("[Home] Sandbox context switched to: " .. package_id) + end + + -- Now load the app's UI document + local entry_path = app_info.install_path .. "/" .. app_info.entry_point + print("[Home] Loading app screen: " .. entry_path) + local loaded = loadScreen(entry_path) + if loaded then + print("[Home] App UI loaded: " .. package_id) + else + print("[Home] Failed to load app UI: " .. entry_path) + end + else + print("[Home] App info missing entry point: " .. package_id) + end + else + print("[Home] Failed to launch app: " .. package_id) + end + else + print("[Home] Cannot launch app: mosis.apps not available") + end +end + +-- initHome() is called via onload in home.rml diff --git a/base-apps/com.mosis.home/home.rml b/base-apps/com.mosis.home/home.rml new file mode 100644 index 0000000..1cc988e --- /dev/null +++ b/base-apps/com.mosis.home/home.rml @@ -0,0 +1,176 @@ + + + + + + + + Virtual Smartphone - Home + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+
+ Phone +
+
+
+ Messages +
+
+
+ Contacts +
+
+
+ Browser +
+ + +
+
+ Gallery +
+
+
+ Camera +
+
+
+ Settings +
+
+
+ Music +
+ + +
+
+ Calendar +
+
+
+ Clock +
+
+
+ Notes +
+
+
+ Maps +
+ + +
+
+ Store +
+
+
+ Files +
+
+
+ Calculator +
+
+
+ Weather +
+ + +
+ +
+
+
+ + +
+
+
+
+
+
+ +
diff --git a/base-apps/com.mosis.home/lock.rml b/base-apps/com.mosis.home/lock.rml new file mode 100644 index 0000000..07ca873 --- /dev/null +++ b/base-apps/com.mosis.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/base-apps/com.mosis.home/manifest.json b/base-apps/com.mosis.home/manifest.json new file mode 100644 index 0000000..0b87299 --- /dev/null +++ b/base-apps/com.mosis.home/manifest.json @@ -0,0 +1,16 @@ +{ + "id": "com.mosis.home", + "name": "Home", + "version": "1.0.0", + "version_code": 1, + "entry": "home.rml", + "icon": "/system/icons/home.tga", + "description": "Mosis home screen and app launcher", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.messages/chat.rml b/base-apps/com.mosis.messages/chat.rml new file mode 100644 index 0000000..15fdd13 --- /dev/null +++ b/base-apps/com.mosis.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/base-apps/com.mosis.messages/manifest.json b/base-apps/com.mosis.messages/manifest.json new file mode 100644 index 0000000..7edf98f --- /dev/null +++ b/base-apps/com.mosis.messages/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "com.mosis.messages", + "name": "Messages", + "version": "1.0.0", + "version_code": 1, + "entry": "messages.rml", + "icon": "/system/icons/message.tga", + "description": "SMS and messaging application", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "sms" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.messages/messages.rml b/base-apps/com.mosis.messages/messages.rml new file mode 100644 index 0000000..073ec88 --- /dev/null +++ b/base-apps/com.mosis.messages/messages.rml @@ -0,0 +1,220 @@ + + + + + + + + + Messages + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ Messages +
+
+ +
+
+
+ + +
+
+ +
+
A
+
+
+ Alice Johnson + 2:34 PM +
+
Hey! Are you coming to the party tonight?
+
+
2
+
+ + +
+
B
+
+
+ Bob Williams + 1:15 PM +
+
Thanks for the help yesterday!
+
+
+ + +
+
C
+
+
+ Carol Davis + Yesterday +
+
The meeting has been rescheduled to Friday
+
+
+ + +
+
D
+
+
+ David Brown + Yesterday +
+
Can you send me the files?
+
+
1
+
+ + +
+
E
+
+
+ Emma Wilson + Mon +
+
See you at the coffee shop!
+
+
+ + +
+
F
+
+
+ Frank Miller + Sun +
+
Great game last night!
+
+
+ + +
+
G
+
+
+ Grace Lee + Sat +
+
Happy birthday! 🎂
+
+
+
+
+ + +
+ +
+ + +
+
+ +
+
+
+ +
+
+ +
diff --git a/base-apps/com.mosis.music/manifest.json b/base-apps/com.mosis.music/manifest.json new file mode 100644 index 0000000..bee87f9 --- /dev/null +++ b/base-apps/com.mosis.music/manifest.json @@ -0,0 +1,18 @@ +{ + "id": "com.mosis.music", + "name": "Music", + "version": "1.0.0", + "version_code": 1, + "entry": "music.rml", + "icon": "/system/icons/music.tga", + "description": "Music player application", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "storage" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.music/music.rml b/base-apps/com.mosis.music/music.rml new file mode 100644 index 0000000..cf27378 --- /dev/null +++ b/base-apps/com.mosis.music/music.rml @@ -0,0 +1,415 @@ + + + + + + + + + Music + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ Music +
+
+ +
+
+
+ + +
+ +
+ Good afternoon +
+ + +
+
+
L
+ Liked Songs +
+
+
D
+ Daily Mix 1 +
+
+
R
+ Release Radar +
+
+
C
+ Chill Vibes +
+
+
W
+ Workout Mix +
+
+
F
+ Focus Flow +
+
+ + +
+ Recently Played + SEE ALL +
+ +
+
+
P
+
Pop Hits
+
Playlist
+
+
+
E
+
Electronic
+
Playlist
+
+
+
J
+
Jazz Classics
+
Playlist
+
+
+
R
+
Rock Legends
+
Playlist
+
+
+ + +
+ Made For You + SEE ALL +
+ +
+
1
+
+
Daily Mix 1
+
Based on your listening
+
+
+ +
+
2
+
+
Daily Mix 2
+
Electronic, Ambient, Chill
+
+
+ +
+
D
+
+
Discover Weekly
+
Your weekly mixtape
+
+
+
+ + +
+
M
+
+
Midnight City
+
M83
+
+
+
+ +
+
+ +
+
+
+ + +
+ + + +
+ +
diff --git a/test-apps/com.mosis.sandbox-test.mosis b/base-apps/com.mosis.sandbox-test.mosis similarity index 100% rename from test-apps/com.mosis.sandbox-test.mosis rename to base-apps/com.mosis.sandbox-test.mosis diff --git a/test-apps/com.mosis.sandbox-test/app.lua b/base-apps/com.mosis.sandbox-test/app.lua similarity index 100% rename from test-apps/com.mosis.sandbox-test/app.lua rename to base-apps/com.mosis.sandbox-test/app.lua diff --git a/test-apps/com.mosis.sandbox-test/icon.tga b/base-apps/com.mosis.sandbox-test/icon.tga similarity index 100% rename from test-apps/com.mosis.sandbox-test/icon.tga rename to base-apps/com.mosis.sandbox-test/icon.tga diff --git a/test-apps/com.mosis.sandbox-test/main.rml b/base-apps/com.mosis.sandbox-test/main.rml similarity index 100% rename from test-apps/com.mosis.sandbox-test/main.rml rename to base-apps/com.mosis.sandbox-test/main.rml diff --git a/test-apps/com.mosis.sandbox-test/manifest.json b/base-apps/com.mosis.sandbox-test/manifest.json similarity index 100% rename from test-apps/com.mosis.sandbox-test/manifest.json rename to base-apps/com.mosis.sandbox-test/manifest.json diff --git a/test-apps/com.mosis.sandbox-test/styles.rcss b/base-apps/com.mosis.sandbox-test/styles.rcss similarity index 100% rename from test-apps/com.mosis.sandbox-test/styles.rcss rename to base-apps/com.mosis.sandbox-test/styles.rcss diff --git a/base-apps/com.mosis.settings/manifest.json b/base-apps/com.mosis.settings/manifest.json new file mode 100644 index 0000000..8d1d9b6 --- /dev/null +++ b/base-apps/com.mosis.settings/manifest.json @@ -0,0 +1,16 @@ +{ + "id": "com.mosis.settings", + "name": "Settings", + "version": "1.0.0", + "version_code": 1, + "entry": "settings.rml", + "icon": "/system/icons/settings.tga", + "description": "System settings and configuration", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.settings/settings.rml b/base-apps/com.mosis.settings/settings.rml new file mode 100644 index 0000000..db5c398 --- /dev/null +++ b/base-apps/com.mosis.settings/settings.rml @@ -0,0 +1,360 @@ + + + + + + + + + Settings + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ Settings +
+
+ +
+
+
+ + +
+
+ +
+
U
+ + > +
+ + +
+
Network
+
+
+ +
+
+
Wi-Fi
+
Connected to MosisNetwork
+
+
+
+
+
+
+
+ +
+
+
Bluetooth
+
Off
+
+
+
+
+
+
+
+ +
+
+
Airplane Mode
+
+
+
+
+
+
+ + +
+
Device
+
+
+ +
+
+
Display
+
Brightness, wallpaper, sleep
+
+ > +
+
+
+ +
+
+
Sound
+
Volume, ringtone, vibration
+
+ > +
+
+
+ +
+
+
Notifications
+
App notifications, Do not disturb
+
+ > +
+
+
+ +
+
+
Battery
+
85% - 4h 30m remaining
+
+ > +
+
+
+ +
+
+
Storage
+
32 GB of 128 GB used
+
+ > +
+
+ + +
+
Privacy & Security
+
+
+ +
+
+
Lock Screen
+
PIN, pattern, fingerprint
+
+ > +
+
+
+ +
+
+
Privacy
+
Permissions, account activity
+
+ > +
+
+
+ +
+
+
Location
+
On - High accuracy
+
+
+
+
+
+
+ + +
+
About
+
+
+ +
+
+
About Phone
+
Mosis Virtual Phone v1.0
+
+ > +
+
+
+
+ + +
+
+ +
+
+
+ +
+
+ +
diff --git a/base-apps/com.mosis.store/manifest.json b/base-apps/com.mosis.store/manifest.json new file mode 100644 index 0000000..869ea91 --- /dev/null +++ b/base-apps/com.mosis.store/manifest.json @@ -0,0 +1,19 @@ +{ + "id": "com.mosis.store", + "name": "Mosis Store", + "version": "1.0.0", + "version_code": 1, + "entry": "store.rml", + "icon": "/system/icons/store.tga", + "description": "App store for downloading and installing apps", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "is_system_app": true, + "permissions": [ + "network", + "storage" + ], + "min_api_version": 1 +} diff --git a/base-apps/com.mosis.store/store.lua b/base-apps/com.mosis.store/store.lua new file mode 100644 index 0000000..ff579cb --- /dev/null +++ b/base-apps/com.mosis.store/store.lua @@ -0,0 +1,394 @@ +-- store.lua - App Store system app logic +-- Milestone 10: Device-Side App Management + +-- State +local state = { + screen = "home", -- home, games, updates, search, detail + installed = {}, -- Installed apps from mosis.apps + updates = {}, -- Available updates + featured = {}, -- Featured apps from store API + categories = {}, -- Category list + search_query = "", -- Current search + selected_app = nil, -- Selected app for detail view + is_loading = false, + error_message = nil +} + +-- Store API configuration +local STORE_API = "https://portal.mosis.dev/store" + +-- ============================================================================ +-- Initialization +-- ============================================================================ + +function init() + print("[Store] Initializing...") + + -- Load installed apps + refreshInstalledApps() + + -- Check for updates + checkForUpdates() + + -- Fetch featured apps (async) + fetchFeaturedApps() +end + +function refreshInstalledApps() + if mosis and mosis.apps then + state.installed = mosis.apps.getInstalled() or {} + print("[Store] Loaded " .. #state.installed .. " installed apps") + else + print("[Store] Warning: mosis.apps API not available") + state.installed = {} + end +end + +function checkForUpdates() + if mosis and mosis.apps then + state.updates = mosis.apps.checkUpdates() or {} + print("[Store] Found " .. #state.updates .. " updates") + updateBadge() + end +end + +function updateBadge() + -- Update the updates tab badge + local badge = document:GetElementById("updates-badge") + if badge then + if #state.updates > 0 then + badge.inner_rml = tostring(#state.updates) + badge.style.display = "block" + else + badge.style.display = "none" + end + end +end + +-- ============================================================================ +-- API Calls +-- ============================================================================ + +function fetchFeaturedApps() + state.is_loading = true + + -- TODO: Make HTTP request to STORE_API + -- For now, use placeholder data + state.featured = { + { + id = "com.mosis.weather", + name = "Weather Pro", + category = "Weather", + rating = 4.8, + downloads = 125000, + size = 15728640, -- 15 MB + description = "Beautiful forecasts for your virtual world", + icon = "W", + color = "#2196F3" + }, + { + id = "com.mosis.notes", + name = "Notes", + category = "Productivity", + rating = 4.7, + downloads = 89000, + size = 8388608, -- 8 MB + description = "Simple note-taking app", + icon = "N", + color = "#03DAC6" + } + } + + state.is_loading = false + render() +end + +function searchApps(query) + state.search_query = query + state.screen = "search" + + if query == "" then + state.screen = "home" + render() + return + end + + state.is_loading = true + render() + + -- TODO: Make HTTP request to STORE_API/search + -- For now, filter featured apps + local results = {} + local lower_query = query:lower() + for _, app in ipairs(state.featured) do + if app.name:lower():find(lower_query) or + app.category:lower():find(lower_query) then + table.insert(results, app) + end + end + + state.search_results = results + state.is_loading = false + render() +end + +-- ============================================================================ +-- Installation +-- ============================================================================ + +function installApp(app_id, download_url, signature) + print("[Store] Installing: " .. app_id) + + showProgress(app_id) + + if mosis and mosis.apps then + mosis.apps.install(download_url or "", signature or "", function(progress) + updateProgress(progress) + + if progress.stage == "complete" then + hideProgress() + showToast("App installed successfully!") + refreshInstalledApps() + render() + elseif progress.stage == "failed" then + hideProgress() + showError("Installation failed: " .. (progress.error or "Unknown error")) + end + end) + else + hideProgress() + showError("App installation not available") + end +end + +function uninstallApp(package_id) + print("[Store] Uninstalling: " .. package_id) + + if mosis and mosis.apps then + local success = mosis.apps.uninstall(package_id) + if success then + showToast("App uninstalled") + refreshInstalledApps() + render() + else + showError("Failed to uninstall app") + end + end +end + +function openApp(package_id) + print("[Store] Launching: " .. package_id) + + if mosis and mosis.apps then + mosis.apps.launch(package_id) + end +end + +function updateApp(package_id) + print("[Store] Updating: " .. package_id) + + -- Find update info + for _, update in ipairs(state.updates) do + if update.package_id == package_id then + installApp(package_id, update.download_url, update.signature) + return + end + end + + showError("No update available for this app") +end + +function updateAllApps() + print("[Store] Updating all apps...") + + for _, update in ipairs(state.updates) do + -- Queue updates (in a real implementation, this would be sequential) + installApp(update.package_id, update.download_url, update.signature) + end +end + +-- ============================================================================ +-- UI Helpers +-- ============================================================================ + +function isInstalled(package_id) + for _, app in ipairs(state.installed) do + if app.package_id == package_id then + return true + end + end + return false +end + +function hasUpdate(package_id) + for _, update in ipairs(state.updates) do + if update.package_id == package_id then + return true + end + end + return false +end + +function formatSize(bytes) + if bytes >= 1048576 then + return string.format("%.1f MB", bytes / 1048576) + elseif bytes >= 1024 then + return string.format("%.0f KB", bytes / 1024) + else + return bytes .. " B" + end +end + +function formatDownloads(count) + if count >= 1000000 then + return string.format("%.1fM", count / 1000000) + elseif count >= 1000 then + return string.format("%.0fK", count / 1000) + else + return tostring(count) + end +end + +-- ============================================================================ +-- Progress Dialog +-- ============================================================================ + +function showProgress(app_name) + local dialog = document:GetElementById("progress-dialog") + if dialog then + dialog.style.display = "flex" + local title = document:GetElementById("progress-title") + if title then + title.inner_rml = "Installing " .. (app_name or "App") + end + end +end + +function updateProgress(progress) + local bar = document:GetElementById("progress-bar") + if bar then + bar.style.width = (progress.progress * 100) .. "%" + end + + local status = document:GetElementById("progress-status") + if status then + local stage_names = { + downloading = "Downloading...", + verifying = "Verifying...", + extracting = "Extracting...", + registering = "Registering...", + complete = "Complete!", + failed = "Failed" + } + status.inner_rml = stage_names[progress.stage] or progress.stage + end +end + +function hideProgress() + local dialog = document:GetElementById("progress-dialog") + if dialog then + dialog.style.display = "none" + end +end + +-- ============================================================================ +-- Toast/Error Messages +-- ============================================================================ + +function showToast(message) + local toast = document:GetElementById("toast") + if toast then + toast.inner_rml = message + toast.style.display = "block" + -- Auto-hide after 3 seconds (would need timer API) + end + print("[Store] Toast: " .. message) +end + +function showError(message) + state.error_message = message + local error_el = document:GetElementById("error-dialog") + if error_el then + local msg = document:GetElementById("error-message") + if msg then + msg.inner_rml = message + end + error_el.style.display = "flex" + end + print("[Store] Error: " .. message) +end + +function hideError() + state.error_message = nil + local error_el = document:GetElementById("error-dialog") + if error_el then + error_el.style.display = "none" + end +end + +-- ============================================================================ +-- Navigation +-- ============================================================================ + +function showHome() + state.screen = "home" + setActiveTab("apps") + render() +end + +function showGames() + state.screen = "games" + setActiveTab("games") + render() +end + +function showUpdates() + state.screen = "updates" + setActiveTab("updates") + checkForUpdates() + render() +end + +function showSearch() + state.screen = "search" + render() +end + +function showAppDetail(app_id) + state.screen = "detail" + -- Find app in featured or installed + for _, app in ipairs(state.featured) do + if app.id == app_id then + state.selected_app = app + break + end + end + render() +end + +function setActiveTab(tab) + local tabs = {"apps", "games", "updates"} + for _, t in ipairs(tabs) do + local el = document:GetElementById("nav-" .. t) + if el then + if t == tab then + el:SetClass("active", true) + else + el:SetClass("active", false) + end + end + end +end + +-- ============================================================================ +-- Rendering +-- ============================================================================ + +function render() + -- The RML is mostly static with dynamic data binding + -- In a full implementation, we'd update innerHTML of content areas + print("[Store] Rendering screen: " .. state.screen) +end + +-- Initialize on load +init() diff --git a/base-apps/com.mosis.store/store.rml b/base-apps/com.mosis.store/store.rml new file mode 100644 index 0000000..0a79bdd --- /dev/null +++ b/base-apps/com.mosis.store/store.rml @@ -0,0 +1,416 @@ + + + + + + + + + + Store + + + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ Mosis Store +
+
+ +
+
+
+ + +
+ + + + + + + +
+
For You
+
Games
+
Social
+
Productivity
+
Tools
+
+ + +
+ Recommended for You + See all +
+ +
+
+
N
+
Notes
+
Productivity
+
4.7
+
+
+
C
+
Calculator
+
Tools
+
4.5
+
+
+
W
+
Weather
+
Weather
+
4.8
+
+
+
M
+
Maps
+
Navigation
+
4.6
+
+
+ + +
+ Top Free Apps + See all +
+ +
+
S
+
+
Social Hub
+
Social - 12 MB - 4.9
+
+
Install
+
+ +
+
G
+
+
Games Center
+
Games - 45 MB - 4.7
+
+
Install
+
+ +
+
F
+
+
File Manager
+
Tools - 8 MB - 4.6
+
+
Open
+
+ +
+
M
+
+
Music Player
+
Music - 18 MB - 4.5
+
+
Install
+
+
+ + +
+
+ + Apps +
+
+ + Games +
+
+ + Updates +
+
+ +
diff --git a/base-apps/icons/account.tga b/base-apps/icons/account.tga new file mode 100644 index 0000000..71e7f88 --- /dev/null +++ b/base-apps/icons/account.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d06b26a0c072f57aef0ffaa4fc918766e6c6526213ca799d1e6d260be2a275e +size 9234 diff --git a/base-apps/icons/add.tga b/base-apps/icons/add.tga new file mode 100644 index 0000000..23a46f2 --- /dev/null +++ b/base-apps/icons/add.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:161ad9d1947acdad2be332a7f1c760318554c3587aa51074aacde69e5e248e53 +size 9234 diff --git a/base-apps/icons/back.tga b/base-apps/icons/back.tga new file mode 100644 index 0000000..2f95b91 --- /dev/null +++ b/base-apps/icons/back.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0dddba952a0ab45e5e10b9a12ed9010ee9ea42ca4880499a31ed9bb1491ade89 +size 9234 diff --git a/base-apps/icons/backspace.tga b/base-apps/icons/backspace.tga new file mode 100644 index 0000000..f7d4a34 --- /dev/null +++ b/base-apps/icons/backspace.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d185f970b478e764760d2b02733bdad4563eb14e0feecd5fea5594bde8cc530 +size 9234 diff --git a/base-apps/icons/battery.tga b/base-apps/icons/battery.tga new file mode 100644 index 0000000..9dda0c2 --- /dev/null +++ b/base-apps/icons/battery.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4d4ae61df2a0a020276a7bbd1e07d3951e1a336a4d0827a6b7723d61c1621cd +size 9234 diff --git a/base-apps/icons/browser.tga b/base-apps/icons/browser.tga new file mode 100644 index 0000000..8e24c73 --- /dev/null +++ b/base-apps/icons/browser.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c197f9ea2b46366ce93921277914e61c88d464eec5114645d029f8922ad13ed9 +size 36882 diff --git a/base-apps/icons/calculator.tga b/base-apps/icons/calculator.tga new file mode 100644 index 0000000..8f3cd5a --- /dev/null +++ b/base-apps/icons/calculator.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b26dd55dd906721eeb6aa1013f351245f14419d588b81c0f56aad77c8baeccd6 +size 36882 diff --git a/base-apps/icons/calendar.tga b/base-apps/icons/calendar.tga new file mode 100644 index 0000000..2e1b16f --- /dev/null +++ b/base-apps/icons/calendar.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:496cd2a82e219fe451c0e709ec84c7fa80eb1f9c3f5c6c8718e9bffe6c93753b +size 36882 diff --git a/base-apps/icons/call_small.tga b/base-apps/icons/call_small.tga new file mode 100644 index 0000000..0e87324 --- /dev/null +++ b/base-apps/icons/call_small.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97e1cd5ab2e73c8bd21cd9e0e9f01611a9ab17f369a54ef6b3ad45e9243c5b63 +size 9234 diff --git a/base-apps/icons/camera.tga b/base-apps/icons/camera.tga new file mode 100644 index 0000000..94eff75 --- /dev/null +++ b/base-apps/icons/camera.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bb3513d1cbb13bf808f4bb155f7d2f823307c0d12e100d367b23cfea0da1e5b +size 36882 diff --git a/base-apps/icons/clock.tga b/base-apps/icons/clock.tga new file mode 100644 index 0000000..69fa686 --- /dev/null +++ b/base-apps/icons/clock.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f227c1b4c7fe680576cd2fb89060f758812948bdafa0cc3ca13b768d4ebed6ed +size 36882 diff --git a/base-apps/icons/close.tga b/base-apps/icons/close.tga new file mode 100644 index 0000000..ccb2ac2 --- /dev/null +++ b/base-apps/icons/close.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f858a08e9d05a621484d556045ab5ce829f7c487f082d136d788bd97861aec +size 9234 diff --git a/base-apps/icons/contact_phone.tga b/base-apps/icons/contact_phone.tga new file mode 100644 index 0000000..d6fa4d5 --- /dev/null +++ b/base-apps/icons/contact_phone.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a8b0cd6f02dd2a1d2231acfaeba6e46ab556b4f07123c0cfcee965c46c7b888 +size 9234 diff --git a/base-apps/icons/contacts.tga b/base-apps/icons/contacts.tga new file mode 100644 index 0000000..d6dd4f0 --- /dev/null +++ b/base-apps/icons/contacts.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac88f3429e34ebdcc44feeeb9474ca98dc01a0e6dd37d986a8daaf773efaa434 +size 36882 diff --git a/base-apps/icons/dialpad.tga b/base-apps/icons/dialpad.tga new file mode 100644 index 0000000..9694906 --- /dev/null +++ b/base-apps/icons/dialpad.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce0e370125001136be86837642b237ba27394c78b01b9c9ce6d1e4f85320ef1b +size 9234 diff --git a/base-apps/icons/download.tga b/base-apps/icons/download.tga new file mode 100644 index 0000000..646d3f6 --- /dev/null +++ b/base-apps/icons/download.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6ba618cfd901d45f2cc4ca1ca7951d489f75640db68bcbd49b7b44e27b9f5e2 +size 9234 diff --git a/base-apps/icons/files.tga b/base-apps/icons/files.tga new file mode 100644 index 0000000..18d4517 --- /dev/null +++ b/base-apps/icons/files.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e66c03ebea19367a92190b6703e8745b7060fe7ee97f4accbaafde84d2879e0 +size 36882 diff --git a/base-apps/icons/flash.tga b/base-apps/icons/flash.tga new file mode 100644 index 0000000..691b0e0 --- /dev/null +++ b/base-apps/icons/flash.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b4f073aff4a6c7a3b553a19b8a9481344b97c356cf6d198d949d8e548942739 +size 9234 diff --git a/base-apps/icons/forward.tga b/base-apps/icons/forward.tga new file mode 100644 index 0000000..b8e6f89 --- /dev/null +++ b/base-apps/icons/forward.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a1a8b3c8a754a0b43fa5738b534dec0245d73c8c381341cda9381a7b25f62fb +size 9234 diff --git a/base-apps/icons/gallery.tga b/base-apps/icons/gallery.tga new file mode 100644 index 0000000..8637963 --- /dev/null +++ b/base-apps/icons/gallery.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c27fab4bc2ed6f757a22d1be78ddce0fcd8ffa6f4e854e0d5dfddb78a55718d6 +size 36882 diff --git a/base-apps/icons/game.tga b/base-apps/icons/game.tga new file mode 100644 index 0000000..93f5ecd --- /dev/null +++ b/base-apps/icons/game.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea10844fa645a317801194d59d071d74563f1d561949f7f3ad7b4ab2fb935549 +size 9234 diff --git a/base-apps/icons/heart.tga b/base-apps/icons/heart.tga new file mode 100644 index 0000000..da61902 --- /dev/null +++ b/base-apps/icons/heart.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eef94f67fd12188585fb3776220d72e976146f4186b0da38f53e09632bde61c3 +size 9234 diff --git a/base-apps/icons/history.tga b/base-apps/icons/history.tga new file mode 100644 index 0000000..202b8d5 --- /dev/null +++ b/base-apps/icons/history.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67e44ad050f6da50a833dab72a3aff58c3b8372e8e00a7315676b33c4c9e054f +size 9234 diff --git a/base-apps/icons/home.tga b/base-apps/icons/home.tga new file mode 100644 index 0000000..3ff78f0 --- /dev/null +++ b/base-apps/icons/home.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54c6a2b46e41b8e195c606ed96f02313574ff14c45e87fa076359494d1a6cd85 +size 9234 diff --git a/base-apps/icons/library.tga b/base-apps/icons/library.tga new file mode 100644 index 0000000..93bfb86 --- /dev/null +++ b/base-apps/icons/library.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9f901748c8b65a67692fb4c18f6f04974191c25fb22685e0b7fe4b0b9101d9e +size 9234 diff --git a/base-apps/icons/maps.tga b/base-apps/icons/maps.tga new file mode 100644 index 0000000..4450877 --- /dev/null +++ b/base-apps/icons/maps.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:010619d697b90a634a43fd9c011a4f6d7e47fb8bd3f753d320d16373d59664c0 +size 36882 diff --git a/base-apps/icons/menu.tga b/base-apps/icons/menu.tga new file mode 100644 index 0000000..f97f6ca --- /dev/null +++ b/base-apps/icons/menu.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:affe25fb084638acff5e30b512144f42dadcca90080952a62b275f0b508d8f18 +size 9234 diff --git a/base-apps/icons/message.tga b/base-apps/icons/message.tga new file mode 100644 index 0000000..65a816c --- /dev/null +++ b/base-apps/icons/message.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbae2f2835d8788c7669256afb7c959fbfcdab75c4010efd19cb00e6ef88b983 +size 36882 diff --git a/base-apps/icons/more.tga b/base-apps/icons/more.tga new file mode 100644 index 0000000..ad2db04 --- /dev/null +++ b/base-apps/icons/more.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85e174b65a12dadbae3db34c58e6c4b88b946a074ead948008fdbdf91960be8e +size 9234 diff --git a/base-apps/icons/music.tga b/base-apps/icons/music.tga new file mode 100644 index 0000000..3f01189 --- /dev/null +++ b/base-apps/icons/music.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:107e15abf240a074709daa6b1b029c91c79e4f1a37164c12e597e3fa5681d878 +size 36882 diff --git a/base-apps/icons/notes.tga b/base-apps/icons/notes.tga new file mode 100644 index 0000000..ab1dcdb --- /dev/null +++ b/base-apps/icons/notes.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9418a3c71ac8d5725181df6cbbef15068ab6b2da31686949492ee7c0ec5b5e67 +size 36882 diff --git a/base-apps/icons/phone.tga b/base-apps/icons/phone.tga new file mode 100644 index 0000000..95800ba --- /dev/null +++ b/base-apps/icons/phone.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faf243f7dfc3ea8c18c8dc46069857f977b638fd8f0f1f9bd6c1a0a6a7b68c52 +size 36882 diff --git a/base-apps/icons/play.tga b/base-apps/icons/play.tga new file mode 100644 index 0000000..2e0cfd0 --- /dev/null +++ b/base-apps/icons/play.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:007531183043cdb419b3d3c95c2032f4895c1958df008c670ed5b476dbb92737 +size 9234 diff --git a/base-apps/icons/refresh.tga b/base-apps/icons/refresh.tga new file mode 100644 index 0000000..26840c5 --- /dev/null +++ b/base-apps/icons/refresh.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6971909669c9b4885d6d7a47bb0d39578e9dd623d8db3a76a5471fa17471da89 +size 9234 diff --git a/base-apps/icons/search.tga b/base-apps/icons/search.tga new file mode 100644 index 0000000..fb2cd76 --- /dev/null +++ b/base-apps/icons/search.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aa9168b66c351d986788748d9773d12d271b756e49854e109d609d8d333e946 +size 9234 diff --git a/base-apps/icons/send.tga b/base-apps/icons/send.tga new file mode 100644 index 0000000..b98ac0b --- /dev/null +++ b/base-apps/icons/send.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1e30362c46f5b6933959f9810b99264f82a20f9ed26b02a0d28433af5befe01 +size 9234 diff --git a/base-apps/icons/settings.tga b/base-apps/icons/settings.tga new file mode 100644 index 0000000..92d123e --- /dev/null +++ b/base-apps/icons/settings.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc46b65a5192de576b3ae018887eebd2f529fc7c4719c120c73dd7c780f63e2 +size 36882 diff --git a/base-apps/icons/signal.tga b/base-apps/icons/signal.tga new file mode 100644 index 0000000..ed6c007 --- /dev/null +++ b/base-apps/icons/signal.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23b5eee9afda355a4ef3166cb1e9ca582e161e7b4041bd0833ed24a8e1375276 +size 9234 diff --git a/base-apps/icons/store.tga b/base-apps/icons/store.tga new file mode 100644 index 0000000..234b90d --- /dev/null +++ b/base-apps/icons/store.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19bc9956deed8379041a48fa0847b3b57c3627dcc5784c74e2af6a6a00b2ce73 +size 36882 diff --git a/base-apps/icons/switch-camera.tga b/base-apps/icons/switch-camera.tga new file mode 100644 index 0000000..1b6e09a --- /dev/null +++ b/base-apps/icons/switch-camera.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:414ac737296e8da98f0b7fd04758919e89239037641ef0382b80a3df75a30a15 +size 9234 diff --git a/base-apps/icons/timer.tga b/base-apps/icons/timer.tga new file mode 100644 index 0000000..3d3c501 --- /dev/null +++ b/base-apps/icons/timer.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a60b7132b69e4747a8071404c5b668ba641ae56d7a02c721cb8a8ecc7d3b591 +size 9234 diff --git a/base-apps/icons/weather.tga b/base-apps/icons/weather.tga new file mode 100644 index 0000000..3c153b2 --- /dev/null +++ b/base-apps/icons/weather.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4def86b6b9b913a65eb9573fe5f2929ff2aca76d2faf88a3542f90ed4136ab1a +size 36882 diff --git a/base-apps/icons/wifi.tga b/base-apps/icons/wifi.tga new file mode 100644 index 0000000..c7cbdb6 --- /dev/null +++ b/base-apps/icons/wifi.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0953efd8935e443e52e77efe38b20c4a4baaf5087bcf470a220cd12ee90b825 +size 9234 diff --git a/test-apps/package.bat b/base-apps/package.bat similarity index 100% rename from test-apps/package.bat rename to base-apps/package.bat diff --git a/base-apps/scripts/layout.lua b/base-apps/scripts/layout.lua new file mode 100644 index 0000000..bedfde6 --- /dev/null +++ b/base-apps/scripts/layout.lua @@ -0,0 +1,103 @@ +-- Layout System for Virtual Smartphone +-- Provides reusable UI component helpers +-- Requires navigation.lua to be loaded first + +-- Icon paths (relative to assets/) +local ICON_PATH = "../../icons/" + +-- Default icons +local icons = { + back = ICON_PATH .. "back.tga", + home = ICON_PATH .. "home.tga", + menu = ICON_PATH .. "menu.tga", + search = ICON_PATH .. "search.tga", + more = ICON_PATH .. "more.tga", + close = ICON_PATH .. "close.tga", + wifi = ICON_PATH .. "wifi.tga", + signal = ICON_PATH .. "signal.tga", + battery = ICON_PATH .. "battery.tga" +} + +-- Get current time formatted as HH:MM +local function getCurrentTime() + -- In sandbox, we might not have os.date, use a default + if os and os.date then + return os.date("%H:%M") + end + return "12:30" +end + +-- Update status bar time +function updateStatusTime(doc) + local timeEl = doc:GetElementById("status-time") + if timeEl then + timeEl.inner_rml = getCurrentTime() + end +end + +-- Initialize status bar with current time +-- Call from document onload +function initStatusBar(doc) + updateStatusTime(doc) + + -- Set up timer to update time every minute if timers are available + if setTimeout then + local function updateLoop() + updateStatusTime(doc) + setTimeout(updateLoop, 60000) + end + setTimeout(updateLoop, 60000) + end +end + +-- Initialize app bar back button +-- Call from document onload +function initAppBar(doc) + local backBtn = doc:GetElementById("app-bar-back") + if backBtn then + -- Back button is handled via onclick in RML + -- This is for any additional setup + end +end + +-- Initialize system navigation bar +-- Call from document onload +function initSystemNav(doc) + -- Navigation buttons are handled via onclick in RML + -- This is for any additional setup +end + +-- Full layout initialization +-- Call from document onload: initLayout(document) +function initLayout(doc) + initStatusBar(doc) + initAppBar(doc) + initSystemNav(doc) + print("Layout initialized") +end + +-- Handle back button press (for app bar or system nav) +function onBackPressed() + if canGoBack and canGoBack() then + goBack() + else + -- If at root, go home + if goHome then + goHome() + end + end +end + +-- Handle home button press +function onHomePressed() + if goHome then + goHome() + end +end + +-- Handle recent apps button press (placeholder) +function onRecentPressed() + print("Recent apps pressed (not implemented)") +end + +print("Layout system loaded") diff --git a/base-apps/scripts/navigation.lua b/base-apps/scripts/navigation.lua new file mode 100644 index 0000000..f5eb9ef --- /dev/null +++ b/base-apps/scripts/navigation.lua @@ -0,0 +1,145 @@ +-- 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", + store = "apps/store/store.rml", + camera = "apps/camera/camera.rml", + music = "apps/music/music.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/base-apps/ui/components.rcss b/base-apps/ui/components.rcss new file mode 100644 index 0000000..87a9d45 --- /dev/null +++ b/base-apps/ui/components.rcss @@ -0,0 +1,1485 @@ +/* ============================================== + Components: Material UI Components (VR Optimized) + All sizes increased for VR readability and raycast interaction + All interactive elements have hover/active states + ============================================== */ + +/* ============== Status Bar (VR-sized) ============== */ + +.status-bar { + height: 36px; + padding: 0 16px; + background-color: transparent; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 16px; + color: #FFFFFF; +} + +.status-bar-time { + font-weight: 500; +} + +.status-bar-icons { + display: flex; + gap: 8px; +} + +/* ============== App Bar (VR-sized) ============== */ + +.app-bar { + height: 72px; + padding: 0 16px; + background-color: #1E1E1E; + display: flex; + align-items: center; +} + +.app-bar-nav { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + cursor: pointer; + border-radius: 28px; +} + +.app-bar-nav:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.app-bar-nav:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.app-bar-title { + font-size: 26px; + font-weight: 500; + flex: 1; + color: #FFFFFF; +} + +.app-bar-actions { + display: flex; + gap: 8px; +} + +/* ============== Buttons (VR-sized) ============== */ + +.btn { + padding: 16px 32px; + font-size: 18px; + font-weight: 500; + font-family: LatoLatin; + text-align: center; + border-radius: 8px; + cursor: pointer; + min-height: 56px; +} + +.btn:hover { + opacity: 0.9; +} + +.btn:active { + opacity: 0.8; +} + +.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-secondary:active { + background-color: #00B3A1; +} + +.btn-outlined { + background-color: transparent; + color: #BB86FC; +} + +.btn-outlined:hover { + background-color: rgba(187, 134, 252, 0.15); +} + +.btn-outlined:active { + background-color: rgba(187, 134, 252, 0.25); +} + +.btn-text { + background-color: transparent; + color: #BB86FC; + padding: 12px 20px; +} + +.btn-text:hover { + background-color: rgba(187, 134, 252, 0.1); +} + +.btn-text:active { + background-color: rgba(187, 134, 252, 0.2); +} + +/* Icon Button (VR-sized - larger touch target) */ +.btn-icon { + width: 64px; + height: 64px; + padding: 0; + border-radius: 32px; + background-color: transparent; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + cursor: pointer; +} + +.btn-icon:hover { + background-color: rgba(255, 255, 255, 0.12); +} + +.btn-icon:active { + background-color: rgba(255, 255, 255, 0.2); +} + +/* Floating Action Button (VR-sized) */ +.btn-fab { + width: 72px; + height: 72px; + border-radius: 36px; + background-color: #BB86FC; + color: #000000; + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + position: absolute; + bottom: 100px; + right: 20px; + cursor: pointer; +} + +.btn-fab:hover { + background-color: #D4A5FF; + transform: scale(1.05); +} + +.btn-fab:active { + background-color: #9A67EA; + transform: scale(0.95); +} + +/* ============== Input Fields (VR-sized) ============== */ + +.input-container { + margin-bottom: 20px; +} + +.input-label { + font-size: 16px; + color: #B3B3B3; + margin-bottom: 8px; +} + +.input-field { + width: 100%; + padding: 16px 0; + font-size: 20px; + font-family: LatoLatin; + color: #FFFFFF; + background-color: transparent; + border-bottom-width: 2px; + border-bottom-color: #333333; +} + +.input-field:focus { + border-bottom-color: #BB86FC; +} + +.input-field:hover { + border-bottom-color: #666666; +} + +/* Search Input (VR-sized) */ +.search-bar { + display: flex; + align-items: center; + padding: 14px 20px; + background-color: #2D2D2D; + border-radius: 32px; + margin: 12px 16px; + cursor: pointer; +} + +.search-bar:hover { + background-color: #3D3D3D; +} + +.search-icon { + font-size: 24px; + color: #B3B3B3; + margin-right: 16px; +} + +.search-input { + flex: 1; + background-color: transparent; + font-size: 20px; + color: #FFFFFF; +} + +/* ============== Cards (VR-sized) ============== */ + +.card { + background-color: #1E1E1E; + border-radius: 12px; + padding: 20px; + margin: 12px; + cursor: pointer; +} + +.card:hover { + background-color: #252525; +} + +.card:active { + background-color: #2A2A2A; +} + +.card-elevated { + background-color: #2D2D2D; +} + +.card-elevated:hover { + background-color: #353535; +} + +.card-header { + font-size: 24px; + font-weight: 500; + margin-bottom: 10px; + color: #FFFFFF; +} + +.card-subtitle { + font-size: 18px; + color: #B3B3B3; + margin-bottom: 16px; +} + +.card-content { + font-size: 18px; + color: #B3B3B3; +} + +.card-actions { + display: flex; + justify-content: flex-end; + margin-top: 20px; + gap: 12px; +} + +/* ============== Lists (VR-sized) ============== */ + +.list { + display: flex; + flex-direction: column; +} + +.list-item { + display: flex; + align-items: center; + padding: 16px 20px; + min-height: 72px; + cursor: pointer; +} + +.list-item:hover { + background-color: rgba(255, 255, 255, 0.08); +} + +.list-item:active { + background-color: rgba(255, 255, 255, 0.15); +} + +.list-item-avatar { + width: 56px; + height: 56px; + border-radius: 28px; + background-color: #BB86FC; + margin-right: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #000000; +} + +.list-item-icon { + width: 56px; + height: 56px; + margin-right: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #B3B3B3; +} + +.list-item-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.list-item-title { + font-size: 20px; + color: #FFFFFF; +} + +.list-item-subtitle { + font-size: 16px; + color: #B3B3B3; + margin-top: 4px; +} + +.list-item-action { + font-size: 28px; + color: #666666; + cursor: pointer; +} + +.list-item-action:hover { + color: #FFFFFF; +} + +.list-divider { + height: 1px; + background-color: #333333; + margin-left: 96px; +} + +.list-header { + padding: 20px 20px 12px 20px; + font-size: 18px; + font-weight: 500; + color: #BB86FC; +} + +/* ============== Bottom Navigation (VR-sized) ============== */ + +.bottom-nav { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 72px; + background-color: #1E1E1E; + display: flex; +} + +.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; + background-color: rgba(255, 255, 255, 0.05); +} + +.bottom-nav-item:active { + background-color: rgba(255, 255, 255, 0.1); +} + +.bottom-nav-item.active { + color: #BB86FC; +} + +.bottom-nav-item.active:hover { + color: #D4A5FF; +} + +.bottom-nav-icon { + font-size: 28px; + margin-bottom: 6px; +} + +.bottom-nav-label { + font-size: 14px; +} + +/* ============== Dialogs/Modals (VR-sized) ============== */ + +.modal-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; +} + +.modal-dialog { + background-color: #2D2D2D; + border-radius: 16px; + padding: 32px; + width: 360px; + max-width: 90%; +} + +.modal-title { + font-size: 26px; + font-weight: 500; + color: #FFFFFF; + margin-bottom: 20px; +} + +.modal-content { + font-size: 18px; + color: #B3B3B3; + margin-bottom: 32px; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 12px; +} + +/* ============== Toast/Snackbar (VR-sized) ============== */ + +.toast { + position: absolute; + bottom: 100px; + left: 20px; + right: 20px; + background-color: #323232; + color: #FFFFFF; + padding: 20px 24px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 18px; +} + +.toast-action { + color: #BB86FC; + font-weight: 500; + margin-left: 20px; + cursor: pointer; +} + +.toast-action:hover { + color: #D4A5FF; +} + +/* ============== Toggle/Switch (VR-sized) ============== */ + +.toggle { + width: 52px; + height: 32px; + border-radius: 16px; + background-color: #666666; + position: relative; + cursor: pointer; +} + +.toggle:hover { + background-color: #777777; +} + +.toggle.active { + background-color: rgba(187, 134, 252, 0.5); +} + +.toggle.active:hover { + background-color: rgba(187, 134, 252, 0.6); +} + +.toggle-thumb { + width: 24px; + height: 24px; + border-radius: 12px; + background-color: #B3B3B3; + position: absolute; + top: 4px; + left: 4px; +} + +.toggle.active .toggle-thumb { + background-color: #BB86FC; + left: 24px; +} + +/* ============== Chips/Tags (VR-sized) ============== */ + +.chip { + display: inline-flex; + align-items: center; + padding: 10px 18px; + background-color: #2D2D2D; + border-radius: 24px; + font-size: 18px; + color: #FFFFFF; + margin: 6px; + cursor: pointer; +} + +.chip:hover { + background-color: #3D3D3D; +} + +.chip:active { + background-color: #454545; +} + +.chip.active { + background-color: #BB86FC; + color: #000000; +} + +.chip.active:hover { + background-color: #D4A5FF; +} + +.chip-icon { + margin-right: 10px; +} + +.chip-delete { + margin-left: 10px; + font-size: 22px; + color: #B3B3B3; + cursor: pointer; +} + +.chip-delete:hover { + color: #FFFFFF; +} + +/* ============== Dividers ============== */ + +.divider { + height: 1px; + background-color: #333333; +} + +.divider-inset { + margin-left: 96px; +} + +/* ============== App Grid (Home Screen - VR-sized) ============== */ + +.app-grid { + display: flex; + flex-wrap: wrap; + padding: 20px; +} + +.app-icon { + width: 25%; + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 0; + cursor: pointer; + border-radius: 12px; +} + +.app-icon:hover { + background-color: rgba(255, 255, 255, 0.08); +} + +.app-icon:active { + background-color: rgba(255, 255, 255, 0.15); +} + +.app-icon-image { + width: 72px; + height: 72px; + border-radius: 18px; + background-color: #BB86FC; + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + color: #000000; + margin-bottom: 12px; +} + +.app-icon-image:hover { + transform: scale(1.05); +} + +.app-icon-label { + font-size: 16px; + color: #FFFFFF; + text-align: center; +} + +/* ============== Dock (VR-sized) ============== */ + +.dock { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 100px; + background-color: rgba(30, 30, 30, 0.95); + display: flex; + justify-content: space-around; + align-items: center; + padding: 0 40px; +} + +.dock-item { + width: 72px; + height: 72px; + border-radius: 18px; + background-color: #BB86FC; + display: flex; + align-items: center; + justify-content: center; + font-size: 36px; + color: #000000; + cursor: pointer; +} + +.dock-item:hover { + transform: scale(1.1); + background-color: #D4A5FF; +} + +.dock-item:active { + transform: scale(0.95); + background-color: #9A67EA; +} + +/* ============== Dial Pad (VR-sized) ============== */ + +.dial-pad { + display: flex; + flex-wrap: wrap; + padding: 20px; +} + +.dial-key { + width: 33.33%; + height: 96px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 48px; +} + +.dial-key:hover { + background-color: rgba(255, 255, 255, 0.12); +} + +.dial-key:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.dial-key-number { + font-size: 42px; + font-weight: 300; + color: #FFFFFF; +} + +.dial-key-letters { + font-size: 14px; + color: #B3B3B3; + letter-spacing: 3px; + margin-top: 4px; +} + +.dial-display { + font-size: 48px; + font-weight: 300; + color: #FFFFFF; + text-align: center; + padding: 32px 20px; + letter-spacing: 3px; +} + +.dial-actions { + display: flex; + justify-content: center; + padding: 20px; + gap: 64px; +} + +.dial-call-btn { + width: 80px; + height: 80px; + border-radius: 40px; + background-color: #4CAF50; + display: flex; + align-items: center; + justify-content: center; + font-size: 36px; + color: #FFFFFF; + cursor: pointer; +} + +.dial-call-btn:hover { + background-color: #66BB6A; + transform: scale(1.05); +} + +.dial-call-btn:active { + background-color: #43A047; + transform: scale(0.95); +} + +/* ============== Chat Bubbles (VR-sized) ============== */ + +.chat-container { + display: flex; + flex-direction: column; + padding: 20px; + gap: 12px; +} + +.chat-bubble { + max-width: 75%; + padding: 14px 20px; + border-radius: 24px; + font-size: 18px; +} + +.chat-bubble-sent { + align-self: flex-end; + background-color: #BB86FC; + color: #000000; + border-bottom-right-radius: 6px; +} + +.chat-bubble-received { + align-self: flex-start; + background-color: #2D2D2D; + color: #FFFFFF; + border-bottom-left-radius: 6px; +} + +.chat-timestamp { + font-size: 14px; + color: #666666; + text-align: center; + margin: 12px 0; +} + +.chat-input-container { + display: flex; + align-items: center; + padding: 12px 20px; + background-color: #1E1E1E; +} + +.chat-input { + flex: 1; + padding: 14px 20px; + background-color: #2D2D2D; + border-radius: 32px; + font-size: 18px; + color: #FFFFFF; + margin-right: 12px; +} + +.chat-input:hover { + background-color: #3D3D3D; +} + +.chat-input:focus { + background-color: #353535; +} + +.chat-send-btn { + width: 56px; + height: 56px; + border-radius: 28px; + background-color: #BB86FC; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #000000; + cursor: pointer; +} + +.chat-send-btn:hover { + background-color: #D4A5FF; + transform: scale(1.05); +} + +.chat-send-btn:active { + background-color: #9A67EA; + transform: scale(0.95); +} + +/* ============== Section Headers (VR-sized) ============== */ + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24px 20px 16px 20px; +} + +.section-title { + font-size: 24px; + font-weight: 600; + color: #FFFFFF; +} + +.section-action { + font-size: 16px; + font-weight: 600; + color: #B3B3B3; + cursor: pointer; +} + +.section-action:hover { + color: #FFFFFF; +} + +/* ============== Navigation Item (VR-sized) ============== */ + +.nav-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + color: #B3B3B3; + padding: 12px; +} + +.nav-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.nav-item:active { + background-color: rgba(255, 255, 255, 0.1); +} + +.nav-item.active { + color: #FFFFFF; +} + +.nav-item img { + width: 32px; + height: 32px; + margin-bottom: 6px; +} + +.nav-item span { + font-size: 14px; +} + +/* ============== Quick Cards (VR-sized) ============== */ + +.quick-card { + display: flex; + align-items: center; + background-color: #282828; + border-radius: 8px; + padding: 0; + height: 72px; + overflow: hidden; + cursor: pointer; +} + +.quick-card:hover { + background-color: #353535; +} + +.quick-card:active { + background-color: #3D3D3D; +} + +.quick-card-art { + width: 72px; + height: 72px; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #FFFFFF; +} + +.quick-card-title { + font-size: 17px; + font-weight: 600; + color: #FFFFFF; + padding: 0 16px; +} + +/* ============== Recent/Playlist Items (VR-sized) ============== */ + +.recent-item { + min-width: 160px; + cursor: pointer; + padding: 8px; + border-radius: 8px; +} + +.recent-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.recent-art { + width: 160px; + height: 160px; + border-radius: 12px; + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 52px; + color: #FFFFFF; +} + +.recent-title { + font-size: 18px; + color: #FFFFFF; + font-weight: 500; + margin-bottom: 6px; +} + +.recent-subtitle { + font-size: 16px; + color: #B3B3B3; +} + +.playlist-item { + display: flex; + align-items: center; + padding: 16px 20px; + cursor: pointer; +} + +.playlist-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.playlist-item:active { + background-color: rgba(255, 255, 255, 0.1); +} + +.playlist-art { + width: 72px; + height: 72px; + border-radius: 8px; + margin-right: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #FFFFFF; +} + +.playlist-info { + flex: 1; +} + +.playlist-title { + font-size: 20px; + color: #FFFFFF; + font-weight: 500; +} + +.playlist-meta { + font-size: 16px; + color: #B3B3B3; + margin-top: 6px; +} + +/* ============== Mini Player (VR-sized) ============== */ + +.mini-player { + display: flex; + align-items: center; + padding: 12px 20px; + background-color: #282828; +} + +.mini-player-art { + width: 64px; + height: 64px; + border-radius: 8px; + background-color: #667eea; + margin-right: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: #FFFFFF; +} + +.mini-player-info { + flex: 1; +} + +.mini-player-title { + font-size: 18px; + color: #FFFFFF; + font-weight: 500; +} + +.mini-player-artist { + font-size: 16px; + color: #B3B3B3; + margin-top: 4px; +} + +.mini-player-controls { + display: flex; + gap: 12px; +} + +.mini-control-btn { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 28px; +} + +.mini-control-btn:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.mini-control-btn:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.mini-control-btn img { + width: 32px; + height: 32px; +} + +/* ============== Camera Controls (VR-sized) ============== */ + +.camera-btn { + width: 56px; + height: 56px; + border-radius: 28px; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; +} + +.camera-btn:hover { + background-color: rgba(255, 255, 255, 0.25); +} + +.camera-btn:active { + background-color: rgba(255, 255, 255, 0.35); +} + +.camera-btn img { + width: 32px; + height: 32px; +} + +.camera-mode { + font-size: 18px; + color: #B3B3B3; + cursor: pointer; + padding: 12px; + border-radius: 8px; +} + +.camera-mode:hover { + background-color: rgba(255, 255, 255, 0.1); + color: #FFFFFF; +} + +.camera-mode.active { + color: #FFD700; + font-weight: 600; +} + +.capture-btn { + width: 88px; + height: 88px; + border-radius: 44px; + background-color: #FFFFFF; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.capture-btn:hover { + transform: scale(1.05); +} + +.capture-btn:active { + transform: scale(0.95); + background-color: #E0E0E0; +} + +.switch-camera-btn { + width: 64px; + height: 64px; + border-radius: 32px; + background-color: rgba(255, 255, 255, 0.25); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.switch-camera-btn:hover { + background-color: rgba(255, 255, 255, 0.35); +} + +.switch-camera-btn:active { + background-color: rgba(255, 255, 255, 0.45); +} + +.switch-camera-btn img { + width: 32px; + height: 32px; +} + +.gallery-preview { + width: 64px; + height: 64px; + border-radius: 12px; + background-color: #333333; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.gallery-preview:hover { + background-color: #444444; +} + +.gallery-preview img { + width: 32px; + height: 32px; + opacity: 0.8; +} + +.zoom-btn { + width: 44px; + height: 44px; + border-radius: 22px; + background-color: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #FFFFFF; + cursor: pointer; +} + +.zoom-btn:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +/* ============== Store Components (VR-sized) ============== */ + +.app-card { + min-width: 180px; + background-color: #1E1E1E; + border-radius: 16px; + padding: 16px; + cursor: pointer; +} + +.app-card:hover { + background-color: #2A2A2A; +} + +.app-card:active { + background-color: #303030; +} + +.app-card-icon { + width: 80px; + height: 80px; + border-radius: 20px; + margin-bottom: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 36px; + color: #000000; +} + +.app-card-name { + font-size: 18px; + font-weight: 500; + color: #FFFFFF; + margin-bottom: 6px; +} + +.app-card-category { + font-size: 16px; + color: #B3B3B3; + margin-bottom: 10px; +} + +.app-card-rating { + display: flex; + align-items: center; + font-size: 16px; + color: #B3B3B3; +} + +.app-list-item { + display: flex; + align-items: center; + padding: 16px 20px; + cursor: pointer; +} + +.app-list-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.app-list-item:active { + background-color: rgba(255, 255, 255, 0.1); +} + +.app-list-icon { + width: 72px; + height: 72px; + border-radius: 16px; + margin-right: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 32px; + color: #000000; +} + +.app-list-info { + flex: 1; +} + +.app-list-name { + font-size: 20px; + font-weight: 500; + color: #FFFFFF; +} + +.app-list-meta { + font-size: 16px; + color: #B3B3B3; + margin-top: 4px; +} + +.app-list-rating { + display: flex; + align-items: center; + margin-top: 6px; +} + +.app-list-rating span { + font-size: 16px; + color: #B3B3B3; +} + +.install-btn { + background-color: #BB86FC; + color: #000000; + font-size: 16px; + font-weight: 600; + padding: 12px 28px; + border-radius: 24px; + cursor: pointer; +} + +.install-btn:hover { + background-color: #D4A5FF; +} + +.install-btn:active { + background-color: #9A67EA; +} + +.install-btn.installed { + background-color: transparent; + color: #BB86FC; +} + +.install-btn.installed:hover { + background-color: rgba(187, 134, 252, 0.15); +} + +.category-chip { + background-color: #2D2D2D; + color: #FFFFFF; + font-size: 18px; + padding: 12px 24px; + border-radius: 24px; + white-space: nowrap; + cursor: pointer; +} + +.category-chip:hover { + background-color: #3D3D3D; +} + +.category-chip.active { + background-color: #BB86FC; + color: #000000; +} + +.category-chip.active:hover { + background-color: #D4A5FF; +} + +/* ============== Featured Banner (VR-sized) ============== */ + +.featured-banner { + margin: 0 20px 20px 20px; + height: 200px; + background-color: #6200EE; + border-radius: 20px; + padding: 28px; + display: flex; + flex-direction: column; + justify-content: flex-end; + cursor: pointer; +} + +.featured-banner:hover { + opacity: 0.95; +} + +.featured-tag { + font-size: 14px; + color: rgba(255,255,255,0.7); + text-transform: uppercase; + margin-bottom: 10px; +} + +.featured-title { + font-size: 32px; + font-weight: 600; + color: #FFFFFF; + margin-bottom: 6px; +} + +.featured-subtitle { + font-size: 18px; + color: rgba(255,255,255,0.8); +} + +/* ============== Store Search (VR-sized) ============== */ + +.store-search { + margin: 20px; + background-color: #2D2D2D; + border-radius: 32px; + padding: 16px 24px; + display: flex; + align-items: center; + cursor: pointer; +} + +.store-search:hover { + background-color: #3D3D3D; +} + +.store-search img { + width: 28px; + height: 28px; + margin-right: 16px; + opacity: 0.6; +} + +.store-search-text { + font-size: 20px; + color: #B3B3B3; +} + +/* ============== Store Navigation (VR-sized) ============== */ + +.store-bottom-nav { + display: flex; + height: 72px; + background-color: #1E1E1E; +} + +.store-nav-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + color: #B3B3B3; +} + +.store-nav-item:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +.store-nav-item.active { + color: #BB86FC; +} + +.store-nav-item img { + width: 32px; + height: 32px; + margin-bottom: 6px; +} + +.store-nav-item span { + font-size: 14px; +} + +/* ============== Music Navigation (VR-sized) ============== */ + +.music-bottom-nav { + display: flex; + height: 72px; + background-color: #1E1E1E; +} diff --git a/base-apps/ui/html.rcss b/base-apps/ui/html.rcss new file mode 100644 index 0000000..ac19961 --- /dev/null +++ b/base-apps/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/base-apps/ui/layout.rcss b/base-apps/ui/layout.rcss new file mode 100644 index 0000000..edf6ad9 --- /dev/null +++ b/base-apps/ui/layout.rcss @@ -0,0 +1,270 @@ +/* ============================================== + Layout Components: Reusable App Layout Structure + System status bar, app bar, navigation bar + ============================================== */ + +/* ============== System Status Bar ============== */ +/* Top bar showing time, signal, wifi, battery */ + +.system-status-bar { + height: 36px; + padding: 0 16px; + background-color: transparent; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 16px; + color: #FFFFFF; + z-index: 1000; +} + +.system-status-bar.bg-surface { + background-color: #1E1E1E; +} + +.system-status-time { + font-weight: 500; + font-size: 16px; +} + +.system-status-icons { + display: flex; + gap: 8px; + align-items: center; +} + +.system-status-icons img { + width: 24px; + height: 24px; + pointer-events: none; +} + +/* ============== App Bar ============== */ +/* Title bar with back button and optional actions */ + +.app-bar { + height: 72px; + padding: 0 8px; + background-color: #1E1E1E; + display: flex; + align-items: center; + z-index: 900; +} + +.app-bar.transparent { + background-color: transparent; +} + +.app-bar.primary { + background-color: #121212; +} + +.app-bar-back { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 28px; +} + +.app-bar-back:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.app-bar-back:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.app-bar-back img { + width: 32px; + height: 32px; + pointer-events: none; +} + +.app-bar-title { + flex: 1; + font-size: 24px; + font-weight: 500; + color: #FFFFFF; + padding-left: 8px; +} + +.app-bar-actions { + display: flex; + gap: 4px; +} + +.app-bar-action { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 28px; +} + +.app-bar-action:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.app-bar-action:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.app-bar-action img { + width: 28px; + height: 28px; + pointer-events: none; +} + +/* ============== System Navigation Bar ============== */ +/* Bottom bar with back, home, and recent buttons */ + +.system-nav-bar { + height: 56px; + background-color: #0A0A0A; + display: flex; + align-items: center; + justify-content: space-around; + z-index: 1000; +} + +.system-nav-bar.transparent { + background-color: rgba(10, 10, 10, 0.9); +} + +.system-nav-btn { + width: 72px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 24px; +} + +.system-nav-btn:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.system-nav-btn:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.system-nav-btn img { + width: 28px; + height: 28px; + pointer-events: none; + opacity: 0.8; +} + +/* Home button - pill shape */ +.system-nav-home { + width: 96px; + height: 8px; + background-color: #FFFFFF; + border-radius: 4px; + cursor: pointer; + opacity: 0.6; +} + +.system-nav-home:hover { + opacity: 0.8; +} + +.system-nav-home:active { + opacity: 1.0; +} + +/* ============== Screen Layout ============== */ +/* Standard app screen structure */ + +.app-screen { + width: 100%; + height: 100%; + background-color: #121212; + display: flex; + flex-direction: column; +} + +.app-content { + flex: 1; + overflow: auto; + display: flex; + flex-direction: column; +} + +/* Content padding for nav bar */ +.app-content.with-nav { + padding-bottom: 56px; +} + +/* Content padding for dock */ +.app-content.with-dock { + padding-bottom: 100px; +} + +/* ============== Combined Header ============== */ +/* Status bar + App bar combined */ + +.app-header { + display: flex; + flex-direction: column; + background-color: #1E1E1E; +} + +.app-header.transparent { + background-color: transparent; +} + +.app-header .system-status-bar { + background-color: transparent; +} + +/* ============== Notification Badge ============== */ + +.notification-badge { + position: absolute; + top: 8px; + right: 8px; + min-width: 20px; + height: 20px; + background-color: #CF6679; + border-radius: 10px; + font-size: 12px; + font-weight: 600; + color: #FFFFFF; + display: flex; + align-items: center; + justify-content: center; + padding: 0 6px; +} + +/* ============== Recording Indicator ============== */ +/* Shown when camera/mic is active */ + +.recording-indicator { + position: absolute; + top: 8px; + right: 8px; + width: 12px; + height: 12px; + background-color: #F44336; + border-radius: 6px; + animation: recording-pulse 1.5s ease-in-out infinite; +} + +@keyframes recording-pulse { + 0%, 100% { opacity: 1.0; } + 50% { opacity: 0.4; } +} + +/* ============== Divider ============== */ + +.header-divider { + height: 1px; + background-color: #333333; +} diff --git a/base-apps/ui/theme.rcss b/base-apps/ui/theme.rcss new file mode 100644 index 0000000..43aab1b --- /dev/null +++ b/base-apps/ui/theme.rcss @@ -0,0 +1,333 @@ +/* ============================================== + Theme: Material Dark for Virtual Smartphone (VR Optimized) + All sizes increased for VR readability and raycast interaction + ============================================== */ + +/* 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 (VR-sized) ============== */ + +.text-h1 { + font-size: 120px; + font-weight: 300; +} + +.text-h2 { + font-size: 80px; + font-weight: 300; +} + +.text-h3 { + font-size: 64px; + font-weight: 400; +} + +.text-h4 { + font-size: 48px; + font-weight: 400; +} + +.text-h5 { + font-size: 32px; + font-weight: 400; +} + +.text-h6 { + font-size: 28px; + font-weight: 500; +} + +.text-body1 { + font-size: 22px; + font-weight: 400; +} + +.text-body2 { + font-size: 18px; + font-weight: 400; +} + +.text-caption { + font-size: 16px; + font-weight: 400; +} + +.text-overline { + font-size: 14px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 2px; +} + +/* ============== 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; +} + +/* Hover highlight color - used for interactive element feedback */ +.bg-hover { + background-color: rgba(255, 255, 255, 0.12); +} + +/* ============== Spacing Utilities (VR-scaled) ============== */ + +.p-0 { padding: 0; } +.p-1 { padding: 6px; } +.p-2 { padding: 12px; } +.p-3 { padding: 18px; } +.p-4 { padding: 24px; } +.p-5 { padding: 36px; } +.p-6 { padding: 48px; } +.p-8 { padding: 72px; } + +.m-0 { margin: 0; } +.m-1 { margin: 6px; } +.m-2 { margin: 12px; } +.m-3 { margin: 18px; } +.m-4 { margin: 24px; } +.m-5 { margin: 36px; } +.m-6 { margin: 48px; } +.m-8 { margin: 72px; } + +.mt-1 { margin-top: 6px; } +.mt-2 { margin-top: 12px; } +.mt-3 { margin-top: 18px; } +.mt-4 { margin-top: 24px; } + +.mb-1 { margin-bottom: 6px; } +.mb-2 { margin-bottom: 12px; } +.mb-3 { margin-bottom: 18px; } +.mb-4 { margin-bottom: 24px; } + +/* ============== 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: 6px; +} + +.rounded-lg { + border-radius: 12px; +} + +.rounded-xl { + border-radius: 20px; +} + +.rounded-full { + border-radius: 9999px; +} + +/* ============== Screen Structure ============== */ + +.screen { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background-color: #121212; +} + +.screen-content { + flex: 1; + overflow: auto; +} + +/* ============== Animations ============== */ + +@keyframes fade-in { + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@keyframes slide-in-right { + 0% { transform: translateX(100px); opacity: 0; } + 100% { transform: translateX(0px); opacity: 1; } +} + +@keyframes slide-in-left { + 0% { transform: translateX(-100px); opacity: 0; } + 100% { transform: translateX(0px); opacity: 1; } +} + +@keyframes slide-up { + 0% { transform: translateY(50px); opacity: 0; } + 100% { transform: translateY(0px); opacity: 1; } +} + +@keyframes scale-in { + 0% { transform: scale(0.9); opacity: 0; } + 100% { transform: scale(1.0); opacity: 1; } +} + +/* Hover highlight animation */ +@keyframes hover-pulse { + 0% { background-color: rgba(255, 255, 255, 0.0); } + 50% { background-color: rgba(255, 255, 255, 0.15); } + 100% { background-color: rgba(255, 255, 255, 0.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; +} + +/* ============== Interactive Base Class ============== */ +/* All interactive elements should use cursor: pointer and have hover feedback */ + +.interactive { + cursor: pointer; +} + +.interactive:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.interactive:active { + background-color: rgba(255, 255, 255, 0.2); +} diff --git a/designer/main.cpp b/designer/main.cpp index 73624e7..5393fa9 100644 --- a/designer/main.cpp +++ b/designer/main.cpp @@ -1097,7 +1097,7 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height lua_pushstring(L, app.GetIconPath().c_str()); lua_setfield(L, -2, "icon"); - lua_pushboolean(L, false); // Not a system app + lua_pushboolean(L, app.is_system_app); lua_setfield(L, -2, "is_system_app"); lua_pushstring(L, app.app_path.c_str()); diff --git a/designer/src/app_discovery.cpp b/designer/src/app_discovery.cpp index 5e34dae..6bdf0f6 100644 --- a/designer/src/app_discovery.cpp +++ b/designer/src/app_discovery.cpp @@ -68,6 +68,7 @@ bool AppDiscovery::LoadAppManifest(const std::string& app_directory, AppInfo& in info.version = j.value("version", "1.0.0"); info.icon = j.value("icon", "icon.tga"); info.description = j.value("description", ""); + info.is_system_app = j.value("is_system_app", false); // Normalize path separators info.app_path = app_directory; diff --git a/designer/src/app_discovery.h b/designer/src/app_discovery.h index 8125458..a39fd6d 100644 --- a/designer/src/app_discovery.h +++ b/designer/src/app_discovery.h @@ -11,13 +11,20 @@ struct AppInfo { std::string name; // Display name std::string version; // Version string std::string entry; // Entry point (e.g., "main.rml") - std::string icon; // Icon filename (e.g., "icon.tga") + std::string icon; // Icon filename (e.g., "icon.tga") or path (e.g., "/system/icons/phone.tga") std::string description; // App description std::string app_path; // Full path to app directory + bool is_system_app = false; // True for system apps (com.mosis.*) // Computed paths std::string GetEntryPath() const { return app_path + "/" + entry; } - std::string GetIconPath() const { return app_path + "/" + icon; } + std::string GetIconPath() const { + // If icon starts with /, it's an absolute/system path - don't prepend app_path + if (!icon.empty() && icon[0] == '/') { + return icon; + } + return app_path + "/" + icon; + } }; class AppDiscovery { diff --git a/docs/BASE_APPS.md b/docs/BASE_APPS.md new file mode 100644 index 0000000..8354b01 --- /dev/null +++ b/docs/BASE_APPS.md @@ -0,0 +1,386 @@ +# Base Apps Migration Plan + +Convert system apps from embedded assets to standalone `.mosis` packages in `base-apps/`. + +--- + +## Current State + +### Apps in `src/main/assets/apps/` + +| App | Files | Icon | +|-----|-------|------| +| home | home.rml, home.lua, lock.rml | home.tga | +| dialer | dialer.rml, calling.rml | phone.tga | +| contacts | contacts.rml | contacts.tga | +| messages | messages.rml | message.tga | +| settings | settings.rml | settings.tga | +| browser | browser.rml | browser.tga | +| camera | camera.rml | camera.tga | +| store | store.rml | store.tga | +| music | music.rml | music.tga | + +### Shared Dependencies + +All apps currently reference shared resources via relative paths: + +``` +../../ui/html.rcss - Base HTML element styles +../../ui/theme.rcss - Color scheme, typography +../../ui/components.rcss - Buttons, cards, lists +../../scripts/navigation.lua - Screen navigation system +../../icons/*.tga - App and UI icons +``` + +### Current Navigation Model + +Apps are not truly separate - they're screens loaded via a central navigation system: + +```lua +-- navigation.lua +local screens = { + home = "apps/home/home.rml", + dialer = "apps/dialer/dialer.rml", + -- etc. +} +mosis.loadDocument(screens[name]) +``` + +--- + +## Target State + +### Directory Structure + +``` +base-apps/ +├── com.mosis.home/ +│ ├── manifest.json +│ ├── main.rml +│ ├── home.lua +│ ├── lock.rml +│ ├── icon.tga +│ └── styles.rcss +├── com.mosis.dialer/ +│ ├── manifest.json +│ ├── main.rml (dialer.rml) +│ ├── calling.rml +│ ├── icon.tga +│ └── styles.rcss +├── com.mosis.contacts/ +├── com.mosis.messages/ +├── com.mosis.settings/ +├── com.mosis.browser/ +├── com.mosis.camera/ +├── com.mosis.store/ +├── com.mosis.music/ +└── package.bat +``` + +### Package IDs + +| App | Package ID | +|-----|------------| +| Home | com.mosis.home | +| Dialer | com.mosis.dialer | +| Contacts | com.mosis.contacts | +| Messages | com.mosis.messages | +| Settings | com.mosis.settings | +| Browser | com.mosis.browser | +| Camera | com.mosis.camera | +| Store | com.mosis.store | +| Music | com.mosis.music | + +--- + +## Key Decisions + +### 1. Shared Resources Strategy + +**Decision**: System asset path (`/system/`) + +Apps reference shared resources via a system path that the kernel provides: + +```html + + +``` + +The kernel maps `/system/` to `src/main/assets/` (or bundled assets on Android). + +**Rationale**: +- Avoids duplicating ~50KB of styles/icons per app +- Single source of truth for theming +- Apps can still have local overrides + +### 2. Navigation Model + +**Decision**: Hybrid approach + +- **System apps** (home, dialer, contacts, etc.) use shared navigation for seamless transitions +- **Third-party apps** launch via `mosis.apps.launch()` with isolated sandbox + +System apps get `is_system_app = true` flag: +- Can access `/system/` resources +- Share navigation state +- Can load each other's screens directly + +### 3. Home App Special Status + +The home app is the launcher/shell: +- Always running in background +- Provides dock and app grid +- Launches other apps via `mosis.apps.launch(package_id)` +- Cannot be uninstalled + +--- + +## Implementation Tasks + +### Phase 1: Infrastructure + +- [ ] **1.1** Rename `test-apps/` to `base-apps/` +- [ ] **1.2** Move `com.mosis.sandbox-test` to `base-apps/` +- [ ] **1.3** Implement `/system/` path mapping in kernel + - Desktop: Map to `assets/` directory + - Android: Map to bundled assets +- [ ] **1.4** Add `is_system_app` flag to manifest schema +- [ ] **1.5** Update designer `--test-apps` flag to `--apps` for consistency + +### Phase 2: Convert Apps + +For each app: + +- [ ] **2.1** Create package directory in `base-apps/com.mosis.{name}/` +- [ ] **2.2** Create `manifest.json` with proper metadata +- [ ] **2.3** Copy RML files, rename entry to `main.rml` +- [ ] **2.4** Copy app-specific Lua scripts +- [ ] **2.5** Copy icon from `icons/{name}.tga` to `icon.tga` +- [ ] **2.6** Update resource paths to use `/system/` prefix +- [ ] **2.7** Create `styles.rcss` for app-specific styles (extract from inline) + +### Phase 3: Update Home App + +- [ ] **3.1** Update home.lua to use `mosis.apps.launch()` for launching +- [ ] **3.2** Keep shared navigation for system app transitions +- [ ] **3.3** Display all installed apps (base + third-party) in grid +- [ ] **3.4** Update dock icons to launch via package ID + +### Phase 4: Cleanup + +- [ ] **4.1** Remove old `src/main/assets/apps/` directory +- [ ] **4.2** Update build scripts to package base-apps +- [ ] **4.3** Update Android to include base-apps packages +- [ ] **4.4** Update documentation + +--- + +## Manifest Examples + +### System App (Dialer) + +```json +{ + "id": "com.mosis.dialer", + "name": "Phone", + "version": "1.0.0", + "version_code": 1, + "entry": "main.rml", + "icon": "icon.tga", + "description": "Make and receive phone calls", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "permissions": [ + "contacts.read" + ], + "is_system_app": true, + "min_api_version": 1 +} +``` + +### Home App (Launcher) + +```json +{ + "id": "com.mosis.home", + "name": "Home", + "version": "1.0.0", + "version_code": 1, + "entry": "main.rml", + "icon": "icon.tga", + "description": "Mosis Home Launcher", + "developer": { + "name": "Mosis Team", + "email": "dev@mosis.dev" + }, + "permissions": [ + "system.launcher" + ], + "is_system_app": true, + "is_launcher": true, + "min_api_version": 1 +} +``` + +--- + +## Resource Path Mapping + +### Before (Relative) + +```html + + + +``` + +### After (System Path) + +```html + + + +``` + +--- + +## Testing Checklist + +- [ ] Designer loads home from `base-apps/com.mosis.home/` +- [ ] All base apps appear in home screen grid +- [ ] Tapping app icon launches the app +- [ ] Navigation between system apps works (back button, dock) +- [ ] Third-party apps (sandbox-test) launch in isolated sandbox +- [ ] `/system/` paths resolve correctly +- [ ] Android build includes all base-apps packages +- [ ] Hot-reload still works for development + +--- + +## Open Questions + +1. **Lock screen**: Part of home app or separate `com.mosis.lockscreen`? +2. **Calling screen**: Part of dialer or system-level overlay? +3. **Notifications**: System-level or per-app handling? +4. **Settings persistence**: Shared settings service or per-app? + +--- + +## File Changes Summary + +| Action | Path | +|--------|------| +| Rename | `test-apps/` → `base-apps/` | +| Create | `base-apps/com.mosis.{home,dialer,...}/` | +| Create | Each app's `manifest.json` | +| Move | App RML/Lua files to package dirs | +| Update | RML paths from `../../` to `/system/` | +| Delete | `src/main/assets/apps/` (after migration) | +| Update | Designer command line args | +| Update | Android asset bundling | + +--- + +## Reusable Layout Components + +New standardized UI components for consistent app layouts. Located in `src/main/assets/ui/layout.rcss` and `src/main/assets/scripts/layout.lua`. + +### Components + +| Component | CSS Class | Description | +|-----------|-----------|-------------| +| Status Bar | `.system-status-bar` | Top bar with time, wifi, signal, battery | +| App Bar | `.app-bar` | Title bar with back button and actions | +| System Nav | `.system-nav-bar` | Bottom bar with back, home, recent buttons | +| App Screen | `.app-screen` | Standard app screen container | +| App Content | `.app-content` | Scrollable content area | + +### Standard App Structure + +```html + + +
+ 12:30 +
+ + + +
+
+ + +
+
+ +
+ App Title +
+
+ +
+
+
+ + +
+ +
+ + +
+
+ +
+
+
+ +
+
+ +``` + +### Required Includes + +```html + + + + + + + + +``` + +### CSS Modifiers + +| Class | Description | +|-------|-------------| +| `.app-content.with-nav` | Adds bottom padding for system nav bar | +| `.app-content.with-dock` | Adds bottom padding for dock | +| `.app-bar.transparent` | Transparent app bar background | +| `.system-status-bar.bg-surface` | Solid surface color background | + +### Lua Functions (layout.lua) + +| Function | Description | +|----------|-------------| +| `initLayout(doc)` | Initialize all layout components | +| `initStatusBar(doc)` | Setup status bar time updates | +| `onBackPressed()` | Handle back button (goes back or home) | +| `onHomePressed()` | Handle home button | +| `onRecentPressed()` | Handle recent apps button | + +### Updated Apps + +- [x] Settings - Uses full layout with system nav bar +- [x] Contacts - Uses status bar and app bar (has own bottom tabs) +- [ ] Other apps - Still using old structure + +--- + +*Created: 2025-01-19* +*Updated: 2025-01-20* diff --git a/docs/TESTING-FRAMEWORK.md b/docs/TESTING-FRAMEWORK.md index 1681eda..0f76c8d 100644 --- a/docs/TESTING-FRAMEWORK.md +++ b/docs/TESTING-FRAMEWORK.md @@ -1,26 +1,239 @@ # Automated Testing Framework -The designer-test (`designer-test/`) provides automated UI testing. +The Mosis Designer supports two testing approaches: +1. **JSON Action Playback** - Built into the designer for scripted UI testing +2. **C++ Test Framework** - External test runner in `designer-test/` for programmatic 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 (with retry logic and exponential backoff) -3. **LogParser**: Monitors log file for navigation events -4. **TestRunner**: Orchestrates test execution, reports results -5. **UIInspector**: Dumps UI hierarchy with atomic writes (temp file + rename pattern) +## JSON Action Playback -## Key Implementation Details +The designer can record and playback UI interactions using JSON files. This is useful for: +- Automated screenshot capture +- Regression testing +- Demo recordings +- UI verification -- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths (e.g., `D|\Dev\...`). The UIInspector normalizes paths for correct document matching. -- **Atomic File Writes**: Hierarchy files are written to `.tmp` then renamed to prevent partial reads. -- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff (30ms base) and validates JSON completeness. -- **Dynamic Back Button**: `GoHome()` finds back buttons from hierarchy by class (`app-bar-nav` or `browser-nav-btn`) instead of fixed coordinates. +### Command-Line Options -## Writing Tests +```bash +# Run with action playback +mosis-designer.exe --simulator --test-apps base-apps \ + --playback test_navigation.json \ + --screenshot-after result.png \ + --hierarchy hierarchy.json +``` + +| Flag | Description | +|------|-------------| +| `--playback ` | JSON file containing actions to replay | +| `--screenshot-after ` | Save screenshot after playback completes | +| `--hierarchy ` | Dump UI hierarchy to JSON after playback | + +### Recording Actions + +Press `R` in the designer to start/stop recording. Actions are saved to `recorded_actions.json`. + +### Action Types + +#### Tap +Single tap at coordinates. +```json +{ + "type": "tap", + "x": 270, + "y": 480, + "timestamp": 1000 +} +``` + +#### Swipe +Drag from one point to another. +```json +{ + "type": "swipe", + "x1": 270, + "y1": 600, + "x2": 270, + "y2": 200, + "duration": 300, + "timestamp": 2000 +} +``` + +#### Long Press +Press and hold at coordinates. +```json +{ + "type": "long_press", + "x": 270, + "y": 480, + "duration": 800, + "timestamp": 3000 +} +``` + +#### Button +System button press (back, home, recents). +```json +{ + "type": "button", + "button": "back", + "timestamp": 4000 +} +``` + +#### Wait +Pause execution for a duration. +```json +{ + "type": "wait", + "duration": 1500, + "timestamp": 5000 +} +``` + +#### Key +Raw keyboard input (for special keys). +```json +{ + "type": "key", + "key_code": 256, + "pressed": true, + "timestamp": 6000 +} +``` + +Common key codes: +- `256` - Escape (Back) +- `301` - F12 (Debugger) +- `294` - F5 (Reload) + +### Complete Test File Example + +```json +{ + "name": "Navigate to Messages", + "description": "Test navigation from home to Messages app", + "screen_width": 540, + "screen_height": 960, + "initial_screen": "", + "actions": [ + { + "type": "wait", + "duration": 500, + "timestamp": 0 + }, + { + "type": "tap", + "x": 207, + "y": 220, + "timestamp": 500 + }, + { + "type": "wait", + "duration": 1500, + "timestamp": 600 + } + ] +} +``` + +### Finding Tap Coordinates + +Use the `--hierarchy` flag to dump UI element bounds: + +```bash +mosis-designer.exe --simulator --test-apps base-apps \ + --playback wait_only.json \ + --hierarchy hierarchy.json +``` + +The hierarchy JSON contains element bounds: +```json +{ + "bounds": { + "x": 171.5, + "y": 183.2, + "width": 72.0, + "height": 72.0 + }, + "classes": ["app-icon-image"], + "tag": "div" +} +``` + +Calculate tap center: `x = bounds.x + width/2`, `y = bounds.y + height/2` + +### Home Screen Grid Layout + +For the default 540x960 resolution with 4-column grid: + +| Row | Y Range | Center Y | +|-----|---------|----------| +| 1 | 64-167 | ~115 | +| 2 | 183-286 | ~220 | +| 3 | 302-405 | ~350 | + +| Column | X Range | Center X | +|--------|---------|----------| +| 1 | 20-145 | ~82 | +| 2 | 145-270 | ~207 | +| 3 | 270-395 | ~332 | +| 4 | 395-520 | ~457 | + +Dock items are at Y ~910. + +--- + +## Asset Path Requirements + +When testing apps in `base-apps/`, ensure shared assets are accessible. Apps use relative paths like `../../ui/html.rcss`. + +From `base-apps/com.mosis.app/`, the path `../../` resolves to `MosisService/`. + +Required directories at `MosisService/` root: +``` +MosisService/ +├── ui/ # html.rcss, theme.rcss, components.rcss, layout.rcss +├── scripts/ # navigation.lua, layout.lua +├── icons/ # Shared icon assets +└── base-apps/ # Test apps +``` + +Copy from `src/main/assets/`: +```bash +cp -r src/main/assets/ui . +cp -r src/main/assets/scripts . +cp -r src/main/assets/icons . +``` + +--- + +## C++ Test Framework + +The `designer-test/` project provides programmatic testing with element finding. + +### Architecture + +| Component | Purpose | +|-----------|---------| +| WindowController | Sends mouse/keyboard via Windows SendInput API | +| HierarchyReader | Parses UI hierarchy JSON, finds elements by ID/class | +| LogParser | Monitors log file for navigation events | +| TestRunner | Orchestrates execution, reports results | +| UIInspector | Dumps hierarchy with atomic writes | + +### Key Implementation Details + +- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths +- **Atomic File Writes**: Hierarchy written to `.tmp` then renamed +- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff +- **Dynamic Back Button**: Finds back buttons by class instead of fixed coordinates + +### Writing C++ 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); @@ -28,12 +241,11 @@ bool ClickById(TestContext& ctx, const std::string& id) { int x = element->bounds.centerX(); int y = element->bounds.centerY(); - ScaleToPhysical(ctx, x, y); // Convert logical to physical coords + ScaleToPhysical(ctx, x, y); ctx.window.SendClick(x, y); return true; } -// Test example bool TestNavigateToDialer(TestContext& ctx) { GoHome(ctx); ctx.log.Clear(); @@ -46,9 +258,9 @@ bool TestNavigateToDialer(TestContext& ctx) { } ``` -## Test Output +### Test Output -Tests produce JSON results at `test_results.json`: +Results saved to `test_results.json`: ```json { "name": "Mosis Designer UI Tests", @@ -58,3 +270,91 @@ Tests produce JSON results at `test_results.json`: ] } ``` + +--- + +## Example Test Suite + +Test files for common scenarios: + +### test_home_only.json +```json +{ + "name": "Home Screenshot", + "description": "Capture home screen", + "screen_width": 540, + "screen_height": 960, + "actions": [ + {"type": "wait", "duration": 500, "timestamp": 0} + ] +} +``` + +### test_settings.json +```json +{ + "name": "Navigate to Settings", + "description": "Tap Settings app (Row 3, Col 1)", + "screen_width": 540, + "screen_height": 960, + "actions": [ + {"type": "wait", "duration": 500, "timestamp": 0}, + {"type": "tap", "x": 82, "y": 350, "timestamp": 500}, + {"type": "wait", "duration": 1500, "timestamp": 600} + ] +} +``` + +### test_browser.json +```json +{ + "name": "Navigate to Browser", + "description": "Tap Browser in dock (Col 4)", + "screen_width": 540, + "screen_height": 960, + "actions": [ + {"type": "wait", "duration": 500, "timestamp": 0}, + {"type": "tap", "x": 445, "y": 910, "timestamp": 500}, + {"type": "wait", "duration": 1500, "timestamp": 600} + ] +} +``` + +### Running Tests + +```bash +# Single test with screenshot +mosis-designer.exe --simulator --test-apps base-apps \ + --playback test_settings.json \ + --screenshot-after screenshot_settings.png + +# Test with hierarchy dump for debugging +mosis-designer.exe --simulator --test-apps base-apps \ + --playback test_home_only.json \ + --screenshot-after home.png \ + --hierarchy hierarchy.json +``` + +--- + +## Troubleshooting + +### Tap not registering +- Check coordinates against hierarchy bounds +- Ensure tap is within the clickable element (e.g., `app-icon-image`, not `app-icon-label`) +- Use hierarchy dump to verify element positions + +### Stylesheet errors +``` +[ERROR] Failed to load style sheet D|/Dev/.../ui/html.rcss +``` +- Copy shared assets to MosisService root (see Asset Path Requirements) + +### App not loading +- Check console for `launchApp` call +- Verify manifest.json exists in app directory +- Check entry file path in manifest + +### Screenshot shows wrong screen +- Increase wait duration after tap +- Verify navigation completed in console output diff --git a/src/main/assets/apps/browser/browser.rml b/src/main/assets/apps/browser/browser.rml index aa8924c..bfbba21 100644 --- a/src/main/assets/apps/browser/browser.rml +++ b/src/main/assets/apps/browser/browser.rml @@ -3,17 +3,11 @@ + + Browser - - -
- 12:30 -
- * - + - | + + +
+ 12:30 +
+ + +
-
- +
+
- +
- 🔒 - -
-
- + S +
- + +
+
+
-
{{ page_title }}
+
Example Domain
- {{ page_content }} + This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
- More information... + More information...
Related Links
-
-
IANA — IANA-managed Reserved Domains
+
+
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.
+
Certain domains are set aside and unavailable for registration.
-
+
RFC 2606 - Reserved Top Level DNS Names
tools.ietf.org > html > rfc2606
-
This document describes some domain names that are reserved for documentation purposes.
+
This document describes domain names reserved for documentation.
@@ -236,20 +227,20 @@
- - Home + + Home
- {{ tabs.size }} - Tabs + 1 + Tabs
- - New Tab + + New Tab
- - Menu + + Menu
diff --git a/src/main/assets/apps/camera/camera.rml b/src/main/assets/apps/camera/camera.rml index 3b7e2a0..8e8b1dc 100644 --- a/src/main/assets/apps/camera/camera.rml +++ b/src/main/assets/apps/camera/camera.rml @@ -3,15 +3,13 @@ + + Camera - + + +
+ 12:30 +
+ + + +
+
+
@@ -325,11 +311,10 @@
-
C
Camera Preview
-
+
Tap to focus
@@ -346,10 +331,8 @@
- +
Flash: Auto
- -
Timer: Off
diff --git a/src/main/assets/apps/contacts/contacts.rml b/src/main/assets/apps/contacts/contacts.rml index 4d65537..2901d77 100644 --- a/src/main/assets/apps/contacts/contacts.rml +++ b/src/main/assets/apps/contacts/contacts.rml @@ -3,21 +3,14 @@ + + Contacts - + + +
+ 12:30 +
+ + + +
+
+
-
+
+ +
Contacts -
+
+
+ +
+
+ +
+
-
-
-
-
{{ contact.initial }}
+
+
+ +
A
+
+
A
-
{{ contact.name }}
-
{{ contact.phone }}
+
Alice Johnson
+
+1 555-0101
+
+
+ +
+
+
+
A
+
+
Andrew Smith
+
+1 555-0102
+
+
+ +
+
+ + +
B
+
+
B
+
+
Bob Williams
+
+1 555-0201
+
+
+ +
+
+ + +
C
+
+
C
+
+
Carol Davis
+
+1 555-0301
+
+
+ +
+
+
+
C
+
+
Chris Miller
+
+1 555-0302
+
+
+ +
+
+ + +
D
+
+
D
+
+
David Brown
+
+1 555-0401
+
+
+ +
+
+ + +
E
+
+
E
+
+
Emma Wilson
+
+1 555-0501
+
+
+ +
+
+ + +
J
+
+
J
+
+
John Doe
+
+1 555-1234
+
+
+ +
+
+ + +
M
+
+
M
+
+
Mary Taylor
+
+1 555-0601
+
+
+ +
+
+
+
M
+
+
Michael Lee
+
+1 555-0602
+
+
+ +
+
+ + +
S
+
+
S
+
+
Sarah Anderson
+
+1 555-0701
@@ -120,21 +298,23 @@
-
+
+ +
- -
-
- - Keypad + +
+
+ + Keypad
-
- - Recent +
+ + Recent
-
- - Contacts +
+ + Contacts
diff --git a/src/main/assets/apps/dialer/dialer.rml b/src/main/assets/apps/dialer/dialer.rml index 54c4c66..c5bd8c9 100644 --- a/src/main/assets/apps/dialer/dialer.rml +++ b/src/main/assets/apps/dialer/dialer.rml @@ -3,201 +3,154 @@ + + Phone - + + +
+ 12:30 +
+ + + +
+
+
-
+
+ +
Phone
- -
-
Keypad
-
Recent
-
Contacts
-
+ +
+ +
{{ dial_number }}
- -
{{ dial_number }}
+ +
+
+ 1 + +
+
+ 2 + ABC +
+
+ 3 + DEF +
+
+ 4 + GHI +
+
+ 5 + JKL +
+
+ 6 + MNO +
+
+ 7 + PQRS +
+
+ 8 + TUV +
+
+ 9 + WXYZ +
+
+ * + +
+
+ 0 + + +
+
+ # + +
+
- -
-
- 1 - -
-
- 2 - ABC -
-
- 3 - DEF -
-
- 4 - GHI -
-
- 5 - JKL -
-
- 6 - MNO -
-
- 7 - PQRS -
-
- 8 - TUV -
-
- 9 - WXYZ -
-
- * - -
-
- 0 - + -
-
- # - + +
+
+
+ +
+
+ +
- -
-
-
-
-
- - -
-
- - Keypad + +
+
+ + Keypad
-
- - Recent +
+ + Recent
-
- - Contacts +
+ + Contacts
diff --git a/src/main/assets/apps/home/home.lua b/src/main/assets/apps/home/home.lua index bbdadca..2a71f36 100644 --- a/src/main/assets/apps/home/home.lua +++ b/src/main/assets/apps/home/home.lua @@ -34,27 +34,23 @@ function initHome(doc) print("[Home] Initializing home screen...") home_document = doc - -- Get installed third-party apps + -- Get all installed apps if mosis and mosis.apps then installed_apps = mosis.apps.getInstalled() or {} print("[Home] Found " .. #installed_apps .. " installed apps") - -- Filter to only third-party (non-system) apps - local third_party = {} + -- Log each app for _, app in ipairs(installed_apps) do - if not app.is_system_app then - table.insert(third_party, app) - print("[Home] Third-party app: " .. app.name .. " (" .. app.package_id .. ")") - end + local app_type = app.is_system_app and "System" or "Third-party" + print("[Home] " .. app_type .. " app: " .. app.name .. " (" .. app.package_id .. ")") end - installed_apps = third_party else print("[Home] Warning: mosis.apps API not available") installed_apps = {} end - -- Render dynamic apps - renderThirdPartyApps() + -- Render all apps + renderInstalledApps() end -- Generate a color based on package_id @@ -79,17 +75,17 @@ function getAppInitial(name) return name:sub(1, 1):upper() end --- Render third-party apps into the grid -function renderThirdPartyApps() +-- Render all installed apps into the grid +function renderInstalledApps() -- Use stored document reference if not home_document then print("[Home] Could not get document reference") return end - local grid = home_document:GetElementById("third-party-apps") + local grid = home_document:GetElementById("installed-apps") if not grid then - print("[Home] third-party-apps container not found") + print("[Home] installed-apps container not found") return end @@ -97,7 +93,7 @@ function renderThirdPartyApps() grid.inner_rml = "" if #installed_apps == 0 then - print("[Home] No third-party apps to display") + print("[Home] No apps to display") return end @@ -110,25 +106,23 @@ function renderThirdPartyApps() -- Check if app has an icon if app.icon and app.icon ~= "" then - local icon_path - -- Check if icon is already a full path (starts with / or contains :/) - if app.icon:sub(1, 1) == "/" or app.icon:find(":/") then - -- Already a full path - icon_path = app.icon - elseif app.install_path and app.install_path ~= "" then - -- Relative filename - construct full path from install_path - icon_path = app.install_path .. "/" .. app.icon - else - icon_path = app.icon - end - -- Use file:// prefix for absolute paths to prevent RmlUi URL resolution - local src_path = icon_path - if icon_path:sub(1, 1) == "/" then - src_path = "file://" .. icon_path + local icon_path = app.icon + + -- Handle /system/ paths - map to shared assets + if icon_path:sub(1, 8) == "/system/" then + -- Map /system/icons/foo.tga to ../../icons/foo.tga (relative to home) + icon_path = "../../" .. icon_path:sub(9) -- Remove "/system/" prefix + print("[Home] Mapped system icon: " .. app.icon .. " -> " .. icon_path) + elseif icon_path:sub(1, 1) ~= "/" and not icon_path:find(":/") then + -- Relative path - prepend install_path + if app.install_path and app.install_path ~= "" then + icon_path = app.install_path .. "/" .. icon_path + end end + -- Use img tag for actual icon - icon_html = '' - print("[Home] Loading icon: " .. src_path) + icon_html = '' + print("[Home] Loading icon: " .. icon_path) else -- Fallback to initial letter icon_html = '' .. initial .. '' @@ -137,7 +131,7 @@ function renderThirdPartyApps() html = html .. [[
+ onclick="launchApp(']] .. app.package_id .. [[')"> ]] .. icon_html .. [[
]] .. app.name .. [[ @@ -146,7 +140,7 @@ function renderThirdPartyApps() end grid.inner_rml = html - print("[Home] Rendered " .. #installed_apps .. " third-party apps") + print("[Home] Rendered " .. #installed_apps .. " apps") end -- Get app info by package_id @@ -159,8 +153,8 @@ function getAppInfo(package_id) return nil end --- Launch a third-party app -function launchThirdPartyApp(package_id) +-- Launch an app by package_id +function launchApp(package_id) print("[Home] Launching app: " .. package_id) if mosis and mosis.apps then diff --git a/src/main/assets/apps/home/home.rml b/src/main/assets/apps/home/home.rml index 1cc988e..77a0650 100644 --- a/src/main/assets/apps/home/home.rml +++ b/src/main/assets/apps/home/home.rml @@ -44,12 +44,13 @@ } /* Third-party apps use same sizing as system apps */ - #third-party-apps .app-icon { + #installed-apps .app-icon { width: 25%; box-sizing: border-box; + padding: 8px 0; } - #third-party-apps .app-icon-image { + #installed-apps .app-icon-image { width: 72px; height: 72px; border-radius: 18px; @@ -60,11 +61,11 @@ cursor: pointer; } - #third-party-apps .app-icon-image:hover { + #installed-apps .app-icon-image:hover { transform: scale(1.05); } - #third-party-apps .app-icon-label { + #installed-apps .app-icon-label { display: block; text-align: center; font-size: 16px; @@ -86,80 +87,8 @@
- -
-
- Phone -
-
-
- Messages -
-
-
- Contacts -
-
-
- Browser -
- - -
-
- Gallery -
-
-
- Camera -
-
-
- Settings -
-
-
- Music -
- - -
-
- Calendar -
-
-
- Clock -
-
-
- Notes -
-
-
- Maps -
- - -
-
- Store -
-
-
- Files -
-
-
- Calculator -
-
-
- Weather -
- - -
+ +
diff --git a/src/main/assets/apps/messages/messages.rml b/src/main/assets/apps/messages/messages.rml index 32efd10..073ec88 100644 --- a/src/main/assets/apps/messages/messages.rml +++ b/src/main/assets/apps/messages/messages.rml @@ -3,17 +3,11 @@ + + Messages - + + +
+ 12:30 +
+ + + +
+
+
-
+
+ +
Messages -
+
+
+ +
+
-
-
-
{{ conv.name | slice(0, 1) }}
-
-
- {{ conv.name }} - {{ conv.time }} +
+
+ +
+
A
+
+
+ Alice Johnson + 2:34 PM +
+
Hey! Are you coming to the party tonight?
+
+
2
+
+ + +
+
B
+
+
+ Bob Williams + 1:15 PM +
+
Thanks for the help yesterday!
+
+
+ + +
+
C
+
+
+ Carol Davis + Yesterday +
+
The meeting has been rescheduled to Friday
+
+
+ + +
+
D
+
+
+ David Brown + Yesterday +
+
Can you send me the files?
+
+
1
+
+ + +
+
E
+
+
+ Emma Wilson + Mon +
+
See you at the coffee shop!
+
+
+ + +
+
F
+
+
+ Frank Miller + Sun +
+
Great game last night!
+
+
+ + +
+
G
+
+
+ Grace Lee + Sat +
+
Happy birthday! 🎂
-
{{ conv.last_message }}
-
{{ conv.unread }}
-
+
+ +
+ + +
+
+ +
+
+
+ +
+
diff --git a/src/main/assets/apps/music/music.rml b/src/main/assets/apps/music/music.rml index 501cc4b..cf27378 100644 --- a/src/main/assets/apps/music/music.rml +++ b/src/main/assets/apps/music/music.rml @@ -3,23 +3,16 @@ + + Music - + + +
+ 12:30 +
+ + + +
+
+
-
- +
+
Music -
- +
+
+ +
- +
Good afternoon
@@ -376,14 +377,6 @@
Your weekly mixtape
- -
-
R
-
-
Release Radar
-
New music from artists you follow
-
-
@@ -393,7 +386,7 @@
Midnight City
M83
-
+
diff --git a/src/main/assets/apps/settings/settings.rml b/src/main/assets/apps/settings/settings.rml index 9c37cb5..db5c398 100644 --- a/src/main/assets/apps/settings/settings.rml +++ b/src/main/assets/apps/settings/settings.rml @@ -3,17 +3,11 @@ + + Settings - + + +
+ 12:30 +
+ + + +
+
+
-
+
+ +
Settings -
+
+
+ +
+
-
- -
-
U
-