add documentation site with markdown rendering (M12)

This commit is contained in:
2026-01-18 22:07:35 +01:00
parent 94a573f218
commit 8cb3cf769d
14 changed files with 4847 additions and 0 deletions

View File

@@ -0,0 +1,535 @@
# Best Practices
Guidelines for building high-quality Mosis apps that users love.
## Performance
### Minimize DOM Queries
Cache element references instead of querying repeatedly:
```lua
-- Bad: Queries on every frame
function updateScore()
document:GetElementById("score").inner_rml = tostring(score)
end
-- Good: Cache the reference
local scoreElement
function onLoad()
scoreElement = document:GetElementById("score")
end
function updateScore()
scoreElement.inner_rml = tostring(score)
end
```
### Batch DOM Updates
Group multiple changes together:
```lua
-- Bad: Multiple separate updates
elem1.style.color = "red"
elem2.style.color = "red"
elem3.style.color = "red"
-- Good: Use a class
parent:SetClass("error-state", true)
```
### Use Efficient Data Structures
```lua
-- For frequent lookups, use tables as maps
local itemLookup = {}
for i, item in ipairs(items) do
itemLookup[item.id] = item
end
-- O(1) lookup instead of O(n) search
local item = itemLookup["item-123"]
```
### Clean Up Timers
Always clear intervals when navigating away:
```lua
local updateInterval
function onScreenLoad()
updateInterval = setInterval(function()
updateData()
end, 1000)
end
function onScreenUnload()
if updateInterval then
clearInterval(updateInterval)
updateInterval = nil
end
end
```
### Lazy Load Content
Don't load everything at startup:
```lua
-- Load data when user scrolls to section
function onSectionVisible(sectionId)
if not loadedSections[sectionId] then
loadSectionData(sectionId)
loadedSections[sectionId] = true
end
end
```
## User Experience
### Provide Feedback
Show users that actions are happening:
```lua
function onSubmit()
-- Show loading state immediately
submitButton:SetClass("loading", true)
submitButton:SetAttribute("disabled", "disabled")
http.post(url, data, function(response)
submitButton:SetClass("loading", false)
submitButton:RemoveAttribute("disabled")
if response.ok then
showSuccess("Saved!")
else
showError("Failed to save")
end
end)
end
```
### Handle Errors Gracefully
Never show raw error messages to users:
```lua
http.get(url, function(response)
if response.ok then
displayData(json.decode(response.body))
else
-- User-friendly message
showMessage("Unable to load data. Please check your connection.")
-- Log details for debugging
console.error("API error:", response.status, response.body)
end
end)
```
### Make Touch Targets Large Enough
Minimum 48dp for touchable elements:
```css
.button {
min-width: 48dp;
min-height: 48dp;
padding: 12dp 24dp;
}
.list-item {
min-height: 56dp;
padding: 16dp;
}
```
### Support Undo for Destructive Actions
```lua
local deletedItem = nil
local undoTimeout = nil
function deleteItem(itemId)
deletedItem = items[itemId]
items[itemId] = nil
updateList()
showUndoSnackbar("Item deleted", function()
-- Undo callback
items[itemId] = deletedItem
deletedItem = nil
updateList()
end)
-- Clear undo after 5 seconds
undoTimeout = setTimeout(function()
deletedItem = nil
permanentlyDelete(itemId)
end, 5000)
end
```
### Remember User State
Restore position and selections when returning:
```lua
function onScreenUnload()
storage.set("list_scroll_position", scrollContainer.scroll_top)
storage.set("selected_tab", currentTab)
end
function onScreenLoad()
local scrollPos = storage.get("list_scroll_position")
if scrollPos then
scrollContainer.scroll_top = scrollPos
end
local tab = storage.get("selected_tab")
if tab then
selectTab(tab)
end
end
```
## Code Quality
### Use Local Variables
Local variables are faster and prevent global pollution:
```lua
-- Bad: Global
count = 0
-- Good: Local
local count = 0
-- Good: Module-level local
local Utils = {}
local cache = {} -- Private to module
```
### Handle Edge Cases
```lua
function divide(a, b)
if b == 0 then
console.warn("Division by zero")
return 0
end
return a / b
end
function getUsername(user)
if not user then
return "Unknown"
end
return user.name or user.email or "Unknown"
end
```
### Use Meaningful Names
```lua
-- Bad
local t = {}
local n = 0
-- Good
local userScores = {}
local attemptCount = 0
-- Bad
function p(x)
return x * 100
end
-- Good
function toPercentage(decimal)
return decimal * 100
end
```
### Keep Functions Small
Each function should do one thing:
```lua
-- Bad: Does too much
function processUser(userId)
local user = fetchUser(userId)
validateUser(user)
updateUserStats(user)
sendWelcomeEmail(user)
logActivity(user)
return formatUserResponse(user)
end
-- Good: Composed of small functions
function processNewUser(userId)
local user = fetchUser(userId)
if not isValidUser(user) then
return nil, "Invalid user"
end
initializeUserStats(user)
queueWelcomeEmail(user)
return user
end
```
### Comment Why, Not What
```lua
-- Bad: Describes what (obvious from code)
-- Increment counter by 1
counter = counter + 1
-- Good: Explains why
-- Reset retry count after successful connection
-- to prevent unnecessary backoff on next attempt
retryCount = 0
```
## Security
### Validate All Input
```lua
function searchItems(query)
-- Sanitize input
if type(query) ~= "string" then
return {}
end
query = query:sub(1, 100) -- Limit length
query = query:gsub("[^%w%s]", "") -- Remove special chars
return performSearch(query)
end
```
### Don't Trust External Data
```lua
http.get(url, function(response)
local success, data = pcall(function()
return json.decode(response.body)
end)
if not success then
console.error("Invalid JSON from API")
return
end
-- Validate structure
if type(data.items) ~= "table" then
console.error("Missing items array")
return
end
processItems(data.items)
end)
```
### Never Store Secrets in Code
```lua
-- Bad: Hardcoded API key
local API_KEY = "sk-12345abcde"
-- Good: Use environment/config
local apiKey = config.get("api_key")
```
### Sanitize Display Content
When displaying user-generated content, prevent injection:
```lua
function displayComment(text)
-- Escape HTML entities
text = text:gsub("&", "&")
text = text:gsub("<", "&lt;")
text = text:gsub(">", "&gt;")
commentElement.inner_rml = text
end
```
## Accessibility
### Use Semantic Elements
```xml
<!-- Bad: Divs for everything -->
<div class="button" onclick="submit()">Submit</div>
<!-- Good: Proper elements -->
<button onclick="submit()">Submit</button>
<!-- Good: Headings create hierarchy -->
<h1>Settings</h1>
<h2>Account</h2>
<h2>Notifications</h2>
```
### Provide Text Alternatives
```xml
<!-- Images should describe their purpose -->
<img src="icons/search.tga" alt="Search"/>
<!-- Icons with meaning need labels -->
<button aria-label="Close">
<img src="icons/close.tga"/>
</button>
```
### Ensure Color Contrast
Text should have at least 4.5:1 contrast ratio:
```css
/* Good contrast */
.light-text {
color: #ffffff;
background-color: #1a1a2e; /* Contrast: 12.6:1 */
}
/* Bad contrast */
.low-contrast {
color: #888888;
background-color: #666666; /* Contrast: 1.3:1 */
}
```
### Don't Rely on Color Alone
```css
/* Bad: Only color indicates error */
.error {
color: red;
}
/* Good: Icon + color + text */
.error {
color: #ff4444;
}
.error::before {
content: "⚠ ";
}
```
## Testing
### Test Error States
Don't just test the happy path:
```lua
-- Test these scenarios:
-- 1. Empty data
-- 2. Network failure
-- 3. Invalid input
-- 4. Timeouts
-- 5. Missing permissions
```
### Test Navigation Flows
Ensure users can:
- Navigate forward and back
- Return to the home screen
- Handle the back button at any screen
### Test Edge Cases
- Very long text/names
- Empty lists
- Maximum values
- Rapid repeated actions
- Interrupted operations
### Use Debug Logging
```lua
local DEBUG = true
function debugLog(...)
if DEBUG then
print("[DEBUG]", ...)
end
end
-- In production build, set DEBUG = false
```
## Deployment
### Use Meaningful Version Numbers
Follow semantic versioning:
- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes
```json
{
"version": "2.1.3",
"version_code": 15
}
```
### Write Good Release Notes
```
Version 2.1.0
New Features:
- Added dark mode support
- New export to PDF feature
Improvements:
- Faster loading times
- Better error messages
Bug Fixes:
- Fixed crash when opening empty files
- Fixed date format on some devices
```
### Test Before Submitting
1. Run on the Designer
2. Test all features manually
3. Check on a real device if possible
4. Verify all assets load correctly
5. Test offline behavior
## Summary Checklist
Before submitting your app:
- [ ] All features work as expected
- [ ] Error states are handled gracefully
- [ ] Loading states shown during async operations
- [ ] Touch targets are at least 48dp
- [ ] Text is readable (contrast ratio ≥ 4.5:1)
- [ ] No console errors in normal usage
- [ ] Timers and intervals cleaned up properly
- [ ] User data persists correctly
- [ ] App works after fresh install
- [ ] Version number and code are updated
- [ ] Release notes are meaningful
## See Also
- [UI Design Guide](ui-design.md) - Design patterns
- [Lua Scripting Guide](lua-scripting.md) - Code patterns
- [Troubleshooting](../troubleshooting.md) - Common issues

View File

@@ -0,0 +1,506 @@
# Lua Scripting Guide
Mosis apps use Lua for scripting and interactivity. Each app runs in an isolated sandbox with access to Mosis-specific APIs.
## Getting Started
Embed Lua directly in your RML files:
```xml
<body>
<button onclick="sayHello()">Click Me</button>
<script>
function sayHello()
print("Hello from Lua!")
end
</script>
</body>
```
Or use external files:
```xml
<head>
<script src="scripts/app.lua"/>
</head>
```
## Lua Basics
If you're new to Lua, here's a quick primer:
### Variables
```lua
-- Local variables (preferred)
local name = "Mosis"
local count = 42
local enabled = true
local items = {"apple", "banana", "cherry"}
-- Global variables (avoid when possible)
globalVar = "accessible everywhere"
```
### Functions
```lua
-- Basic function
function greet(name)
return "Hello, " .. name .. "!"
end
-- Function with multiple returns
function getPosition()
return 100, 200
end
local x, y = getPosition()
-- Anonymous functions
local double = function(n) return n * 2 end
```
### Control Flow
```lua
-- If statements
if score > 100 then
print("High score!")
elseif score > 50 then
print("Good job!")
else
print("Keep trying!")
end
-- Loops
for i = 1, 10 do
print(i)
end
for index, value in ipairs(items) do
print(index, value)
end
while condition do
-- loop body
end
```
### Tables
```lua
-- Array-like table
local colors = {"red", "green", "blue"}
print(colors[1]) -- "red" (Lua is 1-indexed)
-- Dictionary-like table
local user = {
name = "Alice",
age = 25,
premium = true
}
print(user.name)
print(user["age"])
-- Mixed table
local app = {
name = "MyApp",
version = "1.0",
features = {"dark mode", "notifications"}
}
```
## DOM Manipulation
Access and modify UI elements using the `document` object:
### Getting Elements
```lua
-- By ID
local button = document:GetElementById("my-button")
-- By tag name
local paragraphs = document:GetElementsByTagName("p")
-- By class name
local cards = document:GetElementsByClassName("card")
```
### Modifying Content
```lua
local element = document:GetElementById("message")
-- Set inner content (HTML-like)
element.inner_rml = "<strong>Hello!</strong>"
-- Get inner content
local content = element.inner_rml
-- Set text only (safer, no HTML parsing)
element:SetInnerRML("Plain text here")
```
### Modifying Attributes
```lua
local input = document:GetElementById("username")
-- Get attribute
local value = input:GetAttribute("value")
-- Set attribute
input:SetAttribute("placeholder", "Enter username")
-- Remove attribute
input:RemoveAttribute("disabled")
```
### Modifying Styles
```lua
local box = document:GetElementById("box")
-- Set individual properties
box.style.width = "200dp"
box.style.backgroundColor = "#00d4ff"
box.style.display = "none" -- hide element
-- Read properties
local width = box.style.width
```
### Classes
```lua
local element = document:GetElementById("panel")
-- Add class
element:SetClass("active", true)
-- Remove class
element:SetClass("active", false)
-- Check class
if element:IsClassSet("active") then
print("Panel is active")
end
```
## Event Handling
### Inline Events
```xml
<button onclick="handleClick()">Click</button>
<input onchange="handleChange(event)"/>
<div onmouseover="handleHover()"/>
```
### Event Listeners
```lua
local button = document:GetElementById("my-button")
-- Add listener
button:AddEventListener("click", function(event)
print("Button clicked!")
end)
-- Remove listener (need reference)
local handler = function(event)
print("Clicked")
end
button:AddEventListener("click", handler)
button:RemoveEventListener("click", handler)
```
### Event Object
```lua
function handleEvent(event)
-- Event type
print(event.type) -- "click", "change", etc.
-- Target element
local target = event:GetCurrentElement()
-- Mouse position (for mouse events)
local x = event.parameters.mouse_x
local y = event.parameters.mouse_y
-- Stop propagation
event:StopPropagation()
end
```
### Common Events
| Event | Description |
|-------|-------------|
| `click` | Element clicked |
| `dblclick` | Element double-clicked |
| `mousedown` | Mouse button pressed |
| `mouseup` | Mouse button released |
| `mouseover` | Mouse enters element |
| `mouseout` | Mouse leaves element |
| `focus` | Element gains focus |
| `blur` | Element loses focus |
| `change` | Input value changed |
| `submit` | Form submitted |
| `keydown` | Key pressed |
| `keyup` | Key released |
## Timers
### setTimeout
```lua
-- Execute once after delay
local timerId = setTimeout(function()
print("Executed after 1 second")
end, 1000) -- milliseconds
-- Cancel timer
clearTimeout(timerId)
```
### setInterval
```lua
-- Execute repeatedly
local intervalId = setInterval(function()
print("Tick")
end, 1000)
-- Cancel interval
clearInterval(intervalId)
```
## Storage
Persist data between app sessions:
```lua
-- Save data
storage.set("username", "Alice")
storage.set("settings", {
darkMode = true,
notifications = false
})
-- Load data
local username = storage.get("username")
local settings = storage.get("settings")
-- Delete data
storage.remove("username")
-- Clear all data
storage.clear()
```
## Navigation
Navigate between screens in your app:
```lua
-- Navigate to screen
navigateTo("settings") -- loads assets/settings.rml
-- Go back
goBack()
-- Go to home screen
goHome()
-- Replace current screen (no back)
replaceTo("login")
```
### Navigation Events
```lua
-- Listen for navigation
onNavigate(function(screenName)
print("Navigated to: " .. screenName)
end)
-- Listen for back
onBack(function()
print("Going back")
end)
```
## HTTP Requests
Make network requests (requires `network` permission):
```lua
-- GET request
http.get("https://api.example.com/data", function(response)
if response.ok then
local data = json.decode(response.body)
print(data.message)
else
print("Error: " .. response.status)
end
end)
-- POST request
http.post("https://api.example.com/submit", {
headers = {
["Content-Type"] = "application/json"
},
body = json.encode({
name = "Alice",
action = "subscribe"
})
}, function(response)
print("Status: " .. response.status)
end)
```
## JSON
```lua
-- Parse JSON string
local data = json.decode('{"name": "Alice", "age": 25}')
print(data.name)
-- Convert to JSON string
local str = json.encode({
items = {"a", "b", "c"},
count = 3
})
```
## Date and Time
```lua
-- Current timestamp
local now = os.time()
-- Format date
local formatted = os.date("%Y-%m-%d %H:%M:%S", now)
-- Parse date components
local t = os.date("*t", now)
print(t.year, t.month, t.day, t.hour, t.min, t.sec)
```
## Utilities
### String Functions
```lua
-- Concatenation
local greeting = "Hello, " .. name .. "!"
-- String functions
string.upper("hello") -- "HELLO"
string.lower("HELLO") -- "hello"
string.sub("hello", 1, 3) -- "hel"
string.find("hello", "ll") -- 3
string.gsub("hello", "l", "L") -- "heLLo"
string.format("Score: %d", 100) -- "Score: 100"
```
### Math Functions
```lua
math.floor(3.7) -- 3
math.ceil(3.2) -- 4
math.round(3.5) -- 4
math.abs(-5) -- 5
math.min(1, 2, 3) -- 1
math.max(1, 2, 3) -- 3
math.random() -- 0-1
math.random(1, 6) -- 1-6
```
### Table Functions
```lua
-- Insert
table.insert(items, "new item")
table.insert(items, 1, "at beginning")
-- Remove
table.remove(items) -- remove last
table.remove(items, 1) -- remove first
-- Sort
table.sort(items)
table.sort(items, function(a, b) return a > b end) -- descending
-- Length
local count = #items
```
## Sandbox Restrictions
For security, these are **NOT** available:
- `os.execute`, `io.popen` - No shell commands
- `loadfile`, `dofile` - No arbitrary file loading
- `require` - No external modules (use `import` for app modules)
- `debug` library - No debugging hooks
- `rawget`, `rawset` - No metatable bypass
## Best Practices
1. **Use local variables** - Faster and prevents pollution
2. **Handle errors** - Use `pcall` for operations that might fail
3. **Clean up timers** - Clear intervals when navigating away
4. **Minimize DOM queries** - Cache element references
5. **Batch updates** - Group style changes together
### Error Handling
```lua
local success, result = pcall(function()
-- Code that might fail
local data = json.decode(invalidJson)
return data
end)
if success then
print("Parsed:", result)
else
print("Error:", result)
end
```
### Module Pattern
```lua
-- utils.lua
local Utils = {}
function Utils.formatCurrency(amount)
return string.format("$%.2f", amount)
end
function Utils.capitalize(str)
return str:sub(1,1):upper() .. str:sub(2)
end
return Utils
```
```lua
-- main.lua
local Utils = import("utils")
print(Utils.formatCurrency(19.99))
```
## Next Steps
- [Permissions Guide](permissions.md) - Request device capabilities
- [API Reference](../api/lua-api.md) - Complete API documentation
- [Debugging Guide](debugging.md) - Debug your Lua code

View File

@@ -0,0 +1,396 @@
# Permissions Guide
Mosis apps run in a secure sandbox with limited access to device features. To access sensitive capabilities, apps must declare permissions in their manifest.
## Why Permissions?
Permissions protect user privacy and security by:
1. **Informing users** what an app can access before installation
2. **Limiting damage** if an app misbehaves
3. **Maintaining trust** in the Mosis ecosystem
## Declaring Permissions
Add permissions to your `manifest.json`:
```json
{
"id": "com.example.myapp",
"name": "My App",
"permissions": [
"storage",
"network"
]
}
```
Only request permissions your app actually needs. Users are more likely to trust apps with fewer permissions.
## Available Permissions
### storage
**Description:** Persist data locally between app sessions.
**Use cases:**
- Save user preferences
- Cache data for offline use
- Store app state
**API access:**
```lua
storage.set("key", value)
storage.get("key")
storage.remove("key")
storage.clear()
```
**Note:** All apps have access to in-memory storage during a session. The `storage` permission enables persistence across sessions.
---
### network
**Description:** Make HTTP/HTTPS requests to external servers.
**Use cases:**
- Fetch data from APIs
- Submit form data
- Load remote content
**API access:**
```lua
http.get(url, callback)
http.post(url, options, callback)
http.request(options, callback)
```
**Restrictions:**
- HTTPS only (HTTP blocked for security)
- Cannot access localhost or internal IPs
- Subject to CORS policies
---
### clipboard
**Description:** Read from and write to the system clipboard.
**Use cases:**
- Copy text or data
- Paste user content
- Share functionality
**API access:**
```lua
clipboard.write(text)
clipboard.read(callback)
```
---
### notifications
**Description:** Display system notifications to the user.
**Use cases:**
- Reminders
- Alerts
- Background updates
**API access:**
```lua
notifications.show({
title = "Reminder",
body = "Your timer is done!",
icon = "icons/alarm.png"
})
```
**Restrictions:**
- Notifications may be rate-limited
- Users can disable notifications per-app
---
### camera
**Description:** Capture photos using the device camera.
**Use cases:**
- Photo capture
- QR code scanning
- Augmented reality
**API access:**
```lua
camera.capture({
quality = "high",
facing = "back"
}, function(result)
if result.success then
local imageData = result.data
end
end)
```
**Restrictions:**
- User prompt before first access
- Cannot record video (photo only)
---
### microphone
**Description:** Record audio from the device microphone.
**Use cases:**
- Voice notes
- Audio messages
- Voice commands
**API access:**
```lua
microphone.start()
microphone.stop(function(result)
local audioData = result.data
end)
```
**Restrictions:**
- User prompt before first access
- Maximum recording duration enforced
---
### location
**Description:** Access device location information.
**Use cases:**
- Weather apps
- Maps
- Location-based features
**API access:**
```lua
location.get(function(result)
if result.success then
print(result.latitude, result.longitude)
end
end)
location.watch(function(result)
-- Called on location changes
end)
```
**Restrictions:**
- User prompt before first access
- Approximate location only (no precise GPS)
- Battery impact warning
---
### contacts
**Description:** Read device contacts.
**Use cases:**
- Contact picker
- Address book integration
- Sharing with friends
**API access:**
```lua
contacts.pick(function(result)
if result.success then
print(result.name, result.phone)
end
end)
contacts.getAll(function(result)
for i, contact in ipairs(result.contacts) do
print(contact.name)
end
end)
```
**Restrictions:**
- Read-only access
- User prompt before first access
## Permission Levels
| Level | Description | Example |
|-------|-------------|---------|
| **Normal** | Low risk, minimal review | storage |
| **Sensitive** | Requires user prompt | camera, microphone, location |
| **Dangerous** | Extensive review required | contacts |
## Runtime Behavior
### First-Time Prompts
Some permissions trigger a user prompt on first use:
```lua
-- First call triggers prompt
camera.capture(options, function(result)
if result.denied then
-- User denied permission
showPermissionExplanation()
elseif result.success then
-- Permission granted
handlePhoto(result.data)
end
end)
```
### Checking Permission Status
```lua
-- Check if permission is granted
if permissions.check("camera") then
-- Already have permission
showCameraButton()
else
-- Need to request
showRequestButton()
end
```
### Requesting at Runtime
```lua
permissions.request("camera", function(granted)
if granted then
startCamera()
else
showAlternative()
end
end)
```
## Best Practices
### 1. Minimize Permissions
Only request what you need. An app with fewer permissions:
- Builds more user trust
- Passes review faster
- Has smaller attack surface
### 2. Request at the Right Time
Don't request all permissions at startup. Request when the user takes an action that needs it:
```lua
-- Bad: Request on app start
function onAppStart()
permissions.request("camera") -- Why?
end
-- Good: Request when needed
function onTakePhotoClicked()
permissions.request("camera", function(granted)
if granted then
camera.capture(options, handlePhoto)
end
end)
end
```
### 3. Explain Why
Tell users why you need a permission before requesting:
```xml
<div id="permission-explanation" style="display: none;">
<p>This app needs camera access to scan QR codes.</p>
<button onclick="requestCamera()">Enable Camera</button>
</div>
```
### 4. Handle Denial Gracefully
Apps should work (with reduced functionality) even if permissions are denied:
```lua
function capturePhoto()
if not permissions.check("camera") then
-- Offer alternative
showManualEntryOption()
return
end
-- Proceed with camera
end
```
### 5. Don't Ask Again Immediately
If a user denies a permission, don't immediately ask again:
```lua
local lastDenied = storage.get("camera_denied_time")
if lastDenied and os.time() - lastDenied < 86400 then
-- Wait at least 24 hours before asking again
return
end
```
## Review Impact
Permission requests affect app review:
| Permission | Review Impact |
|------------|---------------|
| storage, network | Automatic approval |
| clipboard | Quick review |
| notifications | Standard review |
| camera, microphone | Extended review |
| location | Extended review |
| contacts | Manual review required |
Apps requesting sensitive permissions must:
1. Justify the need in submission notes
2. Use the permission appropriately
3. Respect user privacy
## Troubleshooting
### "Permission not declared"
```
Error: Cannot use camera without 'camera' permission
```
Add the permission to your manifest:
```json
"permissions": ["camera"]
```
### "Permission denied by user"
Handle this gracefully in your code:
```lua
if result.denied then
showAlternativeUI()
end
```
### "Permission blocked"
The user permanently blocked the permission. Direct them to settings:
```lua
if result.blocked then
showMessage("Please enable camera in system settings")
end
```
## See Also
- [Manifest Reference](../api/manifest.md) - Full manifest documentation
- [Security Guide](security.md) - App security best practices
- [Publishing Guide](publishing.md) - App review process

View File

@@ -0,0 +1,395 @@
# UI Design Guide
Mosis uses RML (RmlUi Markup Language) and RCSS (RmlUi CSS) for building user interfaces. If you know HTML and CSS, you'll feel right at home.
## RML Basics
RML is similar to HTML but with some differences optimized for UI rendering.
### Document Structure
```xml
<rml>
<head>
<title>App Title</title>
<link type="text/rcss" href="styles.rcss"/>
</head>
<body>
<!-- Your UI here -->
</body>
</rml>
```
### Common Elements
| Element | Usage |
|---------|-------|
| `<div>` | Container/layout |
| `<p>` | Paragraph text |
| `<span>` | Inline text |
| `<h1>` - `<h6>` | Headings |
| `<img>` | Images |
| `<button>` | Clickable buttons |
| `<input>` | Text input fields |
| `<select>` | Dropdown menus |
| `<progress>` | Progress bars |
### Layout Example
```xml
<div class="app-bar">
<div class="app-bar-nav" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
<span class="app-bar-title">My App</span>
</div>
<div class="content">
<div class="card">
<h2>Welcome</h2>
<p>This is a card component.</p>
</div>
</div>
<div class="dock">
<button class="dock-item" onclick="navigateTo('home')">
<img src="icons/home.tga"/>
</button>
</div>
```
## RCSS Styling
RCSS is CSS with some limitations and extensions.
### Supported Properties
**Layout:**
- `display` (block, inline, inline-block, flex, none)
- `position` (static, relative, absolute, fixed)
- `width`, `height`, `min-width`, `max-width`, `min-height`, `max-height`
- `margin`, `padding` (including directional variants)
- `flex`, `flex-direction`, `flex-wrap`, `justify-content`, `align-items`
- `overflow` (visible, hidden, scroll, auto)
**Visual:**
- `background-color`, `background` (with decorators)
- `color`
- `border`, `border-radius`
- `opacity`
- `box-shadow` (via decorators)
**Typography:**
- `font-family`
- `font-size`
- `font-weight` (normal, bold)
- `font-style` (normal, italic)
- `text-align` (left, center, right)
- `line-height`
- `text-decoration`
### Units
| Unit | Description |
|------|-------------|
| `dp` | Density-independent pixels (recommended) |
| `px` | Pixels |
| `%` | Percentage of parent |
| `em` | Relative to font size |
Always use `dp` for consistent sizing across devices:
```css
.button {
padding: 12dp 24dp;
font-size: 16dp;
border-radius: 8dp;
}
```
### Colors
```css
/* Hex colors */
color: #ffffff;
color: #fff;
color: #00d4ff80; /* with alpha */
/* RGB/RGBA */
color: rgb(255, 255, 255);
color: rgba(0, 212, 255, 0.5);
```
### Pseudo-classes
```css
button {
background-color: #00d4ff;
}
button:hover {
background-color: #00b8e6;
}
button:active {
background-color: #0099cc;
}
button:focus {
border: 2dp solid #ffffff;
}
button:disabled {
opacity: 0.5;
}
```
## Flexbox Layout
RCSS supports flexbox for modern layouts:
```css
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.column {
display: flex;
flex-direction: column;
gap: 10dp;
}
.grow {
flex: 1;
}
```
```xml
<div class="row">
<span>Left</span>
<span class="grow"></span>
<span>Right</span>
</div>
```
## Images
Images should be in TGA format for best performance:
```xml
<img src="icons/star.tga"/>
```
Supported formats:
- TGA (recommended)
- PNG
- JPEG
### Image Sizing
```css
img {
width: 32dp;
height: 32dp;
}
/* Aspect ratio maintained */
img.icon {
width: 24dp;
height: auto;
}
```
## Input Elements
### Text Input
```xml
<input type="text" id="username" placeholder="Enter username"/>
```
```css
input {
width: 100%;
padding: 12dp;
background-color: #2a2a4e;
border: 1dp solid #3a3a5e;
border-radius: 8dp;
color: #ffffff;
font-size: 16dp;
}
input:focus {
border-color: #00d4ff;
}
```
### Select/Dropdown
```xml
<select id="country">
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
</select>
```
### Progress Bar
```xml
<progress id="loading" value="0.5" max="1"/>
```
```css
progress {
width: 100%;
height: 8dp;
background-color: #2a2a4e;
border-radius: 4dp;
}
progress fill {
background-color: #00d4ff;
border-radius: 4dp;
}
```
## Scrolling
```xml
<div class="scroll-container">
<div class="scroll-content">
<!-- Long content here -->
</div>
</div>
```
```css
.scroll-container {
height: 300dp;
overflow: auto;
}
```
## Decorators
RCSS uses decorators for advanced visual effects:
```css
/* Gradient background */
.gradient {
decorator: horizontal-gradient(#1a1a2e #2a2a4e);
}
/* Image background */
.card {
decorator: image(background.tga);
}
/* Border image */
.fancy-border {
decorator: ninepatch(border.tga, 10dp, 10dp, 10dp, 10dp);
}
```
## Animations
RCSS supports keyframe animations:
```css
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fade-in 0.3s ease-out;
}
```
### Transitions
```css
.button {
transition: background-color 0.2s, transform 0.1s;
}
.button:hover {
background-color: #00b8e6;
}
.button:active {
transform: scale(0.95);
}
```
## Responsive Design
Design for the Mosis phone screen (1080x1920 logical pixels):
```css
/* Base styles for portrait */
.content {
padding: 16dp;
}
/* Adjust for available space */
.app-bar {
height: 56dp;
padding: 0 16dp;
}
.dock {
height: 64dp;
padding: 8dp;
}
```
## Design Tokens
Use CSS variables for consistent theming:
```css
:root {
--primary: #00d4ff;
--primary-dark: #00b8e6;
--background: #1a1a2e;
--surface: #2a2a4e;
--text-primary: #ffffff;
--text-secondary: #a0a0a0;
--spacing-sm: 8dp;
--spacing-md: 16dp;
--spacing-lg: 24dp;
--radius-sm: 4dp;
--radius-md: 8dp;
--radius-lg: 16dp;
}
.button {
background-color: var(--primary);
padding: var(--spacing-md);
border-radius: var(--radius-md);
}
```
## Best Practices
1. **Use dp units** - Ensures consistent sizing across devices
2. **Test touch targets** - Minimum 48dp for touchable elements
3. **Maintain contrast** - Ensure text is readable (4.5:1 ratio minimum)
4. **Use semantic structure** - Proper headings, lists, etc.
5. **Optimize images** - Use TGA format, appropriate sizes
6. **Keep it simple** - Mobile-first design, avoid clutter
## Next Steps
- [Lua Scripting Guide](lua-scripting.md) - Add interactivity
- [Components Library](components.md) - Pre-built UI components
- [Theme Reference](theme.md) - Complete theming guide