add shared assets at root for base-apps testing

icons/, scripts/, ui/ folders are needed at root for base-apps
manifest paths like ../../icons/ to resolve correctly during testing
This commit is contained in:
2026-01-20 12:46:29 +01:00
parent 469535f79a
commit f3eab207a5
50 changed files with 2560 additions and 4 deletions

6
.gitignore vendored
View File

@@ -17,10 +17,8 @@ test_*.log
*_hierarchy.json
recorded_actions.json
# Duplicate asset folders (use src/main/assets/ instead)
/icons/
/scripts/
/ui/
# Note: /icons/, /scripts/, /ui/ at root are needed for base-apps testing
# They should be copies of src/main/assets/{icons,scripts,ui}
# Sandbox data created during testing
/src/main/assets/sandbox_data/

BIN
icons/account.tga LFS Normal file

Binary file not shown.

BIN
icons/add.tga LFS Normal file

Binary file not shown.

BIN
icons/back.tga LFS Normal file

Binary file not shown.

BIN
icons/backspace.tga LFS Normal file

Binary file not shown.

BIN
icons/battery.tga LFS Normal file

Binary file not shown.

BIN
icons/browser.tga LFS Normal file

Binary file not shown.

BIN
icons/calculator.tga LFS Normal file

Binary file not shown.

BIN
icons/calendar.tga LFS Normal file

Binary file not shown.

BIN
icons/call_small.tga LFS Normal file

Binary file not shown.

BIN
icons/camera.tga LFS Normal file

Binary file not shown.

BIN
icons/clock.tga LFS Normal file

Binary file not shown.

BIN
icons/close.tga LFS Normal file

Binary file not shown.

BIN
icons/contact_phone.tga LFS Normal file

Binary file not shown.

BIN
icons/contacts.tga LFS Normal file

Binary file not shown.

BIN
icons/dialpad.tga LFS Normal file

Binary file not shown.

BIN
icons/download.tga LFS Normal file

Binary file not shown.

BIN
icons/files.tga LFS Normal file

Binary file not shown.

BIN
icons/flash.tga LFS Normal file

Binary file not shown.

BIN
icons/forward.tga LFS Normal file

Binary file not shown.

BIN
icons/gallery.tga LFS Normal file

Binary file not shown.

BIN
icons/game.tga LFS Normal file

Binary file not shown.

BIN
icons/heart.tga LFS Normal file

Binary file not shown.

BIN
icons/history.tga LFS Normal file

Binary file not shown.

BIN
icons/home.tga LFS Normal file

Binary file not shown.

BIN
icons/library.tga LFS Normal file

Binary file not shown.

BIN
icons/maps.tga LFS Normal file

Binary file not shown.

BIN
icons/menu.tga LFS Normal file

Binary file not shown.

BIN
icons/message.tga LFS Normal file

Binary file not shown.

BIN
icons/more.tga LFS Normal file

Binary file not shown.

BIN
icons/music.tga LFS Normal file

Binary file not shown.

BIN
icons/notes.tga LFS Normal file

Binary file not shown.

BIN
icons/phone.tga LFS Normal file

Binary file not shown.

BIN
icons/play.tga LFS Normal file

Binary file not shown.

BIN
icons/refresh.tga LFS Normal file

Binary file not shown.

BIN
icons/search.tga LFS Normal file

Binary file not shown.

BIN
icons/send.tga LFS Normal file

Binary file not shown.

BIN
icons/settings.tga LFS Normal file

Binary file not shown.

BIN
icons/signal.tga LFS Normal file

Binary file not shown.

BIN
icons/store.tga LFS Normal file

Binary file not shown.

BIN
icons/switch-camera.tga LFS Normal file

Binary file not shown.

BIN
icons/timer.tga LFS Normal file

Binary file not shown.

BIN
icons/weather.tga LFS Normal file

Binary file not shown.

BIN
icons/wifi.tga LFS Normal file

Binary file not shown.

103
scripts/layout.lua Normal file
View File

@@ -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")

145
scripts/navigation.lua Normal file
View File

@@ -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 .. ")")

1485
ui/components.rcss Normal file

File diff suppressed because it is too large Load Diff

93
ui/html.rcss Normal file
View File

@@ -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;
}

270
ui/layout.rcss Normal file
View File

@@ -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;
}

333
ui/theme.rcss Normal file
View File

@@ -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);
}