add simulator mode to desktop designer for testing apps
- Add --simulator flag to launch home screen showing discovered apps - Create app discovery system to scan test-apps/ directory - Build simulator home screen with dark phone-like UI - Add Lua API: simulator.launchApp, simulator.goHome, simulator.getApps - ESC key returns to home when inside an app - Apps displayed with icons in grid layout Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
154
designer/assets/simulator/home.rcss
Normal file
154
designer/assets/simulator/home.rcss
Normal file
@@ -0,0 +1,154 @@
|
||||
/* Simulator Home Screen Styles */
|
||||
|
||||
body {
|
||||
font-family: LatoLatin;
|
||||
background-color: #1a1a2e;
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Status Bar */
|
||||
.status-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 24dp;
|
||||
padding: 0 12dp;
|
||||
background-color: #0f0f1a;
|
||||
font-size: 12dp;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.status-time {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8dp;
|
||||
}
|
||||
|
||||
.status-wifi, .status-battery {
|
||||
font-size: 10dp;
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
/* Home Content */
|
||||
.home-content {
|
||||
flex: 1;
|
||||
padding: 16dp;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.home-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24dp;
|
||||
}
|
||||
|
||||
.home-header h1 {
|
||||
font-size: 24dp;
|
||||
font-weight: bold;
|
||||
margin: 0 0 4dp 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.home-header .subtitle {
|
||||
font-size: 14dp;
|
||||
color: #888888;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* App Grid */
|
||||
.app-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 16dp;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.no-apps {
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
padding: 32dp;
|
||||
}
|
||||
|
||||
.no-apps p {
|
||||
margin: 8dp 0;
|
||||
}
|
||||
|
||||
.no-apps .hint {
|
||||
font-size: 12dp;
|
||||
color: #555555;
|
||||
}
|
||||
|
||||
/* App Icon */
|
||||
.app-icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 80dp;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.app-icon:hover .app-icon-image {
|
||||
transform: scale(1.1);
|
||||
background-color: #3d3d5c;
|
||||
}
|
||||
|
||||
.app-icon-image {
|
||||
width: 56dp;
|
||||
height: 56dp;
|
||||
border-radius: 12dp;
|
||||
background-color: #2d2d44;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
transition: transform 0.1s, background-color 0.1s;
|
||||
}
|
||||
|
||||
.app-icon-image img {
|
||||
width: 48dp;
|
||||
height: 48dp;
|
||||
}
|
||||
|
||||
.app-icon-placeholder {
|
||||
font-size: 24dp;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.app-icon-label {
|
||||
font-size: 11dp;
|
||||
color: #cccccc;
|
||||
margin-top: 6dp;
|
||||
text-align: center;
|
||||
max-width: 80dp;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Navigation Bar */
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 24dp;
|
||||
height: 40dp;
|
||||
background-color: #0f0f1a;
|
||||
align-items: center;
|
||||
border-top-width: 1dp;
|
||||
border-top-color: #2d2d44;
|
||||
}
|
||||
|
||||
.nav-hint {
|
||||
font-size: 11dp;
|
||||
color: #666666;
|
||||
}
|
||||
36
designer/assets/simulator/home.rml
Normal file
36
designer/assets/simulator/home.rml
Normal file
@@ -0,0 +1,36 @@
|
||||
<rml>
|
||||
<head>
|
||||
<title>Mosis Simulator</title>
|
||||
<link type="text/rcss" href="home.rcss"/>
|
||||
<script src="simulator.lua"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="status-bar">
|
||||
<span class="status-time" id="status-time">12:00</span>
|
||||
<span class="status-icons">
|
||||
<span class="status-wifi">●</span>
|
||||
<span class="status-battery">■</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="home-content">
|
||||
<div class="home-header">
|
||||
<h1>Test Apps</h1>
|
||||
<p class="subtitle">Tap an app to launch</p>
|
||||
</div>
|
||||
|
||||
<div class="app-grid" id="app-grid">
|
||||
<!-- Apps will be populated dynamically by Lua -->
|
||||
<div class="no-apps" id="no-apps">
|
||||
<p>No apps found</p>
|
||||
<p class="hint">Place apps in test-apps/ folder</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav-bar">
|
||||
<div class="nav-hint">ESC = Back</div>
|
||||
<div class="nav-hint">F5 = Reload</div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
158
designer/assets/simulator/simulator.lua
Normal file
158
designer/assets/simulator/simulator.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
-- Simulator Home Screen Logic
|
||||
|
||||
local apps = {}
|
||||
|
||||
-- Helper to get document (may not be available immediately)
|
||||
local function getDoc()
|
||||
if document then
|
||||
return document
|
||||
end
|
||||
if rmlui and rmlui.contexts and rmlui.contexts.main then
|
||||
local ctx = rmlui.contexts.main
|
||||
if ctx.documents then
|
||||
for i, doc in ipairs(ctx.documents) do
|
||||
if doc then
|
||||
return doc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Populate the app grid with discovered apps
|
||||
function populateAppGrid()
|
||||
local doc = getDoc()
|
||||
if not doc then
|
||||
print("[Simulator] Document not available for populateAppGrid")
|
||||
return
|
||||
end
|
||||
|
||||
local grid = doc:GetElementById("app-grid")
|
||||
if not grid then
|
||||
print("[Simulator] app-grid element not found")
|
||||
return
|
||||
end
|
||||
|
||||
-- Clear existing content
|
||||
grid.inner_rml = ""
|
||||
|
||||
if #apps == 0 then
|
||||
grid.inner_rml = [[
|
||||
<div class="no-apps">
|
||||
<p>No apps found</p>
|
||||
<p class="hint">Place apps in test-apps/ folder</p>
|
||||
</div>
|
||||
]]
|
||||
return
|
||||
end
|
||||
|
||||
-- Build app icons
|
||||
local html = ""
|
||||
for i, app in ipairs(apps) do
|
||||
local icon_html
|
||||
if app.icon and app.icon ~= "" then
|
||||
icon_html = string.format('<img src="%s"/>', app.icon)
|
||||
else
|
||||
icon_html = '<span class="app-icon-placeholder">■</span>'
|
||||
end
|
||||
|
||||
html = html .. string.format([[
|
||||
<div class="app-icon" onclick="launchApp('%s')">
|
||||
<div class="app-icon-image">%s</div>
|
||||
<span class="app-icon-label">%s</span>
|
||||
</div>
|
||||
]], app.id, icon_html, app.name)
|
||||
end
|
||||
|
||||
grid.inner_rml = html
|
||||
print("[Simulator] Populated " .. #apps .. " apps")
|
||||
end
|
||||
|
||||
-- Get apps from C++ and populate the grid
|
||||
function refreshApps()
|
||||
if simulator and simulator.getApps then
|
||||
apps = simulator.getApps()
|
||||
print("[Simulator] Got " .. #apps .. " apps from C++")
|
||||
populateAppGrid()
|
||||
else
|
||||
print("[Simulator] simulator.getApps not available")
|
||||
end
|
||||
end
|
||||
|
||||
-- Called from C++ after apps are discovered (backup method)
|
||||
function setApps(app_list)
|
||||
apps = app_list
|
||||
populateAppGrid()
|
||||
end
|
||||
|
||||
-- Launch an app by ID
|
||||
function launchApp(app_id)
|
||||
print("[Simulator] Launching app: " .. app_id)
|
||||
|
||||
-- Find the app
|
||||
for _, app in ipairs(apps) do
|
||||
if app.id == app_id then
|
||||
-- Call C++ function to launch the app
|
||||
if simulator and simulator.launchApp then
|
||||
simulator.launchApp(app.entry, app.path, app.id)
|
||||
else
|
||||
print("[Simulator] Error: simulator.launchApp not available")
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
print("[Simulator] App not found: " .. app_id)
|
||||
end
|
||||
|
||||
-- Update the time display
|
||||
function updateTime()
|
||||
local doc = getDoc()
|
||||
if not doc then return end
|
||||
|
||||
local timeEl = doc:GetElementById("status-time")
|
||||
if timeEl then
|
||||
-- Use os.date if available, otherwise show static time
|
||||
local time_str = "12:00"
|
||||
if os and os.date then
|
||||
time_str = os.date("%H:%M")
|
||||
end
|
||||
timeEl.inner_rml = time_str
|
||||
end
|
||||
end
|
||||
|
||||
-- Initialize
|
||||
print("[Simulator] Home screen loaded")
|
||||
|
||||
-- Try immediate initialization first
|
||||
local doc = getDoc()
|
||||
if doc then
|
||||
print("[Simulator] Document available immediately")
|
||||
refreshApps()
|
||||
updateTime()
|
||||
elseif setInterval then
|
||||
print("[Simulator] Document not ready, setting up timer")
|
||||
-- Use a one-time timer to refresh apps after document is ready
|
||||
local initTimerId = nil
|
||||
local attempts = 0
|
||||
initTimerId = setInterval(function()
|
||||
attempts = attempts + 1
|
||||
local d = getDoc()
|
||||
if d then
|
||||
print("[Simulator] Document ready after " .. attempts .. " attempts")
|
||||
clearInterval(initTimerId)
|
||||
refreshApps()
|
||||
updateTime()
|
||||
elseif attempts > 50 then
|
||||
-- Give up after 5 seconds
|
||||
print("[Simulator] Gave up waiting for document")
|
||||
clearInterval(initTimerId)
|
||||
end
|
||||
end, 100)
|
||||
|
||||
-- Update time every minute
|
||||
setInterval(updateTime, 60000)
|
||||
else
|
||||
print("[Simulator] No setInterval and no document - cannot init")
|
||||
end
|
||||
Reference in New Issue
Block a user