mirror of
https://codeberg.org/waydeer/layer-shika.git
synced 2025-12-12 16:35:56 +00:00
feat: add event loop integration examples
This commit is contained in:
parent
26a994a4b8
commit
8c9e5fb92c
9 changed files with 565 additions and 16 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -150,6 +150,12 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
|
@ -1007,6 +1013,16 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-loop-examples"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"env_logger",
|
||||||
|
"layer-shika",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ members = [
|
||||||
"examples/simple-bar",
|
"examples/simple-bar",
|
||||||
"examples/multi-surface",
|
"examples/multi-surface",
|
||||||
"examples/declarative-config",
|
"examples/declarative-config",
|
||||||
|
"examples/event-loop",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
|
|
@ -20,22 +20,6 @@ cd examples/simple-bar
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building All Examples
|
|
||||||
|
|
||||||
From the workspace root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --workspace
|
|
||||||
```
|
|
||||||
|
|
||||||
Or build a specific example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build -p simple-bar
|
|
||||||
cargo build -p multi-surface
|
|
||||||
cargo build -p declarative-config
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example Progression
|
## Example Progression
|
||||||
|
|
||||||
**Recommended learning path:**
|
**Recommended learning path:**
|
||||||
|
|
@ -43,6 +27,7 @@ cargo build -p declarative-config
|
||||||
1. **simple-bar** - Start here to understand the basics
|
1. **simple-bar** - Start here to understand the basics
|
||||||
2. **multi-surface** - Learn about multiple surfaces and callbacks
|
2. **multi-surface** - Learn about multiple surfaces and callbacks
|
||||||
3. **declarative-config** - See the alternative configuration approach
|
3. **declarative-config** - See the alternative configuration approach
|
||||||
|
4. **event-loop** - Explore event loop integration with timers and channels
|
||||||
|
|
||||||
## Common Patterns
|
## Common Patterns
|
||||||
|
|
||||||
|
|
|
||||||
27
examples/event-loop/Cargo.toml
Normal file
27
examples/event-loop/Cargo.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "event-loop-examples"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "timer"
|
||||||
|
path = "src/timer.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "channel"
|
||||||
|
path = "src/channel.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "custom-source"
|
||||||
|
path = "src/custom_source.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
layer-shika = { path = "../.." }
|
||||||
|
anyhow = "1.0"
|
||||||
|
env_logger = "0.11.7"
|
||||||
|
log.workspace = true
|
||||||
72
examples/event-loop/README.md
Normal file
72
examples/event-loop/README.md
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Event Loop Integration Examples
|
||||||
|
|
||||||
|
This directory contains examples demonstrating how to integrate custom event sources
|
||||||
|
with layer-shika's event loop.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Timer (`timer.rs`)
|
||||||
|
|
||||||
|
Demonstrates how to add periodic timers to update UI elements (e.g., a clock).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --bin timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channel (`channel.rs`)
|
||||||
|
|
||||||
|
Shows how to use channels for communication between background threads and the UI.
|
||||||
|
Useful for async operations, network requests, or any off-main-thread work.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --bin channel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Event Source (`custom_source.rs`)
|
||||||
|
|
||||||
|
Demonstrates adding custom file descriptor-based event sources for I/O monitoring.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --bin custom-source
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
All examples use `shell.event_loop_handle()` to get a handle that allows registering
|
||||||
|
event sources with the main event loop. The callbacks receive `&mut AppState` which
|
||||||
|
provides access to window components and output information.
|
||||||
|
|
||||||
|
### Timer Pattern
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
handle.add_timer(Duration::from_secs(1), |_instant, app_state| {
|
||||||
|
// Update UI components here
|
||||||
|
TimeoutAction::ToInstant(Instant::now() + Duration::from_secs(1))
|
||||||
|
})?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Channel Pattern
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
let (_token, sender) = handle.add_channel(|message: MyMessage, app_state| {
|
||||||
|
// Handle messages from background threads
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Send from another thread
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
sender.send(MyMessage::Update("data".into())).unwrap();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Descriptor Pattern
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use layer_shika::calloop::{Generic, Interest, Mode};
|
||||||
|
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
handle.add_fd(file, Interest::READ, Mode::Level, |app_state| {
|
||||||
|
// Handle I/O readiness
|
||||||
|
})?;
|
||||||
|
```
|
||||||
158
examples/event-loop/src/channel.rs
Normal file
158
examples/event-loop/src/channel.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use layer_shika::calloop::TimeoutAction;
|
||||||
|
use layer_shika::calloop::channel::Sender;
|
||||||
|
use layer_shika::prelude::*;
|
||||||
|
use layer_shika::slint_interpreter::Value;
|
||||||
|
|
||||||
|
enum UiMessage {
|
||||||
|
UpdateStatus(String),
|
||||||
|
IncrementCounter(i32),
|
||||||
|
BackgroundTaskComplete(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Info)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
log::info!("Starting channel example");
|
||||||
|
|
||||||
|
let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/demo.slint");
|
||||||
|
|
||||||
|
let mut shell = Shell::from_file(&ui_path)
|
||||||
|
.surface("Main")
|
||||||
|
.size(400, 200)
|
||||||
|
.layer(Layer::Top)
|
||||||
|
.namespace("channel-example")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
|
||||||
|
let (_token, sender) = handle.add_channel(|message: UiMessage, app_state| {
|
||||||
|
for window in app_state.all_outputs() {
|
||||||
|
let component = window.component_instance();
|
||||||
|
|
||||||
|
match &message {
|
||||||
|
UiMessage::UpdateStatus(status) => {
|
||||||
|
if let Err(e) =
|
||||||
|
component.set_property("status", Value::String(status.clone().into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set status: {e}");
|
||||||
|
}
|
||||||
|
log::info!("Status updated: {}", status);
|
||||||
|
}
|
||||||
|
UiMessage::IncrementCounter(delta) => {
|
||||||
|
if let Ok(Value::Number(current)) = component.get_property("counter") {
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let new_value = current as i32 + delta;
|
||||||
|
if let Err(e) =
|
||||||
|
component.set_property("counter", Value::Number(f64::from(new_value)))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set counter: {e}");
|
||||||
|
}
|
||||||
|
log::debug!("Counter: {}", new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiMessage::BackgroundTaskComplete(result) => {
|
||||||
|
if let Err(e) = component
|
||||||
|
.set_property("status", Value::String(format!("Done: {result}").into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set status: {e}");
|
||||||
|
}
|
||||||
|
log::info!("Background task complete: {}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
handle.add_timer(Duration::from_secs(1), |_instant, app_state| {
|
||||||
|
let time_str = current_time_string();
|
||||||
|
|
||||||
|
for window in app_state.all_outputs() {
|
||||||
|
if let Err(e) = window
|
||||||
|
.component_instance()
|
||||||
|
.set_property("time", Value::String(time_str.clone().into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set time property: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeoutAction::ToInstant(Instant::now() + Duration::from_secs(1))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
spawn_background_worker(sender.clone());
|
||||||
|
spawn_counter_worker(sender);
|
||||||
|
|
||||||
|
shell.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_background_worker(sender: Sender<UiMessage>) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let tasks = vec![
|
||||||
|
("Loading configuration...", 500),
|
||||||
|
("Connecting to services...", 800),
|
||||||
|
("Fetching data...", 1200),
|
||||||
|
("Processing results...", 600),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (status, delay_ms) in tasks {
|
||||||
|
if sender
|
||||||
|
.send(UiMessage::UpdateStatus(status.to_string()))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(delay_ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
if sender
|
||||||
|
.send(UiMessage::BackgroundTaskComplete(
|
||||||
|
"All tasks finished".to_string(),
|
||||||
|
))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
if sender
|
||||||
|
.send(UiMessage::UpdateStatus(
|
||||||
|
"Heartbeat from background".to_string(),
|
||||||
|
))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_counter_worker(sender: Sender<UiMessage>) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_millis(50));
|
||||||
|
if sender.send(UiMessage::IncrementCounter(1)).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_time_string() -> String {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let duration = now.duration_since(UNIX_EPOCH).unwrap_or_default();
|
||||||
|
let secs = duration.as_secs();
|
||||||
|
|
||||||
|
let hours = (secs / 3600) % 24;
|
||||||
|
let minutes = (secs / 60) % 60;
|
||||||
|
let seconds = secs % 60;
|
||||||
|
|
||||||
|
format!("{hours:02}:{minutes:02}:{seconds:02}")
|
||||||
|
}
|
||||||
140
examples/event-loop/src/custom_source.rs
Normal file
140
examples/event-loop/src/custom_source.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufReader, Write};
|
||||||
|
use std::os::unix::io::{AsFd, BorrowedFd, FromRawFd, IntoRawFd};
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use layer_shika::calloop::{Interest, Mode, TimeoutAction};
|
||||||
|
use layer_shika::prelude::*;
|
||||||
|
use layer_shika::slint_interpreter::Value;
|
||||||
|
|
||||||
|
struct ReadablePipe {
|
||||||
|
reader: BufReader<File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFd for ReadablePipe {
|
||||||
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||||
|
self.reader.get_ref().as_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Info)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
log::info!("Starting custom event source example");
|
||||||
|
|
||||||
|
let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/demo.slint");
|
||||||
|
|
||||||
|
let mut shell = Shell::from_file(&ui_path)
|
||||||
|
.surface("Main")
|
||||||
|
.size(400, 200)
|
||||||
|
.layer(Layer::Top)
|
||||||
|
.namespace("custom-source-example")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let (mut write_end, read_end) = create_pipe()?;
|
||||||
|
|
||||||
|
let readable = ReadablePipe {
|
||||||
|
reader: BufReader::new(read_end),
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
|
||||||
|
let counter = Cell::new(0i32);
|
||||||
|
|
||||||
|
handle.add_fd(readable, Interest::READ, Mode::Level, move |app_state| {
|
||||||
|
log::debug!("Pipe readable event triggered");
|
||||||
|
|
||||||
|
let count = counter.get() + 1;
|
||||||
|
counter.set(count);
|
||||||
|
|
||||||
|
let status_text = format!("Events received: {count}");
|
||||||
|
|
||||||
|
for window in app_state.all_outputs() {
|
||||||
|
let component = window.component_instance();
|
||||||
|
if let Err(e) = component.set_property("counter", Value::Number(f64::from(count))) {
|
||||||
|
log::error!("Failed to set counter: {e}");
|
||||||
|
}
|
||||||
|
if let Err(e) =
|
||||||
|
component.set_property("status", Value::String(status_text.clone().into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set status: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut event_num = 0;
|
||||||
|
loop {
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
event_num += 1;
|
||||||
|
let message = format!("event-{event_num}\n");
|
||||||
|
if write_end.write_all(message.as_bytes()).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if write_end.flush().is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log::debug!("Wrote event {} to pipe", event_num);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.add_timer(Duration::from_secs(1), |_instant, app_state| {
|
||||||
|
let time_str = current_time_string();
|
||||||
|
|
||||||
|
for window in app_state.all_outputs() {
|
||||||
|
if let Err(e) = window
|
||||||
|
.component_instance()
|
||||||
|
.set_property("time", Value::String(time_str.clone().into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set time property: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeoutAction::ToInstant(Instant::now() + Duration::from_secs(1))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
shell.with_surface("Main", |component| {
|
||||||
|
if let Err(e) =
|
||||||
|
component.set_property("status", Value::String("Waiting for pipe events...".into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set status: {e}");
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
shell.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pipe() -> Result<(File, File)> {
|
||||||
|
let (read_stream, write_stream) = UnixStream::pair()?;
|
||||||
|
|
||||||
|
read_stream.set_nonblocking(true)?;
|
||||||
|
write_stream.set_nonblocking(true)?;
|
||||||
|
|
||||||
|
Ok(unsafe {
|
||||||
|
(
|
||||||
|
FromRawFd::from_raw_fd(write_stream.into_raw_fd()),
|
||||||
|
FromRawFd::from_raw_fd(read_stream.into_raw_fd()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_time_string() -> String {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let duration = now.duration_since(UNIX_EPOCH).unwrap_or_default();
|
||||||
|
let secs = duration.as_secs();
|
||||||
|
|
||||||
|
let hours = (secs / 3600) % 24;
|
||||||
|
let minutes = (secs / 60) % 60;
|
||||||
|
let seconds = secs % 60;
|
||||||
|
|
||||||
|
format!("{hours:02}:{minutes:02}:{seconds:02}")
|
||||||
|
}
|
||||||
83
examples/event-loop/src/timer.rs
Normal file
83
examples/event-loop/src/timer.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use layer_shika::calloop::TimeoutAction;
|
||||||
|
use layer_shika::prelude::*;
|
||||||
|
use layer_shika::slint_interpreter::Value;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Info)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
log::info!("Starting timer example");
|
||||||
|
|
||||||
|
let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/demo.slint");
|
||||||
|
|
||||||
|
let mut shell = Shell::from_file(&ui_path)
|
||||||
|
.surface("Main")
|
||||||
|
.size(400, 200)
|
||||||
|
.layer(Layer::Top)
|
||||||
|
.namespace("timer-example")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let handle = shell.event_loop_handle();
|
||||||
|
|
||||||
|
handle.add_timer(Duration::ZERO, |_instant, app_state| {
|
||||||
|
let time_str = current_time_string();
|
||||||
|
|
||||||
|
for window in app_state.all_outputs() {
|
||||||
|
if let Err(e) = window
|
||||||
|
.component_instance()
|
||||||
|
.set_property("time", Value::String(time_str.clone().into()))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set time property: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("Timer tick: {}", time_str);
|
||||||
|
|
||||||
|
TimeoutAction::ToInstant(Instant::now() + Duration::from_secs(1))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let counter = Cell::new(0i32);
|
||||||
|
handle.add_timer(Duration::ZERO, move |_instant, app_state| {
|
||||||
|
let count = counter.get() + 1;
|
||||||
|
counter.set(count);
|
||||||
|
|
||||||
|
for window in app_state.all_outputs() {
|
||||||
|
if let Err(e) = window
|
||||||
|
.component_instance()
|
||||||
|
.set_property("counter", Value::Number(f64::from(count)))
|
||||||
|
{
|
||||||
|
log::error!("Failed to set counter property: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeoutAction::ToInstant(Instant::now() + Duration::from_millis(100))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
shell.with_surface("Main", |component| {
|
||||||
|
if let Err(e) = component.set_property("status", Value::String("Timer running...".into())) {
|
||||||
|
log::error!("Failed to set status property: {e}");
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
shell.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_time_string() -> String {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let duration = now.duration_since(UNIX_EPOCH).unwrap_or_default();
|
||||||
|
let secs = duration.as_secs();
|
||||||
|
|
||||||
|
let hours = (secs / 3600) % 24;
|
||||||
|
let minutes = (secs / 60) % 60;
|
||||||
|
let seconds = secs % 60;
|
||||||
|
|
||||||
|
format!("{hours:02}:{minutes:02}:{seconds:02}")
|
||||||
|
}
|
||||||
67
examples/event-loop/ui/demo.slint
Normal file
67
examples/event-loop/ui/demo.slint
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
|
||||||
|
export component Main inherits Window {
|
||||||
|
in property <string> time: "00:00:00";
|
||||||
|
in property <int> counter: 0;
|
||||||
|
in property <string> status: "Waiting...";
|
||||||
|
|
||||||
|
background: #2d2d2d;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
padding: 20px;
|
||||||
|
spacing: 20px;
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Event Loop Demo";
|
||||||
|
font-size: 24px;
|
||||||
|
color: #ffffff;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: status;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffb74d;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalLayout {
|
||||||
|
alignment: center;
|
||||||
|
spacing: 40px;
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
Text {
|
||||||
|
text: "Time";
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888888;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: time;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #4fc3f7;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalLayout {
|
||||||
|
alignment: center;
|
||||||
|
Text {
|
||||||
|
text: "Counter";
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888888;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: counter;
|
||||||
|
font-size: 32px;
|
||||||
|
color: #81c784;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue