implement data model binding
This commit is contained in:
@@ -33,6 +33,9 @@ set(RMLUI_SOURCE_DIR ${rmlui_SOURCE_DIR})
|
|||||||
|
|
||||||
add_executable(mosis-designer
|
add_executable(mosis-designer
|
||||||
main.cpp
|
main.cpp
|
||||||
|
src/data_models.cpp
|
||||||
|
src/system_interface.cpp
|
||||||
|
src/utils.cpp
|
||||||
${rmlui_SOURCE_DIR}/Backends/RmlUi_Backend_GLFW_GL3.cpp
|
${rmlui_SOURCE_DIR}/Backends/RmlUi_Backend_GLFW_GL3.cpp
|
||||||
${rmlui_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp
|
${rmlui_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp
|
||||||
${rmlui_SOURCE_DIR}/Backends/RmlUi_Renderer_GL3.cpp
|
${rmlui_SOURCE_DIR}/Backends/RmlUi_Renderer_GL3.cpp
|
||||||
|
|||||||
@@ -40,6 +40,11 @@
|
|||||||
|
|
||||||
.browser-nav-btn.disabled {
|
.browser-nav-btn.disabled {
|
||||||
color: #444444;
|
color: #444444;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-nav-btn.disabled:hover {
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser-url-bar {
|
.browser-url-bar {
|
||||||
@@ -92,6 +97,10 @@
|
|||||||
|
|
||||||
.browser-page-link {
|
.browser-page-link {
|
||||||
color: #1a0dab;
|
color: #1a0dab;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-page-link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +110,11 @@
|
|||||||
|
|
||||||
.browser-search-item {
|
.browser-search-item {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-search-item:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.browser-search-title {
|
.browser-search-title {
|
||||||
@@ -161,7 +175,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="browser-screen">
|
<body class="browser-screen" data-model="browser">
|
||||||
<!-- Status Bar -->
|
<!-- Status Bar -->
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<span class="status-bar-time">12:30</span>
|
<span class="status-bar-time">12:30</span>
|
||||||
@@ -174,34 +188,43 @@
|
|||||||
|
|
||||||
<!-- Browser Toolbar -->
|
<!-- Browser Toolbar -->
|
||||||
<div class="browser-toolbar">
|
<div class="browser-toolbar">
|
||||||
<div class="browser-nav-btn" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
<div class="browser-nav-btn" data-class-disabled="!can_go_back" onclick="goBack()">
|
||||||
<div class="browser-nav-btn disabled"><img src="../icons/forward.tga" style="width: 24px; height: 24px; opacity: 0.3;"/></div>
|
<img src="../icons/back.tga" style="width: 24px; height: 24px;"/>
|
||||||
<div class="browser-url-bar">
|
</div>
|
||||||
<input class="browser-url" type="text" value="example.com"/>
|
<div class="browser-nav-btn disabled">
|
||||||
|
<img src="../icons/forward.tga" style="width: 24px; height: 24px; opacity: 0.3;"/>
|
||||||
|
</div>
|
||||||
|
<div class="browser-url-bar">
|
||||||
|
<span class="browser-secure-icon">🔒</span>
|
||||||
|
<input class="browser-url" type="text" data-value="current_url"/>
|
||||||
|
</div>
|
||||||
|
<div class="browser-nav-btn" data-event-click="refresh()">
|
||||||
|
<img src="../icons/refresh.tga" style="width: 24px; height: 24px;"/>
|
||||||
|
</div>
|
||||||
|
<div class="browser-nav-btn">
|
||||||
|
<img src="../icons/more.tga" style="width: 24px; height: 24px;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="browser-nav-btn"><img src="../icons/refresh.tga" style="width: 24px; height: 24px;"/></div>
|
|
||||||
<div class="browser-nav-btn"><img src="../icons/more.tga" style="width: 24px; height: 24px;"/></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Browser Content -->
|
<!-- Browser Content -->
|
||||||
<div class="browser-content">
|
<div class="browser-content">
|
||||||
<div class="browser-page">
|
<div class="browser-page">
|
||||||
<div class="browser-page-title">Example Domain</div>
|
<div class="browser-page-title">{{ page_title }}</div>
|
||||||
<div class="browser-page-text">
|
<div class="browser-page-text">
|
||||||
This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
|
{{ page_content }}
|
||||||
</div>
|
</div>
|
||||||
<div class="browser-page-text">
|
<div class="browser-page-text">
|
||||||
<span class="browser-page-link">More information...</span>
|
<span class="browser-page-link" data-event-click="navigate('iana.org')">More information...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0e0e0;">
|
<div style="margin-top: 32px; padding-top: 16px; border-top: 1px solid #e0e0e0;">
|
||||||
<div class="browser-page-title" style="font-size: 18px;">Related Links</div>
|
<div class="browser-page-title" style="font-size: 18px;">Related Links</div>
|
||||||
<div class="browser-search-item">
|
<div class="browser-search-item" data-event-click="navigate('iana.org/domains')">
|
||||||
<div class="browser-search-title">IANA — IANA-managed Reserved Domains</div>
|
<div class="browser-search-title">IANA — IANA-managed Reserved Domains</div>
|
||||||
<div class="browser-search-url">www.iana.org > domains > reserved</div>
|
<div class="browser-search-url">www.iana.org > domains > reserved</div>
|
||||||
<div class="browser-search-desc">Certain domains are set aside and unavailable for registration. Learn about reserved top-level domains.</div>
|
<div class="browser-search-desc">Certain domains are set aside and unavailable for registration. Learn about reserved top-level domains.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="browser-search-item">
|
<div class="browser-search-item" data-event-click="navigate('tools.ietf.org/html/rfc2606')">
|
||||||
<div class="browser-search-title">RFC 2606 - Reserved Top Level DNS Names</div>
|
<div class="browser-search-title">RFC 2606 - Reserved Top Level DNS Names</div>
|
||||||
<div class="browser-search-url">tools.ietf.org > html > rfc2606</div>
|
<div class="browser-search-url">tools.ietf.org > html > rfc2606</div>
|
||||||
<div class="browser-search-desc">This document describes some domain names that are reserved for documentation purposes.</div>
|
<div class="browser-search-desc">This document describes some domain names that are reserved for documentation purposes.</div>
|
||||||
@@ -217,7 +240,7 @@
|
|||||||
<span class="browser-tab-label">Home</span>
|
<span class="browser-tab-label">Home</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="browser-tab-btn">
|
<div class="browser-tab-btn">
|
||||||
<span class="browser-tabs-indicator">3</span>
|
<span class="browser-tabs-indicator">{{ tabs.size }}</span>
|
||||||
<span class="browser-tab-label">Tabs</span>
|
<span class="browser-tab-label">Tabs</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="browser-tab-btn">
|
<div class="browser-tab-btn">
|
||||||
|
|||||||
126
assets/screens/calling.rml
Normal file
126
assets/screens/calling.rml
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<rml>
|
||||||
|
<head>
|
||||||
|
<link type="text/rcss" href="../ui/html.rcss"/>
|
||||||
|
<link type="text/rcss" href="../ui/theme.rcss"/>
|
||||||
|
<link type="text/rcss" href="../ui/components.rcss"/>
|
||||||
|
<script src="../scripts/navigation.lua"></script>
|
||||||
|
<script src="../scripts/phone.lua"></script>
|
||||||
|
<title>Calling</title>
|
||||||
|
<style>
|
||||||
|
.calling-screen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, #1a237e 0%, #121212 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calling-status {
|
||||||
|
margin-top: 80px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4CAF50;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calling-name {
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calling-number {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #B3B3B3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calling-avatar {
|
||||||
|
margin-top: 48px;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 60px;
|
||||||
|
background-color: #BB86FC;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 48px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calling-actions {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 120px;
|
||||||
|
display: flex;
|
||||||
|
gap: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-action-btn {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 32px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-action-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-action-btn img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-call-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 40px;
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
border-radius: 36px;
|
||||||
|
background-color: #F44336;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-call-btn:hover {
|
||||||
|
background-color: #E53935;
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-call-btn img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="calling-screen">
|
||||||
|
<div class="calling-status">Calling...</div>
|
||||||
|
<div class="calling-name" id="calling-name">Unknown</div>
|
||||||
|
<div class="calling-number" id="calling-number"></div>
|
||||||
|
|
||||||
|
<div class="calling-avatar" id="calling-avatar">?</div>
|
||||||
|
|
||||||
|
<div class="calling-actions">
|
||||||
|
<div class="call-action-btn">
|
||||||
|
<img src="../icons/dialpad.tga"/>
|
||||||
|
</div>
|
||||||
|
<div class="call-action-btn">
|
||||||
|
<img src="../icons/contacts.tga"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="end-call-btn" onclick="endCall()">
|
||||||
|
<img src="../icons/phone.tga"/>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</rml>
|
||||||
164
assets/screens/chat.rml
Normal file
164
assets/screens/chat.rml
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<rml>
|
||||||
|
<head>
|
||||||
|
<link type="text/rcss" href="../ui/html.rcss"/>
|
||||||
|
<link type="text/rcss" href="../ui/theme.rcss"/>
|
||||||
|
<link type="text/rcss" href="../ui/components.rcss"/>
|
||||||
|
<script src="../scripts/navigation.lua"></script>
|
||||||
|
<script src="../scripts/messages.lua"></script>
|
||||||
|
<title>Chat</title>
|
||||||
|
<style>
|
||||||
|
.chat-screen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #121212;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header-status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-messages {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble {
|
||||||
|
max-width: 75%;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-sent {
|
||||||
|
align-self: flex-end;
|
||||||
|
background-color: #BB86FC;
|
||||||
|
color: #000000;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-received {
|
||||||
|
align-self: flex-start;
|
||||||
|
background-color: #2D2D2D;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666666;
|
||||||
|
margin-top: 4px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time-received {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #1E1E1E;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background-color: #2D2D2D;
|
||||||
|
border-radius: 24px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-send-btn {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background-color: #BB86FC;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-send-btn:hover {
|
||||||
|
background-color: #9C64FC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-send-btn img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="chat-screen">
|
||||||
|
<!-- Chat Header -->
|
||||||
|
<div class="app-bar">
|
||||||
|
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
|
<div class="chat-avatar" id="chat-avatar" style="background-color: #4CAF50;">J</div>
|
||||||
|
<div class="chat-header-info">
|
||||||
|
<div class="chat-header-name" id="chat-name">John Wilson</div>
|
||||||
|
<div class="chat-header-status">Online</div>
|
||||||
|
</div>
|
||||||
|
<div class="btn-icon"><img src="../icons/phone.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
|
<div class="btn-icon"><img src="../icons/more.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Messages -->
|
||||||
|
<div class="chat-messages" id="chat-messages">
|
||||||
|
<div class="message-bubble message-received">Hey!</div>
|
||||||
|
<div class="message-bubble message-received">What are you up to?</div>
|
||||||
|
<div class="message-bubble message-sent">Not much, just working</div>
|
||||||
|
<div class="message-bubble message-received">Cool! There's a party at Mike's tonight</div>
|
||||||
|
<div class="message-bubble message-received">Hey, are you coming to the party tonight?</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Input Bar -->
|
||||||
|
<div class="chat-input-bar">
|
||||||
|
<div class="btn-icon"><img src="../icons/add.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
|
<input class="chat-input" type="text" placeholder="Type a message..." id="message-input"/>
|
||||||
|
<div class="chat-send-btn">
|
||||||
|
<img src="../icons/send.tga"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</rml>
|
||||||
161
assets/screens/contact_detail.rml
Normal file
161
assets/screens/contact_detail.rml
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<rml>
|
||||||
|
<head>
|
||||||
|
<link type="text/rcss" href="../ui/html.rcss"/>
|
||||||
|
<link type="text/rcss" href="../ui/theme.rcss"/>
|
||||||
|
<link type="text/rcss" href="../ui/components.rcss"/>
|
||||||
|
<script src="../scripts/navigation.lua"></script>
|
||||||
|
<script src="../scripts/phone.lua"></script>
|
||||||
|
<script src="../scripts/contacts.lua"></script>
|
||||||
|
<title>Contact</title>
|
||||||
|
<style>
|
||||||
|
.contact-detail-screen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #121212;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-header {
|
||||||
|
background: linear-gradient(180deg, #1E1E1E 0%, #121212 100%);
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-avatar-large {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 48px;
|
||||||
|
background-color: #BB86FC;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 40px;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-name-large {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 32px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-action-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background-color: #BB86FC;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-action-icon img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-action-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #B3B3B3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info-section {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #B3B3B3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-info-value {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #FFFFFF;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="contact-detail-screen" onload="updateContactDetail()">
|
||||||
|
<!-- App Bar -->
|
||||||
|
<div class="app-bar">
|
||||||
|
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
|
<span class="app-bar-title">Contact</span>
|
||||||
|
<div class="btn-icon"><img src="../icons/more.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Header -->
|
||||||
|
<div class="contact-header">
|
||||||
|
<div class="contact-avatar-large" id="contact-avatar">?</div>
|
||||||
|
<div class="contact-name-large" id="contact-name">Contact Name</div>
|
||||||
|
|
||||||
|
<div class="contact-actions">
|
||||||
|
<div class="contact-action" id="call-action">
|
||||||
|
<div class="contact-action-icon" style="background-color: #4CAF50;">
|
||||||
|
<img src="../icons/phone.tga"/>
|
||||||
|
</div>
|
||||||
|
<span class="contact-action-label">Call</span>
|
||||||
|
</div>
|
||||||
|
<div class="contact-action" id="message-action">
|
||||||
|
<div class="contact-action-icon" style="background-color: #2196F3;">
|
||||||
|
<img src="../icons/message.tga"/>
|
||||||
|
</div>
|
||||||
|
<span class="contact-action-label">Message</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Info -->
|
||||||
|
<div class="contact-info-section">
|
||||||
|
<div class="contact-info-item">
|
||||||
|
<img src="../icons/phone.tga" class="contact-info-icon" style="width: 24px; height: 24px;"/>
|
||||||
|
<div class="contact-info-content">
|
||||||
|
<div class="contact-info-label">Mobile</div>
|
||||||
|
<div class="contact-info-value" id="contact-phone">+1 (555) 000-0000</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="contact-info-item">
|
||||||
|
<img src="../icons/message.tga" class="contact-info-icon" style="width: 24px; height: 24px;"/>
|
||||||
|
<div class="contact-info-content">
|
||||||
|
<div class="contact-info-label">Email</div>
|
||||||
|
<div class="contact-info-value" id="contact-email">email@example.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</rml>
|
||||||
@@ -67,9 +67,29 @@
|
|||||||
color: #B3B3B3;
|
color: #B3B3B3;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-call-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-call-btn:hover {
|
||||||
|
background-color: rgba(76, 175, 80, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-call-btn img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="contacts-screen">
|
<body class="contacts-screen" data-model="contacts">
|
||||||
<!-- App Bar -->
|
<!-- App Bar -->
|
||||||
<div class="app-bar">
|
<div class="app-bar">
|
||||||
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
@@ -85,94 +105,16 @@
|
|||||||
|
|
||||||
<!-- Contacts List -->
|
<!-- Contacts List -->
|
||||||
<div class="contacts-list">
|
<div class="contacts-list">
|
||||||
<!-- A -->
|
<div data-for="contact : contacts">
|
||||||
<div class="contact-letter">A</div>
|
<div class="contact-item" data-event-click="select_contact(contact.id); navigateTo('contact_detail')">
|
||||||
<div class="contact-item">
|
<div class="contact-avatar" data-style-background-color="contact.color">{{ contact.initial }}</div>
|
||||||
<div class="contact-avatar" style="background-color: #E91E63;">A</div>
|
<div class="contact-info">
|
||||||
<div class="contact-info">
|
<div class="contact-name">{{ contact.name }}</div>
|
||||||
<div class="contact-name">Alice Johnson</div>
|
<div class="contact-phone">{{ contact.phone }}</div>
|
||||||
<div class="contact-phone">+1 (555) 123-4567</div>
|
</div>
|
||||||
</div>
|
<div class="contact-call-btn">
|
||||||
</div>
|
<img src="../icons/phone.tga"/>
|
||||||
<div class="contact-item">
|
</div>
|
||||||
<div class="contact-avatar" style="background-color: #9C27B0;">A</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Andrew Smith</div>
|
|
||||||
<div class="contact-phone">+1 (555) 234-5678</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- B -->
|
|
||||||
<div class="contact-letter">B</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #2196F3;">B</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Bob Williams</div>
|
|
||||||
<div class="contact-phone">+1 (555) 345-6789</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #00BCD4;">B</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Brian Davis</div>
|
|
||||||
<div class="contact-phone">+1 (555) 456-7890</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- C -->
|
|
||||||
<div class="contact-letter">C</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #4CAF50;">C</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Carol Martinez</div>
|
|
||||||
<div class="contact-phone">+1 (555) 567-8901</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- D -->
|
|
||||||
<div class="contact-letter">D</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #FF9800;">D</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">David Lee</div>
|
|
||||||
<div class="contact-phone">+1 (555) 678-9012</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- J -->
|
|
||||||
<div class="contact-letter">J</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #F44336;">J</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">John Wilson</div>
|
|
||||||
<div class="contact-phone">+1 (555) 789-0123</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- M -->
|
|
||||||
<div class="contact-letter">M</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #673AB7;">M</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Mom</div>
|
|
||||||
<div class="contact-phone">+1 (555) 890-1234</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #3F51B5;">M</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Mike Brown</div>
|
|
||||||
<div class="contact-phone">+1 (555) 901-2345</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- S -->
|
|
||||||
<div class="contact-letter">S</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<div class="contact-avatar" style="background-color: #009688;">S</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-name">Sarah Taylor</div>
|
|
||||||
<div class="contact-phone">+1 (555) 012-3456</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<link type="text/rcss" href="../ui/theme.rcss"/>
|
<link type="text/rcss" href="../ui/theme.rcss"/>
|
||||||
<link type="text/rcss" href="../ui/components.rcss"/>
|
<link type="text/rcss" href="../ui/components.rcss"/>
|
||||||
<script src="../scripts/navigation.lua"></script>
|
<script src="../scripts/navigation.lua"></script>
|
||||||
<script src="../scripts/phone.lua"></script>
|
|
||||||
<title>Phone</title>
|
<title>Phone</title>
|
||||||
<style>
|
<style>
|
||||||
.dialer-screen {
|
.dialer-screen {
|
||||||
@@ -105,7 +104,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="dialer-screen">
|
<body class="dialer-screen" data-model="phone">
|
||||||
<!-- App Bar -->
|
<!-- App Bar -->
|
||||||
<div class="app-bar">
|
<div class="app-bar">
|
||||||
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
@@ -114,69 +113,71 @@
|
|||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<div class="dialer-tabs">
|
<div class="dialer-tabs">
|
||||||
<div class="dialer-tab" id="tab-keypad">Keypad</div>
|
<div class="dialer-tab active" id="tab-keypad">Keypad</div>
|
||||||
<div class="dialer-tab active" id="tab-recent">Recent</div>
|
<div class="dialer-tab" id="tab-recent">Recent</div>
|
||||||
<div class="dialer-tab" id="tab-contacts">Contacts</div>
|
<div class="dialer-tab" onclick="navigateTo('contacts')">Contacts</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dial Display -->
|
<!-- Dial Display -->
|
||||||
<div class="dial-display" id="dial-display"></div>
|
<div class="dial-display">{{ dial_number }}</div>
|
||||||
|
|
||||||
<!-- Dial Pad -->
|
<!-- Dial Pad -->
|
||||||
<div class="dial-pad">
|
<div class="dial-pad">
|
||||||
<div class="dial-key" onclick="dialPress('1')">
|
<div class="dial-key" data-event-click="dial_press('1')">
|
||||||
<span class="dial-key-number">1</span>
|
<span class="dial-key-number">1</span>
|
||||||
<span class="dial-key-letters"></span>
|
<span class="dial-key-letters"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('2')">
|
<div class="dial-key" data-event-click="dial_press('2')">
|
||||||
<span class="dial-key-number">2</span>
|
<span class="dial-key-number">2</span>
|
||||||
<span class="dial-key-letters">ABC</span>
|
<span class="dial-key-letters">ABC</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('3')">
|
<div class="dial-key" data-event-click="dial_press('3')">
|
||||||
<span class="dial-key-number">3</span>
|
<span class="dial-key-number">3</span>
|
||||||
<span class="dial-key-letters">DEF</span>
|
<span class="dial-key-letters">DEF</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('4')">
|
<div class="dial-key" data-event-click="dial_press('4')">
|
||||||
<span class="dial-key-number">4</span>
|
<span class="dial-key-number">4</span>
|
||||||
<span class="dial-key-letters">GHI</span>
|
<span class="dial-key-letters">GHI</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('5')">
|
<div class="dial-key" data-event-click="dial_press('5')">
|
||||||
<span class="dial-key-number">5</span>
|
<span class="dial-key-number">5</span>
|
||||||
<span class="dial-key-letters">JKL</span>
|
<span class="dial-key-letters">JKL</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('6')">
|
<div class="dial-key" data-event-click="dial_press('6')">
|
||||||
<span class="dial-key-number">6</span>
|
<span class="dial-key-number">6</span>
|
||||||
<span class="dial-key-letters">MNO</span>
|
<span class="dial-key-letters">MNO</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('7')">
|
<div class="dial-key" data-event-click="dial_press('7')">
|
||||||
<span class="dial-key-number">7</span>
|
<span class="dial-key-number">7</span>
|
||||||
<span class="dial-key-letters">PQRS</span>
|
<span class="dial-key-letters">PQRS</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('8')">
|
<div class="dial-key" data-event-click="dial_press('8')">
|
||||||
<span class="dial-key-number">8</span>
|
<span class="dial-key-number">8</span>
|
||||||
<span class="dial-key-letters">TUV</span>
|
<span class="dial-key-letters">TUV</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('9')">
|
<div class="dial-key" data-event-click="dial_press('9')">
|
||||||
<span class="dial-key-number">9</span>
|
<span class="dial-key-number">9</span>
|
||||||
<span class="dial-key-letters">WXYZ</span>
|
<span class="dial-key-letters">WXYZ</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('*')">
|
<div class="dial-key" data-event-click="dial_press('*')">
|
||||||
<span class="dial-key-number">*</span>
|
<span class="dial-key-number">*</span>
|
||||||
<span class="dial-key-letters"></span>
|
<span class="dial-key-letters"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('0')">
|
<div class="dial-key" data-event-click="dial_press('0')">
|
||||||
<span class="dial-key-number">0</span>
|
<span class="dial-key-number">0</span>
|
||||||
<span class="dial-key-letters">+</span>
|
<span class="dial-key-letters">+</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dial-key" onclick="dialPress('#')">
|
<div class="dial-key" data-event-click="dial_press('#')">
|
||||||
<span class="dial-key-number">#</span>
|
<span class="dial-key-number">#</span>
|
||||||
<span class="dial-key-letters"></span>
|
<span class="dial-key-letters"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Call Button -->
|
<!-- Call Actions -->
|
||||||
<div class="dial-actions">
|
<div class="dial-actions">
|
||||||
<div class="dial-call-btn" onclick="makeCall()"><img src="../icons/call_small.tga" style="width: 32px; height: 32px;"/></div>
|
<div style="width: 56px;"></div>
|
||||||
|
<div class="dial-call-btn" data-event-click="make_call()"><img src="../icons/call_small.tga" style="width: 32px; height: 32px; pointer-events: none;"/></div>
|
||||||
|
<div class="btn-icon" data-event-click="dial_backspace()" style="width: 56px; height: 56px;"><img src="../icons/backspace.tga" style="width: 24px; height: 24px; pointer-events: none;"/></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom Navigation -->
|
<!-- Bottom Navigation -->
|
||||||
|
|||||||
@@ -86,50 +86,9 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat View Styles */
|
|
||||||
.chat-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-header-avatar {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background-color: #BB86FC;
|
|
||||||
margin-right: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-header-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-header-name {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-header-status {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #B3B3B3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="messages-screen">
|
<body class="messages-screen" data-model="messages">
|
||||||
<!-- App Bar -->
|
<!-- App Bar -->
|
||||||
<div class="app-bar">
|
<div class="app-bar">
|
||||||
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
@@ -139,82 +98,16 @@
|
|||||||
|
|
||||||
<!-- Conversations List -->
|
<!-- Conversations List -->
|
||||||
<div class="conversations-list">
|
<div class="conversations-list">
|
||||||
<div class="conversation-item">
|
<div class="conversation-item" data-for="conv : conversations" data-event-click="select_conversation(conv.id); navigateTo('chat')">
|
||||||
<div class="conversation-avatar" style="background-color: #4CAF50;">J</div>
|
<div class="conversation-avatar" data-style-background-color="conv.color">{{ conv.name | slice(0, 1) }}</div>
|
||||||
<div class="conversation-content">
|
<div class="conversation-content">
|
||||||
<div class="conversation-header">
|
<div class="conversation-header">
|
||||||
<span class="conversation-name">John Wilson</span>
|
<span class="conversation-name">{{ conv.name }}</span>
|
||||||
<span class="conversation-time">2:30 PM</span>
|
<span class="conversation-time">{{ conv.time }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="conversation-preview">Hey, are you coming to the party tonight?</div>
|
<div class="conversation-preview">{{ conv.last_message }}</div>
|
||||||
</div>
|
|
||||||
<div class="conversation-unread">2</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-item">
|
|
||||||
<div class="conversation-avatar" style="background-color: #673AB7;">M</div>
|
|
||||||
<div class="conversation-content">
|
|
||||||
<div class="conversation-header">
|
|
||||||
<span class="conversation-name">Mom</span>
|
|
||||||
<span class="conversation-time">1:15 PM</span>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-preview">Don't forget to call your grandmother!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-item">
|
|
||||||
<div class="conversation-avatar" style="background-color: #E91E63;">A</div>
|
|
||||||
<div class="conversation-content">
|
|
||||||
<div class="conversation-header">
|
|
||||||
<span class="conversation-name">Alice Johnson</span>
|
|
||||||
<span class="conversation-time">Yesterday</span>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-preview">Thanks for the help with the project!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-item">
|
|
||||||
<div class="conversation-avatar" style="background-color: #2196F3;">B</div>
|
|
||||||
<div class="conversation-content">
|
|
||||||
<div class="conversation-header">
|
|
||||||
<span class="conversation-name">Bob Williams</span>
|
|
||||||
<span class="conversation-time">Yesterday</span>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-preview">Did you see the game last night?</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-item">
|
|
||||||
<div class="conversation-avatar" style="background-color: #FF9800;">W</div>
|
|
||||||
<div class="conversation-content">
|
|
||||||
<div class="conversation-header">
|
|
||||||
<span class="conversation-name">Work Group</span>
|
|
||||||
<span class="conversation-time">Mon</span>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-preview">Sarah: Meeting moved to 3pm</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-item">
|
|
||||||
<div class="conversation-avatar" style="background-color: #009688;">S</div>
|
|
||||||
<div class="conversation-content">
|
|
||||||
<div class="conversation-header">
|
|
||||||
<span class="conversation-name">Sarah Taylor</span>
|
|
||||||
<span class="conversation-time">Sun</span>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-preview">See you at the coffee shop!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-item">
|
|
||||||
<div class="conversation-avatar" style="background-color: #F44336;">D</div>
|
|
||||||
<div class="conversation-content">
|
|
||||||
<div class="conversation-header">
|
|
||||||
<span class="conversation-name">David Lee</span>
|
|
||||||
<span class="conversation-time">Sat</span>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-preview">Great talking to you!</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="conversation-unread" data-if="conv.unread > 0">{{ conv.unread }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background-color: #666666;
|
background-color: #666666;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-toggle.active {
|
.settings-toggle.active {
|
||||||
@@ -147,7 +148,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="settings-screen">
|
<body class="settings-screen" data-model="settings">
|
||||||
<!-- App Bar -->
|
<!-- App Bar -->
|
||||||
<div class="app-bar">
|
<div class="app-bar">
|
||||||
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
<div class="btn-icon" onclick="goBack()"><img src="../icons/back.tga" style="width: 24px; height: 24px;"/></div>
|
||||||
@@ -161,8 +162,8 @@
|
|||||||
<div class="user-card">
|
<div class="user-card">
|
||||||
<div class="user-avatar">U</div>
|
<div class="user-avatar">U</div>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="user-name">User Name</div>
|
<div class="user-name">{{ user_name }}</div>
|
||||||
<div class="user-email">user@example.com</div>
|
<div class="user-email">{{ user_email }}</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="settings-action">></span>
|
<span class="settings-action">></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,9 +175,10 @@
|
|||||||
<div class="settings-icon">W</div>
|
<div class="settings-icon">W</div>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-title">Wi-Fi</div>
|
<div class="settings-title">Wi-Fi</div>
|
||||||
<div class="settings-subtitle">Connected to Home_Network</div>
|
<div class="settings-subtitle" data-if="wifi">Connected to {{ wifi_network }}</div>
|
||||||
|
<div class="settings-subtitle" data-if="!wifi">Off</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-toggle active">
|
<div class="settings-toggle" data-class-active="wifi" data-event-click="wifi = !wifi">
|
||||||
<div class="settings-toggle-thumb"></div>
|
<div class="settings-toggle-thumb"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,9 +186,10 @@
|
|||||||
<div class="settings-icon">B</div>
|
<div class="settings-icon">B</div>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-title">Bluetooth</div>
|
<div class="settings-title">Bluetooth</div>
|
||||||
<div class="settings-subtitle">Off</div>
|
<div class="settings-subtitle" data-if="bluetooth">On</div>
|
||||||
|
<div class="settings-subtitle" data-if="!bluetooth">Off</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-toggle">
|
<div class="settings-toggle" data-class-active="bluetooth" data-event-click="bluetooth = !bluetooth">
|
||||||
<div class="settings-toggle-thumb"></div>
|
<div class="settings-toggle-thumb"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +198,7 @@
|
|||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-title">Airplane Mode</div>
|
<div class="settings-title">Airplane Mode</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-toggle">
|
<div class="settings-toggle" data-class-active="airplane_mode" data-event-click="airplane_mode = !airplane_mode">
|
||||||
<div class="settings-toggle-thumb"></div>
|
<div class="settings-toggle-thumb"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,7 +235,7 @@
|
|||||||
<div class="settings-icon">B</div>
|
<div class="settings-icon">B</div>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-title">Battery</div>
|
<div class="settings-title">Battery</div>
|
||||||
<div class="settings-subtitle">85% - About 12h remaining</div>
|
<div class="settings-subtitle">{{ battery_percent }}% - {{ battery_remaining }}</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="settings-action">></span>
|
<span class="settings-action">></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,7 +243,7 @@
|
|||||||
<div class="settings-icon">S</div>
|
<div class="settings-icon">S</div>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-title">Storage</div>
|
<div class="settings-title">Storage</div>
|
||||||
<div class="settings-subtitle">45.2 GB used of 128 GB</div>
|
<div class="settings-subtitle">{{ storage_used }}</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="settings-action">></span>
|
<span class="settings-action">></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -269,9 +272,10 @@
|
|||||||
<div class="settings-icon">L</div>
|
<div class="settings-icon">L</div>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-title">Location</div>
|
<div class="settings-title">Location</div>
|
||||||
<div class="settings-subtitle">On - High accuracy</div>
|
<div class="settings-subtitle" data-if="location">On - High accuracy</div>
|
||||||
|
<div class="settings-subtitle" data-if="!location">Off</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-toggle active">
|
<div class="settings-toggle" data-class-active="location" data-event-click="location = !location">
|
||||||
<div class="settings-toggle-thumb"></div>
|
<div class="settings-toggle-thumb"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ local screens = {
|
|||||||
home = "screens/home.rml",
|
home = "screens/home.rml",
|
||||||
lock = "screens/lock.rml",
|
lock = "screens/lock.rml",
|
||||||
dialer = "screens/dialer.rml",
|
dialer = "screens/dialer.rml",
|
||||||
|
calling = "screens/calling.rml",
|
||||||
contacts = "screens/contacts.rml",
|
contacts = "screens/contacts.rml",
|
||||||
|
contact_detail = "screens/contact_detail.rml",
|
||||||
messages = "screens/messages.rml",
|
messages = "screens/messages.rml",
|
||||||
|
chat = "screens/chat.rml",
|
||||||
settings = "screens/settings.rml",
|
settings = "screens/settings.rml",
|
||||||
browser = "screens/browser.rml"
|
browser = "screens/browser.rml"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
-- Phone/Dialer functionality for Virtual Smartphone
|
|
||||||
|
|
||||||
-- Dial pad state
|
|
||||||
local dial_number = ""
|
|
||||||
local max_digits = 15
|
|
||||||
|
|
||||||
-- Add a digit to the dial display
|
|
||||||
function dialPress(digit)
|
|
||||||
if #dial_number < max_digits then
|
|
||||||
dial_number = dial_number .. digit
|
|
||||||
updateDialDisplay()
|
|
||||||
print("Dialed: " .. digit .. " | Number: " .. dial_number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clear the last digit
|
|
||||||
function dialBackspace()
|
|
||||||
if #dial_number > 0 then
|
|
||||||
dial_number = dial_number:sub(1, -2)
|
|
||||||
updateDialDisplay()
|
|
||||||
print("Backspace | Number: " .. dial_number)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clear all digits
|
|
||||||
function dialClear()
|
|
||||||
dial_number = ""
|
|
||||||
updateDialDisplay()
|
|
||||||
print("Cleared dial pad")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Update the dial display element
|
|
||||||
function updateDialDisplay()
|
|
||||||
local display = document:GetElementById("dial-display")
|
|
||||||
if display then
|
|
||||||
-- Format number with dashes for readability
|
|
||||||
local formatted = formatPhoneNumber(dial_number)
|
|
||||||
display.inner_rml = formatted
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Format phone number for display
|
|
||||||
function formatPhoneNumber(number)
|
|
||||||
local len = #number
|
|
||||||
if len == 0 then
|
|
||||||
return ""
|
|
||||||
elseif len <= 3 then
|
|
||||||
return number
|
|
||||||
elseif len <= 6 then
|
|
||||||
return number:sub(1, 3) .. "-" .. number:sub(4)
|
|
||||||
elseif len <= 10 then
|
|
||||||
return "(" .. number:sub(1, 3) .. ") " .. number:sub(4, 6) .. "-" .. number:sub(7)
|
|
||||||
else
|
|
||||||
return "+1 (" .. number:sub(1, 3) .. ") " .. number:sub(4, 6) .. "-" .. number:sub(7, 10)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Make a call (simulated)
|
|
||||||
function makeCall()
|
|
||||||
if #dial_number > 0 then
|
|
||||||
print("Calling: " .. dial_number)
|
|
||||||
-- In a real app, this would initiate a call
|
|
||||||
-- For now, just show feedback
|
|
||||||
else
|
|
||||||
print("No number to call")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- End a call (simulated)
|
|
||||||
function endCall()
|
|
||||||
print("Call ended")
|
|
||||||
dialClear()
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get the current dial number
|
|
||||||
function getDialNumber()
|
|
||||||
return dial_number
|
|
||||||
end
|
|
||||||
|
|
||||||
print("Phone system initialized")
|
|
||||||
239
main.cpp
239
main.cpp
@@ -2,10 +2,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstring>
|
|
||||||
#include <RmlUi/Core.h>
|
#include <RmlUi/Core.h>
|
||||||
#include <RmlUi/Debugger.h>
|
#include <RmlUi/Debugger.h>
|
||||||
#include <RmlUi/Lua.h>
|
#include <RmlUi/Lua.h>
|
||||||
@@ -14,7 +11,6 @@
|
|||||||
#include <RmlUi_Include_Windows.h>
|
#include <RmlUi_Include_Windows.h>
|
||||||
#include <RmlUi_Include_GL3.h>
|
#include <RmlUi_Include_GL3.h>
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <png.h>
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lua.h>
|
#include <lua.h>
|
||||||
@@ -22,106 +18,21 @@ extern "C" {
|
|||||||
#include <lualib.h>
|
#include <lualib.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global app state for Lua access
|
#include "src/data_models.h"
|
||||||
|
#include "src/system_interface.h"
|
||||||
|
#include "src/utils.h"
|
||||||
|
|
||||||
|
// Global app state
|
||||||
struct AppState {
|
struct AppState {
|
||||||
Rml::Context* context = nullptr;
|
Rml::Context* context = nullptr;
|
||||||
Rml::ElementDocument* document = nullptr;
|
Rml::ElementDocument* document = nullptr;
|
||||||
std::filesystem::path assets_path;
|
std::filesystem::path assets_path;
|
||||||
} g_app;
|
} g_app;
|
||||||
|
|
||||||
// Custom system interface to enable logging
|
|
||||||
class LoggingSystemInterface : public Rml::SystemInterface {
|
|
||||||
public:
|
|
||||||
LoggingSystemInterface(Rml::SystemInterface* backend) : backend_(backend) {}
|
|
||||||
|
|
||||||
double GetElapsedTime() override { return backend_->GetElapsedTime(); }
|
|
||||||
|
|
||||||
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override {
|
|
||||||
const char* type_str = "";
|
|
||||||
switch (type) {
|
|
||||||
case Rml::Log::LT_ERROR: type_str = "ERROR"; break;
|
|
||||||
case Rml::Log::LT_WARNING: type_str = "WARNING"; break;
|
|
||||||
case Rml::Log::LT_INFO: type_str = "INFO"; break;
|
|
||||||
case Rml::Log::LT_DEBUG: type_str = "DEBUG"; break;
|
|
||||||
default: type_str = "LOG"; break;
|
|
||||||
}
|
|
||||||
std::println("[RmlUi {}] {}", type_str, message);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward JoinPath to fix Windows path issues
|
|
||||||
void JoinPath(Rml::String& translated_path, const Rml::String& document_path, const Rml::String& path) override {
|
|
||||||
// Fix paths where colon was converted to pipe (D| -> D:)
|
|
||||||
std::string fixed_path = path;
|
|
||||||
if (fixed_path.length() >= 2 && fixed_path[1] == '|') {
|
|
||||||
fixed_path[1] = ':';
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fixed_doc = document_path;
|
|
||||||
if (fixed_doc.length() >= 2 && fixed_doc[1] == '|') {
|
|
||||||
fixed_doc[1] = ':';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use std::filesystem to join paths properly and normalize (resolve ..)
|
|
||||||
std::filesystem::path doc_dir = std::filesystem::path(fixed_doc).parent_path();
|
|
||||||
std::filesystem::path result = (doc_dir / fixed_path).lexically_normal();
|
|
||||||
translated_path = result.generic_string();
|
|
||||||
std::println("JoinPath: {} + {} -> {}", fixed_doc, fixed_path, translated_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Rml::SystemInterface* backend_;
|
|
||||||
};
|
|
||||||
|
|
||||||
static LoggingSystemInterface* g_logging_interface = nullptr;
|
static LoggingSystemInterface* g_logging_interface = nullptr;
|
||||||
|
|
||||||
// Custom file interface to fix Windows path issues (D| -> D:)
|
|
||||||
class WindowsFileInterface : public Rml::FileInterface {
|
|
||||||
public:
|
|
||||||
Rml::FileHandle Open(const Rml::String& path) override {
|
|
||||||
// Fix paths where colon was converted to pipe (D| -> D:)
|
|
||||||
std::string fixed_path = path;
|
|
||||||
if (fixed_path.length() >= 2 && fixed_path[1] == '|') {
|
|
||||||
fixed_path[1] = ':';
|
|
||||||
}
|
|
||||||
std::println("FileInterface::Open: {} -> {}", path, fixed_path);
|
|
||||||
|
|
||||||
FILE* fp = fopen(fixed_path.c_str(), "rb");
|
|
||||||
return reinterpret_cast<Rml::FileHandle>(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Close(Rml::FileHandle file) override {
|
|
||||||
fclose(reinterpret_cast<FILE*>(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Read(void* buffer, size_t size, Rml::FileHandle file) override {
|
|
||||||
return fread(buffer, 1, size, reinterpret_cast<FILE*>(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Seek(Rml::FileHandle file, long offset, int origin) override {
|
|
||||||
return fseek(reinterpret_cast<FILE*>(file), offset, origin) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Tell(Rml::FileHandle file) override {
|
|
||||||
return ftell(reinterpret_cast<FILE*>(file));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static WindowsFileInterface* g_file_interface = nullptr;
|
static WindowsFileInterface* g_file_interface = nullptr;
|
||||||
|
|
||||||
void load_fonts(const std::filesystem::path& dir)
|
|
||||||
{
|
|
||||||
for (const auto& file : std::filesystem::directory_iterator(dir))
|
|
||||||
{
|
|
||||||
if (file.path().extension() == ".ttf")
|
|
||||||
{
|
|
||||||
Rml::LoadFontFace(file.path().string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lua function: loadScreen(path) - loads a new RML document
|
// Lua function: loadScreen(path) - loads a new RML document
|
||||||
// Path is relative to assets folder
|
|
||||||
int lua_loadScreen(lua_State* L)
|
int lua_loadScreen(lua_State* L)
|
||||||
{
|
{
|
||||||
std::println("lua_loadScreen called!");
|
std::println("lua_loadScreen called!");
|
||||||
@@ -168,131 +79,12 @@ void registerLuaFunctions()
|
|||||||
{
|
{
|
||||||
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
|
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
|
||||||
|
|
||||||
// Register global function
|
|
||||||
lua_pushcfunction(L, lua_loadScreen);
|
lua_pushcfunction(L, lua_loadScreen);
|
||||||
lua_setglobal(L, "loadScreen");
|
lua_setglobal(L, "loadScreen");
|
||||||
|
|
||||||
std::println("Registered Lua functions");
|
std::println("Registered Lua functions");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DumpElementTree(Rml::Element* element, std::ofstream& out, int depth = 0)
|
|
||||||
{
|
|
||||||
if (!element) return;
|
|
||||||
|
|
||||||
std::string indent(depth * 2, ' ');
|
|
||||||
std::string id = element->GetId().empty() ? "" : "#" + element->GetId();
|
|
||||||
|
|
||||||
out << indent << element->GetTagName() << id
|
|
||||||
<< " @ (" << element->GetAbsoluteLeft() << ", " << element->GetAbsoluteTop() << ") "
|
|
||||||
<< element->GetOffsetWidth() << "x" << element->GetOffsetHeight() << "\n";
|
|
||||||
|
|
||||||
for (int i = 0; i < element->GetNumChildren(); i++)
|
|
||||||
DumpElementTree(element->GetChild(i), out, depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offscreen framebuffer for screenshot capture
|
|
||||||
struct OffscreenFBO {
|
|
||||||
GLuint fbo = 0;
|
|
||||||
GLuint texture = 0;
|
|
||||||
GLuint depth_rbo = 0;
|
|
||||||
int width = 0;
|
|
||||||
int height = 0;
|
|
||||||
|
|
||||||
bool create(int w, int h)
|
|
||||||
{
|
|
||||||
width = w;
|
|
||||||
height = h;
|
|
||||||
|
|
||||||
// Create framebuffer
|
|
||||||
glGenFramebuffers(1, &fbo);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
||||||
|
|
||||||
// Create color texture
|
|
||||||
glGenTextures(1, &texture);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
|
||||||
|
|
||||||
// Create depth/stencil renderbuffer
|
|
||||||
glGenRenderbuffers(1, &depth_rbo);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, depth_rbo);
|
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth_rbo);
|
|
||||||
|
|
||||||
// Check completeness
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
||||||
{
|
|
||||||
std::println("Failed to create offscreen framebuffer");
|
|
||||||
destroy();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy()
|
|
||||||
{
|
|
||||||
if (texture) glDeleteTextures(1, &texture);
|
|
||||||
if (depth_rbo) glDeleteRenderbuffers(1, &depth_rbo);
|
|
||||||
if (fbo) glDeleteFramebuffers(1, &fbo);
|
|
||||||
fbo = texture = depth_rbo = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind()
|
|
||||||
{
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
||||||
glViewport(0, 0, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<unsigned char> readPixels()
|
|
||||||
{
|
|
||||||
std::vector<unsigned char> pixels(width * height * 3);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
|
||||||
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
|
|
||||||
return pixels;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void SavePNG(const std::filesystem::path& path, const std::vector<unsigned char>& pixels, int width, int height)
|
|
||||||
{
|
|
||||||
// Flip vertically (OpenGL origin is bottom-left)
|
|
||||||
std::vector<unsigned char> flipped(width * height * 3);
|
|
||||||
for (int y = 0; y < height; y++)
|
|
||||||
{
|
|
||||||
std::memcpy(&flipped[y * width * 3],
|
|
||||||
&pixels[(height - 1 - y) * width * 3],
|
|
||||||
width * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE* fp = fopen(path.string().c_str(), "wb");
|
|
||||||
if (!fp)
|
|
||||||
{
|
|
||||||
std::println("Failed to open {} for writing", path.string());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
||||||
png_infop info = png_create_info_struct(png);
|
|
||||||
png_init_io(png, fp);
|
|
||||||
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB,
|
|
||||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
png_write_info(png, info);
|
|
||||||
|
|
||||||
std::vector<png_bytep> rows(height);
|
|
||||||
for (int y = 0; y < height; y++)
|
|
||||||
rows[y] = const_cast<png_bytep>(&flipped[y * width * 3]);
|
|
||||||
png_write_image(png, rows.data());
|
|
||||||
png_write_end(png, nullptr);
|
|
||||||
|
|
||||||
png_destroy_write_struct(&png, &info);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
std::println("Screenshot saved to: {}", path.string());
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(const int argc, const char* argv[])
|
int main(const int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
constexpr int window_width = 540;
|
constexpr int window_width = 540;
|
||||||
@@ -312,7 +104,7 @@ int main(const int argc, const char* argv[])
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Backend::Initialize("Load Document Sample", window_width, window_height, true))
|
if (!Backend::Initialize("Mosis Designer", window_width, window_height, true))
|
||||||
{
|
{
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
@@ -335,13 +127,16 @@ int main(const int argc, const char* argv[])
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize sample data and data models
|
||||||
|
initializeSampleData();
|
||||||
|
setupDataModels(g_app.context);
|
||||||
|
|
||||||
// Register custom Lua functions
|
// Register custom Lua functions
|
||||||
registerLuaFunctions();
|
registerLuaFunctions();
|
||||||
|
|
||||||
// Find the assets folder by checking for fonts/ subdirectory
|
// Find the assets folder by checking for fonts/ subdirectory
|
||||||
g_app.assets_path = std::filesystem::absolute(file.parent_path());
|
g_app.assets_path = std::filesystem::absolute(file.parent_path());
|
||||||
|
|
||||||
// Walk up the directory tree to find a folder containing a fonts/ subdirectory with .ttf files
|
|
||||||
std::filesystem::path fonts_path;
|
std::filesystem::path fonts_path;
|
||||||
while (!g_app.assets_path.empty() && g_app.assets_path.has_parent_path())
|
while (!g_app.assets_path.empty() && g_app.assets_path.has_parent_path())
|
||||||
{
|
{
|
||||||
@@ -383,7 +178,6 @@ int main(const int argc, const char* argv[])
|
|||||||
// Dump mode: render and capture screenshot
|
// Dump mode: render and capture screenshot
|
||||||
if (dump_mode)
|
if (dump_mode)
|
||||||
{
|
{
|
||||||
// Create offscreen FBO at window dimensions
|
|
||||||
OffscreenFBO fbo;
|
OffscreenFBO fbo;
|
||||||
if (!fbo.create(window_width, window_height))
|
if (!fbo.create(window_width, window_height))
|
||||||
{
|
{
|
||||||
@@ -414,26 +208,21 @@ int main(const int argc, const char* argv[])
|
|||||||
dump_file.close();
|
dump_file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind FBO and set up OpenGL state for rendering
|
// Bind FBO and render
|
||||||
fbo.bind();
|
fbo.bind();
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
|
|
||||||
// Enable blending for proper text rendering
|
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Render to FBO
|
|
||||||
g_app.context->Update();
|
g_app.context->Update();
|
||||||
g_app.context->Render();
|
g_app.context->Render();
|
||||||
|
|
||||||
// Read pixels
|
|
||||||
auto pixels = fbo.readPixels();
|
auto pixels = fbo.readPixels();
|
||||||
fbo.destroy();
|
fbo.destroy();
|
||||||
|
|
||||||
SavePNG(dump_folder / "screenshot.png", pixels, window_width, window_height);
|
SavePNG(dump_folder / "screenshot.png", pixels, window_width, window_height);
|
||||||
|
|
||||||
// Restore default framebuffer
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
Rml::Shutdown();
|
Rml::Shutdown();
|
||||||
@@ -441,6 +230,7 @@ int main(const int argc, const char* argv[])
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch for file changes
|
||||||
const HANDLE hNotif = FindFirstChangeNotification(g_app.assets_path.c_str(),
|
const HANDLE hNotif = FindFirstChangeNotification(g_app.assets_path.c_str(),
|
||||||
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE);
|
TRUE, FILE_NOTIFY_CHANGE_LAST_WRITE);
|
||||||
|
|
||||||
@@ -469,6 +259,9 @@ int main(const int argc, const char* argv[])
|
|||||||
Backend::PresentFrame();
|
Backend::PresentFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete g_logging_interface;
|
||||||
|
delete g_file_interface;
|
||||||
|
|
||||||
Rml::Shutdown();
|
Rml::Shutdown();
|
||||||
Backend::Shutdown();
|
Backend::Shutdown();
|
||||||
|
|
||||||
|
|||||||
274
src/data_models.cpp
Normal file
274
src/data_models.cpp
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
#include "data_models.h"
|
||||||
|
#include <print>
|
||||||
|
|
||||||
|
// Global data instances
|
||||||
|
SettingsData g_settings;
|
||||||
|
PhoneData g_phone;
|
||||||
|
BrowserData g_browser;
|
||||||
|
std::vector<Conversation> g_conversations;
|
||||||
|
std::vector<Contact> g_contacts;
|
||||||
|
int g_selected_conversation = -1;
|
||||||
|
int g_selected_contact = -1;
|
||||||
|
|
||||||
|
// Data model handles
|
||||||
|
Rml::DataModelHandle g_settings_model;
|
||||||
|
Rml::DataModelHandle g_phone_model;
|
||||||
|
Rml::DataModelHandle g_messages_model;
|
||||||
|
Rml::DataModelHandle g_contacts_model;
|
||||||
|
Rml::DataModelHandle g_browser_model;
|
||||||
|
|
||||||
|
void initializeSampleData()
|
||||||
|
{
|
||||||
|
// Initialize contacts
|
||||||
|
g_contacts = {
|
||||||
|
{1, "Alice Johnson", "+1 (555) 123-4567", "alice@example.com", "#E91E63", "A"},
|
||||||
|
{2, "Andrew Smith", "+1 (555) 234-5678", "andrew@example.com", "#9C27B0", "A"},
|
||||||
|
{3, "Bob Williams", "+1 (555) 345-6789", "bob@example.com", "#2196F3", "B"},
|
||||||
|
{4, "Brian Davis", "+1 (555) 456-7890", "brian@example.com", "#00BCD4", "B"},
|
||||||
|
{5, "Carol Martinez", "+1 (555) 567-8901", "carol@example.com", "#4CAF50", "C"},
|
||||||
|
{6, "David Lee", "+1 (555) 678-9012", "david@example.com", "#FF9800", "D"},
|
||||||
|
{7, "John Wilson", "+1 (555) 789-0123", "john@example.com", "#F44336", "J"},
|
||||||
|
{8, "Mom", "+1 (555) 890-1234", "mom@example.com", "#673AB7", "M"},
|
||||||
|
{9, "Mike Brown", "+1 (555) 901-2345", "mike@example.com", "#3F51B5", "M"},
|
||||||
|
{10, "Sarah Taylor", "+1 (555) 012-3456", "sarah@example.com", "#009688", "S"}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize conversations
|
||||||
|
g_conversations = {
|
||||||
|
{1, "John Wilson", "#4CAF50", "Hey, are you coming to the party tonight?", "2:30 PM", 2, {
|
||||||
|
{"them", "Hey!", "2:25 PM"},
|
||||||
|
{"them", "What are you up to?", "2:26 PM"},
|
||||||
|
{"me", "Not much, just working", "2:27 PM"},
|
||||||
|
{"them", "Cool! There's a party at Mike's tonight", "2:28 PM"},
|
||||||
|
{"them", "Hey, are you coming to the party tonight?", "2:30 PM"}
|
||||||
|
}},
|
||||||
|
{2, "Mom", "#673AB7", "Don't forget to call your grandmother!", "1:15 PM", 0, {
|
||||||
|
{"them", "Hi sweetie!", "1:00 PM"},
|
||||||
|
{"me", "Hi Mom!", "1:05 PM"},
|
||||||
|
{"them", "How are you doing?", "1:10 PM"},
|
||||||
|
{"me", "I'm good, how are you?", "1:12 PM"},
|
||||||
|
{"them", "Don't forget to call your grandmother!", "1:15 PM"}
|
||||||
|
}},
|
||||||
|
{3, "Alice Johnson", "#E91E63", "Thanks for the help with the project!", "Yesterday", 0, {
|
||||||
|
{"me", "Here's the file you needed", "Yesterday"},
|
||||||
|
{"them", "Thanks for the help with the project!", "Yesterday"}
|
||||||
|
}},
|
||||||
|
{4, "Bob Williams", "#2196F3", "Did you see the game last night?", "Yesterday", 0, {
|
||||||
|
{"them", "Did you see the game last night?", "Yesterday"}
|
||||||
|
}},
|
||||||
|
{5, "Work Group", "#FF9800", "Sarah: Meeting moved to 3pm", "Mon", 0, {
|
||||||
|
{"Sarah", "Meeting moved to 3pm", "Mon"}
|
||||||
|
}},
|
||||||
|
{6, "Sarah Taylor", "#009688", "See you at the coffee shop!", "Sun", 0, {
|
||||||
|
{"them", "See you at the coffee shop!", "Sun"}
|
||||||
|
}},
|
||||||
|
{7, "David Lee", "#F44336", "Great talking to you!", "Sat", 0, {
|
||||||
|
{"them", "Great talking to you!", "Sat"}
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize browser tabs
|
||||||
|
g_browser.tabs = {
|
||||||
|
{"example.com", "Example Domain", false},
|
||||||
|
{"rmlui.github.io", "RmlUi Documentation", false},
|
||||||
|
{"github.com", "GitHub", false}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::println("Sample data initialized: {} contacts, {} conversations, {} browser tabs",
|
||||||
|
g_contacts.size(), g_conversations.size(), g_browser.tabs.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupDataModels(Rml::Context* context)
|
||||||
|
{
|
||||||
|
// Messages data model
|
||||||
|
if (auto constructor = context->CreateDataModel("messages"))
|
||||||
|
{
|
||||||
|
if (auto msg_handle = constructor.RegisterStruct<Message>())
|
||||||
|
{
|
||||||
|
msg_handle.RegisterMember("from", &Message::from);
|
||||||
|
msg_handle.RegisterMember("text", &Message::text);
|
||||||
|
msg_handle.RegisterMember("time", &Message::time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto conv_handle = constructor.RegisterStruct<Conversation>())
|
||||||
|
{
|
||||||
|
conv_handle.RegisterMember("id", &Conversation::id);
|
||||||
|
conv_handle.RegisterMember("name", &Conversation::name);
|
||||||
|
conv_handle.RegisterMember("color", &Conversation::color);
|
||||||
|
conv_handle.RegisterMember("last_message", &Conversation::last_message);
|
||||||
|
conv_handle.RegisterMember("time", &Conversation::time);
|
||||||
|
conv_handle.RegisterMember("unread", &Conversation::unread);
|
||||||
|
conv_handle.RegisterMember("messages", &Conversation::messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.RegisterArray<std::vector<Message>>();
|
||||||
|
constructor.RegisterArray<std::vector<Conversation>>();
|
||||||
|
|
||||||
|
constructor.Bind("conversations", &g_conversations);
|
||||||
|
constructor.Bind("selected_conversation", &g_selected_conversation);
|
||||||
|
|
||||||
|
constructor.BindEventCallback("select_conversation",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
if (!args.empty()) {
|
||||||
|
g_selected_conversation = args[0].Get<int>();
|
||||||
|
std::println("Selected conversation: {}", g_selected_conversation);
|
||||||
|
handle.DirtyVariable("selected_conversation");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g_messages_model = constructor.GetModelHandle();
|
||||||
|
std::println("Messages data model created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contacts data model
|
||||||
|
if (auto constructor = context->CreateDataModel("contacts"))
|
||||||
|
{
|
||||||
|
if (auto contact_handle = constructor.RegisterStruct<Contact>())
|
||||||
|
{
|
||||||
|
contact_handle.RegisterMember("id", &Contact::id);
|
||||||
|
contact_handle.RegisterMember("name", &Contact::name);
|
||||||
|
contact_handle.RegisterMember("phone", &Contact::phone);
|
||||||
|
contact_handle.RegisterMember("email", &Contact::email);
|
||||||
|
contact_handle.RegisterMember("color", &Contact::color);
|
||||||
|
contact_handle.RegisterMember("initial", &Contact::initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.RegisterArray<std::vector<Contact>>();
|
||||||
|
|
||||||
|
constructor.Bind("contacts", &g_contacts);
|
||||||
|
constructor.Bind("selected_contact", &g_selected_contact);
|
||||||
|
|
||||||
|
constructor.BindEventCallback("select_contact",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
if (!args.empty()) {
|
||||||
|
g_selected_contact = args[0].Get<int>();
|
||||||
|
std::println("Selected contact: {}", g_selected_contact);
|
||||||
|
handle.DirtyVariable("selected_contact");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g_contacts_model = constructor.GetModelHandle();
|
||||||
|
std::println("Contacts data model created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings data model
|
||||||
|
if (auto constructor = context->CreateDataModel("settings"))
|
||||||
|
{
|
||||||
|
constructor.Bind("wifi", &g_settings.wifi);
|
||||||
|
constructor.Bind("bluetooth", &g_settings.bluetooth);
|
||||||
|
constructor.Bind("airplane_mode", &g_settings.airplane_mode);
|
||||||
|
constructor.Bind("location", &g_settings.location);
|
||||||
|
constructor.Bind("dark_mode", &g_settings.dark_mode);
|
||||||
|
constructor.Bind("notifications", &g_settings.notifications);
|
||||||
|
constructor.Bind("do_not_disturb", &g_settings.do_not_disturb);
|
||||||
|
constructor.Bind("user_name", &g_settings.user_name);
|
||||||
|
constructor.Bind("user_email", &g_settings.user_email);
|
||||||
|
constructor.Bind("wifi_network", &g_settings.wifi_network);
|
||||||
|
constructor.Bind("battery_percent", &g_settings.battery_percent);
|
||||||
|
constructor.Bind("battery_remaining", &g_settings.battery_remaining);
|
||||||
|
constructor.Bind("storage_used", &g_settings.storage_used);
|
||||||
|
|
||||||
|
g_settings_model = constructor.GetModelHandle();
|
||||||
|
std::println("Settings data model created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phone data model
|
||||||
|
if (auto constructor = context->CreateDataModel("phone"))
|
||||||
|
{
|
||||||
|
constructor.Bind("dial_number", &g_phone.dial_number);
|
||||||
|
constructor.Bind("is_calling", &g_phone.is_calling);
|
||||||
|
constructor.Bind("call_contact", &g_phone.call_contact);
|
||||||
|
constructor.Bind("call_duration", &g_phone.call_duration);
|
||||||
|
|
||||||
|
constructor.BindEventCallback("dial_press",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
if (!args.empty()) {
|
||||||
|
g_phone.dial_number += args[0].Get<Rml::String>();
|
||||||
|
std::println("Dial: {}", g_phone.dial_number);
|
||||||
|
handle.DirtyVariable("dial_number");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor.BindEventCallback("dial_backspace",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
if (!g_phone.dial_number.empty()) {
|
||||||
|
g_phone.dial_number.pop_back();
|
||||||
|
handle.DirtyVariable("dial_number");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor.BindEventCallback("dial_clear",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
g_phone.dial_number.clear();
|
||||||
|
handle.DirtyVariable("dial_number");
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor.BindEventCallback("make_call",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
if (!g_phone.dial_number.empty()) {
|
||||||
|
g_phone.is_calling = true;
|
||||||
|
g_phone.call_contact = g_phone.dial_number;
|
||||||
|
std::println("Calling: {}", g_phone.call_contact);
|
||||||
|
handle.DirtyVariable("is_calling");
|
||||||
|
handle.DirtyVariable("call_contact");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor.BindEventCallback("end_call",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
g_phone.is_calling = false;
|
||||||
|
g_phone.call_contact = "";
|
||||||
|
g_phone.call_duration = "00:00";
|
||||||
|
handle.DirtyVariable("is_calling");
|
||||||
|
handle.DirtyVariable("call_contact");
|
||||||
|
handle.DirtyVariable("call_duration");
|
||||||
|
});
|
||||||
|
|
||||||
|
g_phone_model = constructor.GetModelHandle();
|
||||||
|
std::println("Phone data model created");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browser data model
|
||||||
|
if (auto constructor = context->CreateDataModel("browser"))
|
||||||
|
{
|
||||||
|
if (auto tab_handle = constructor.RegisterStruct<BrowserTab>())
|
||||||
|
{
|
||||||
|
tab_handle.RegisterMember("url", &BrowserTab::url);
|
||||||
|
tab_handle.RegisterMember("title", &BrowserTab::title);
|
||||||
|
tab_handle.RegisterMember("is_loading", &BrowserTab::is_loading);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.RegisterArray<std::vector<BrowserTab>>();
|
||||||
|
|
||||||
|
constructor.Bind("current_url", &g_browser.current_url);
|
||||||
|
constructor.Bind("page_title", &g_browser.page_title);
|
||||||
|
constructor.Bind("page_content", &g_browser.page_content);
|
||||||
|
constructor.Bind("tabs", &g_browser.tabs);
|
||||||
|
constructor.Bind("current_tab", &g_browser.current_tab);
|
||||||
|
constructor.Bind("can_go_back", &g_browser.can_go_back);
|
||||||
|
constructor.Bind("can_go_forward", &g_browser.can_go_forward);
|
||||||
|
|
||||||
|
constructor.BindEventCallback("navigate",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
if (!args.empty()) {
|
||||||
|
g_browser.current_url = args[0].Get<Rml::String>();
|
||||||
|
g_browser.page_title = "Loading...";
|
||||||
|
g_browser.can_go_back = true;
|
||||||
|
std::println("Navigating to: {}", g_browser.current_url);
|
||||||
|
handle.DirtyVariable("current_url");
|
||||||
|
handle.DirtyVariable("page_title");
|
||||||
|
handle.DirtyVariable("can_go_back");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor.BindEventCallback("refresh",
|
||||||
|
[](Rml::DataModelHandle handle, Rml::Event& event, const Rml::VariantList& args) {
|
||||||
|
std::println("Refreshing page");
|
||||||
|
});
|
||||||
|
|
||||||
|
g_browser_model = constructor.GetModelHandle();
|
||||||
|
std::println("Browser data model created");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::println("All data models initialized");
|
||||||
|
}
|
||||||
98
src/data_models.h
Normal file
98
src/data_models.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <RmlUi/Core.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Settings data model
|
||||||
|
struct SettingsData {
|
||||||
|
bool wifi = true;
|
||||||
|
bool bluetooth = false;
|
||||||
|
bool airplane_mode = false;
|
||||||
|
bool location = true;
|
||||||
|
bool dark_mode = true;
|
||||||
|
bool notifications = true;
|
||||||
|
bool do_not_disturb = false;
|
||||||
|
|
||||||
|
// User profile
|
||||||
|
Rml::String user_name = "User Name";
|
||||||
|
Rml::String user_email = "user@example.com";
|
||||||
|
|
||||||
|
// Device info
|
||||||
|
Rml::String wifi_network = "Home_Network";
|
||||||
|
int battery_percent = 85;
|
||||||
|
Rml::String battery_remaining = "About 12h remaining";
|
||||||
|
Rml::String storage_used = "45.2 GB used of 128 GB";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phone state data model
|
||||||
|
struct PhoneData {
|
||||||
|
Rml::String dial_number = "";
|
||||||
|
bool is_calling = false;
|
||||||
|
Rml::String call_contact = "";
|
||||||
|
Rml::String call_duration = "00:00";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Messages data model
|
||||||
|
struct Message {
|
||||||
|
Rml::String from;
|
||||||
|
Rml::String text;
|
||||||
|
Rml::String time;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Conversation {
|
||||||
|
int id;
|
||||||
|
Rml::String name;
|
||||||
|
Rml::String color;
|
||||||
|
Rml::String last_message;
|
||||||
|
Rml::String time;
|
||||||
|
int unread;
|
||||||
|
std::vector<Message> messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contact data model
|
||||||
|
struct Contact {
|
||||||
|
int id;
|
||||||
|
Rml::String name;
|
||||||
|
Rml::String phone;
|
||||||
|
Rml::String email;
|
||||||
|
Rml::String color;
|
||||||
|
Rml::String initial;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Browser data model
|
||||||
|
struct BrowserTab {
|
||||||
|
Rml::String url;
|
||||||
|
Rml::String title;
|
||||||
|
bool is_loading;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BrowserData {
|
||||||
|
Rml::String current_url = "example.com";
|
||||||
|
Rml::String page_title = "Example Domain";
|
||||||
|
Rml::String page_content = "This domain is for use in illustrative examples.";
|
||||||
|
std::vector<BrowserTab> tabs;
|
||||||
|
int current_tab = 0;
|
||||||
|
bool can_go_back = false;
|
||||||
|
bool can_go_forward = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global data instances
|
||||||
|
extern SettingsData g_settings;
|
||||||
|
extern PhoneData g_phone;
|
||||||
|
extern BrowserData g_browser;
|
||||||
|
extern std::vector<Conversation> g_conversations;
|
||||||
|
extern std::vector<Contact> g_contacts;
|
||||||
|
extern int g_selected_conversation;
|
||||||
|
extern int g_selected_contact;
|
||||||
|
|
||||||
|
// Data model handles
|
||||||
|
extern Rml::DataModelHandle g_settings_model;
|
||||||
|
extern Rml::DataModelHandle g_phone_model;
|
||||||
|
extern Rml::DataModelHandle g_messages_model;
|
||||||
|
extern Rml::DataModelHandle g_contacts_model;
|
||||||
|
extern Rml::DataModelHandle g_browser_model;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
void initializeSampleData();
|
||||||
|
void setupDataModels(Rml::Context* context);
|
||||||
82
src/system_interface.cpp
Normal file
82
src/system_interface.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#include "system_interface.h"
|
||||||
|
#include <print>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
// LoggingSystemInterface implementation
|
||||||
|
LoggingSystemInterface::LoggingSystemInterface(Rml::SystemInterface* backend)
|
||||||
|
: backend_(backend)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
double LoggingSystemInterface::GetElapsedTime()
|
||||||
|
{
|
||||||
|
return backend_->GetElapsedTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoggingSystemInterface::LogMessage(Rml::Log::Type type, const Rml::String& message)
|
||||||
|
{
|
||||||
|
const char* type_str = "";
|
||||||
|
switch (type) {
|
||||||
|
case Rml::Log::LT_ERROR: type_str = "ERROR"; break;
|
||||||
|
case Rml::Log::LT_WARNING: type_str = "WARNING"; break;
|
||||||
|
case Rml::Log::LT_INFO: type_str = "INFO"; break;
|
||||||
|
case Rml::Log::LT_DEBUG: type_str = "DEBUG"; break;
|
||||||
|
default: type_str = "LOG"; break;
|
||||||
|
}
|
||||||
|
std::println("[RmlUi {}] {}", type_str, message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingSystemInterface::JoinPath(Rml::String& translated_path, const Rml::String& document_path, const Rml::String& path)
|
||||||
|
{
|
||||||
|
// Fix paths where colon was converted to pipe (D| -> D:)
|
||||||
|
std::string fixed_path = path;
|
||||||
|
if (fixed_path.length() >= 2 && fixed_path[1] == '|') {
|
||||||
|
fixed_path[1] = ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fixed_doc = document_path;
|
||||||
|
if (fixed_doc.length() >= 2 && fixed_doc[1] == '|') {
|
||||||
|
fixed_doc[1] = ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use std::filesystem to join paths properly and normalize (resolve ..)
|
||||||
|
std::filesystem::path doc_dir = std::filesystem::path(fixed_doc).parent_path();
|
||||||
|
std::filesystem::path result = (doc_dir / fixed_path).lexically_normal();
|
||||||
|
translated_path = result.generic_string();
|
||||||
|
std::println("JoinPath: {} + {} -> {}", fixed_doc, fixed_path, translated_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowsFileInterface implementation
|
||||||
|
Rml::FileHandle WindowsFileInterface::Open(const Rml::String& path)
|
||||||
|
{
|
||||||
|
// Fix paths where colon was converted to pipe (D| -> D:)
|
||||||
|
std::string fixed_path = path;
|
||||||
|
if (fixed_path.length() >= 2 && fixed_path[1] == '|') {
|
||||||
|
fixed_path[1] = ':';
|
||||||
|
}
|
||||||
|
std::println("FileInterface::Open: {} -> {}", path, fixed_path);
|
||||||
|
|
||||||
|
FILE* fp = fopen(fixed_path.c_str(), "rb");
|
||||||
|
return reinterpret_cast<Rml::FileHandle>(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowsFileInterface::Close(Rml::FileHandle file)
|
||||||
|
{
|
||||||
|
fclose(reinterpret_cast<FILE*>(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t WindowsFileInterface::Read(void* buffer, size_t size, Rml::FileHandle file)
|
||||||
|
{
|
||||||
|
return fread(buffer, 1, size, reinterpret_cast<FILE*>(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowsFileInterface::Seek(Rml::FileHandle file, long offset, int origin)
|
||||||
|
{
|
||||||
|
return fseek(reinterpret_cast<FILE*>(file), offset, origin) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t WindowsFileInterface::Tell(Rml::FileHandle file)
|
||||||
|
{
|
||||||
|
return ftell(reinterpret_cast<FILE*>(file));
|
||||||
|
}
|
||||||
27
src/system_interface.h
Normal file
27
src/system_interface.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <RmlUi/Core.h>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
// Custom system interface to enable logging and fix Windows paths
|
||||||
|
class LoggingSystemInterface : public Rml::SystemInterface {
|
||||||
|
public:
|
||||||
|
LoggingSystemInterface(Rml::SystemInterface* backend);
|
||||||
|
|
||||||
|
double GetElapsedTime() override;
|
||||||
|
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override;
|
||||||
|
void JoinPath(Rml::String& translated_path, const Rml::String& document_path, const Rml::String& path) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Rml::SystemInterface* backend_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom file interface to fix Windows path issues (D| -> D:)
|
||||||
|
class WindowsFileInterface : public Rml::FileInterface {
|
||||||
|
public:
|
||||||
|
Rml::FileHandle Open(const Rml::String& path) override;
|
||||||
|
void Close(Rml::FileHandle file) override;
|
||||||
|
size_t Read(void* buffer, size_t size, Rml::FileHandle file) override;
|
||||||
|
bool Seek(Rml::FileHandle file, long offset, int origin) override;
|
||||||
|
size_t Tell(Rml::FileHandle file) override;
|
||||||
|
};
|
||||||
125
src/utils.cpp
Normal file
125
src/utils.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
#include <print>
|
||||||
|
#include <cstring>
|
||||||
|
#include <RmlUi_Include_GL3.h>
|
||||||
|
#include <png.h>
|
||||||
|
|
||||||
|
void load_fonts(const std::filesystem::path& dir)
|
||||||
|
{
|
||||||
|
for (const auto& file : std::filesystem::directory_iterator(dir))
|
||||||
|
{
|
||||||
|
if (file.path().extension() == ".ttf")
|
||||||
|
{
|
||||||
|
Rml::LoadFontFace(file.path().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpElementTree(Rml::Element* element, std::ofstream& out, int depth)
|
||||||
|
{
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
std::string indent(depth * 2, ' ');
|
||||||
|
std::string id = element->GetId().empty() ? "" : "#" + element->GetId();
|
||||||
|
|
||||||
|
out << indent << element->GetTagName() << id
|
||||||
|
<< " @ (" << element->GetAbsoluteLeft() << ", " << element->GetAbsoluteTop() << ") "
|
||||||
|
<< element->GetOffsetWidth() << "x" << element->GetOffsetHeight() << "\n";
|
||||||
|
|
||||||
|
for (int i = 0; i < element->GetNumChildren(); i++)
|
||||||
|
DumpElementTree(element->GetChild(i), out, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SavePNG(const std::filesystem::path& path, const std::vector<unsigned char>& pixels, int width, int height)
|
||||||
|
{
|
||||||
|
// Flip vertically (OpenGL origin is bottom-left)
|
||||||
|
std::vector<unsigned char> flipped(width * height * 3);
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
std::memcpy(&flipped[y * width * 3],
|
||||||
|
&pixels[(height - 1 - y) * width * 3],
|
||||||
|
width * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* fp = fopen(path.string().c_str(), "wb");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
std::println("Failed to open {} for writing", path.string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
png_init_io(png, fp);
|
||||||
|
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB,
|
||||||
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
png_write_info(png, info);
|
||||||
|
|
||||||
|
std::vector<png_bytep> rows(height);
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
rows[y] = const_cast<png_bytep>(&flipped[y * width * 3]);
|
||||||
|
png_write_image(png, rows.data());
|
||||||
|
png_write_end(png, nullptr);
|
||||||
|
|
||||||
|
png_destroy_write_struct(&png, &info);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
std::println("Screenshot saved to: {}", path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OffscreenFBO::create(int w, int h)
|
||||||
|
{
|
||||||
|
width = w;
|
||||||
|
height = h;
|
||||||
|
|
||||||
|
// Create framebuffer
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
|
||||||
|
// Create color texture
|
||||||
|
glGenTextures(1, &texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||||
|
|
||||||
|
// Create depth/stencil renderbuffer
|
||||||
|
glGenRenderbuffers(1, &depth_rbo);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, depth_rbo);
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth_rbo);
|
||||||
|
|
||||||
|
// Check completeness
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||||
|
{
|
||||||
|
std::println("Failed to create offscreen framebuffer");
|
||||||
|
destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenFBO::destroy()
|
||||||
|
{
|
||||||
|
if (texture) glDeleteTextures(1, &texture);
|
||||||
|
if (depth_rbo) glDeleteRenderbuffers(1, &depth_rbo);
|
||||||
|
if (fbo) glDeleteFramebuffers(1, &fbo);
|
||||||
|
fbo = texture = depth_rbo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OffscreenFBO::bind()
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glViewport(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> OffscreenFBO::readPixels()
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> pixels(width * height * 3);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
29
src/utils.h
Normal file
29
src/utils.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <RmlUi/Core.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Load all TTF fonts from a directory
|
||||||
|
void load_fonts(const std::filesystem::path& dir);
|
||||||
|
|
||||||
|
// Dump element tree to a file for debugging
|
||||||
|
void DumpElementTree(Rml::Element* element, std::ofstream& out, int depth = 0);
|
||||||
|
|
||||||
|
// Save pixels to a PNG file
|
||||||
|
void SavePNG(const std::filesystem::path& path, const std::vector<unsigned char>& pixels, int width, int height);
|
||||||
|
|
||||||
|
// Offscreen framebuffer for screenshot capture
|
||||||
|
struct OffscreenFBO {
|
||||||
|
unsigned int fbo = 0;
|
||||||
|
unsigned int texture = 0;
|
||||||
|
unsigned int depth_rbo = 0;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
bool create(int w, int h);
|
||||||
|
void destroy();
|
||||||
|
void bind();
|
||||||
|
std::vector<unsigned char> readPixels();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user