Expand description
egui_mobius - A thread-aware signal/slot framework for egui applications with reactive capabilities
egui_mobius provides a robust signal/slot architecture inspired by Qt, designed specifically for egui applications. It enables clean separation between UI and background operations through type-safe message passing and thread management.
§Key Features
- Thread-aware Slot System: Each slot maintains its own thread for true hybrid sync/async operation
- Type-safe Message Passing: Strongly typed signals and slots ensure compile-time correctness
- Signal-Slot Architecture: Qt-inspired design pattern for decoupled communication
Value<T>State Management: Thread-safe state container with proper synchronization- Background Operations: Clean thread separation for non-blocking UI
- Flexible Dispatching: Support for both synchronous and asynchronous message dispatching with automatic thread management
- Reactive System: Value change notifications and auto-updating computed values through a signal registry
§Examples
§Basic Signal-Slot Usage
use egui_mobius::factory::create_signal_slot;
use egui_mobius::types::Value;
use egui_mobius::{Signal, Slot};
use std::collections::VecDeque;
// Define message types for type-safe communication
#[derive(Clone)]
enum UiEvent {
AddTask { id: u32, description: String },
CompleteTask { id: u32 },
}
#[derive(Clone)]
enum BackendResponse {
TaskAdded { id: u32 },
TaskCompleted { id: u32 },
}
// Application state
struct AppState {
tasks: Value<VecDeque<String>>,
needs_refresh: Value<bool>,
ui_signal: Signal<UiEvent>,
ui_slot: Slot<BackendResponse>,
}
impl AppState {
fn new(
ui_signal: Signal<UiEvent>,
mut ui_slot: Slot<BackendResponse>,
) -> Self {
// Create shared state
let tasks = Value::new(VecDeque::new());
let needs_refresh = Value::new(false);
// Set up UI response handler
let needs_refresh_clone = Value::clone(&needs_refresh);
ui_slot.start(move |response| {
*needs_refresh_clone.lock().unwrap() = true;
match response {
BackendResponse::TaskAdded { id } => {
println!("UI: Task {} added", id);
}
BackendResponse::TaskCompleted { id } => {
println!("UI: Task {} completed", id);
}
}
});
Self {
tasks,
needs_refresh,
ui_signal,
ui_slot,
}
}
fn add_task(&self, description: String) {
self.ui_signal.send(UiEvent::AddTask {
id: 1,
description,
}).unwrap();
}
}
fn start_backend_thread(
tasks: Value<VecDeque<String>>,
needs_refresh: Value<bool>,
mut backend_slot: Slot<UiEvent>,
backend_signal: Signal<BackendResponse>,
) {
backend_slot.start(move |event| {
let mut task_list = tasks.lock().unwrap();
match event {
UiEvent::AddTask { id, description } => {
task_list.push_back(description);
*needs_refresh.lock().unwrap() = true;
backend_signal.send(BackendResponse::TaskAdded { id }).unwrap();
}
UiEvent::CompleteTask { id } => {
if !task_list.is_empty() {
task_list.pop_front();
*needs_refresh.lock().unwrap() = true;
backend_signal.send(BackendResponse::TaskCompleted { id }).unwrap();
}
}
}
});
}
fn main() {
// Create communication channels
let (ui_signal, backend_slot) = create_signal_slot::<UiEvent>();
let (backend_signal, ui_slot) = create_signal_slot::<BackendResponse>();
// Create application state
let app = AppState::new(ui_signal.clone(), ui_slot);
// Start backend processing thread
start_backend_thread(
Value::clone(&app.tasks),
Value::clone(&app.needs_refresh),
backend_slot,
backend_signal,
);
// Use the application
app.add_task("Learn egui_mobius".to_string());
}§Async Message Dispatching
use egui_mobius::dispatching::AsyncDispatcher;
use egui_mobius::factory::create_signal_slot;
use std::time::Duration;
// Define message types
#[derive(Clone)]
enum Request {
FetchWeather(String), // city name
FetchTime(String), // timezone
}
#[derive(Clone)]
enum Response {
Weather { city: String, temp: f32 },
Time { zone: String, time: String },
}
// Create bidirectional channels
let (signal_to_dispatcher, slot_from_ui) = create_signal_slot::<Request>();
let (signal_to_ui, mut slot_from_dispatcher) = create_signal_slot::<Response>();
// Create and configure the dispatcher
let dispatcher = AsyncDispatcher::<Request, Response>::new();
let signal_to_ui = signal_to_ui.clone();
// Attach async handler
dispatcher.attach_async(slot_from_ui, signal_to_ui, |request| async move {
match request {
Request::FetchWeather(city) => {
// Simulate API call
tokio::time::sleep(Duration::from_millis(100)).await;
Response::Weather {
city,
temp: 22.5,
}
}
Request::FetchTime(zone) => {
tokio::time::sleep(Duration::from_millis(50)).await;
Response::Time {
zone,
time: "10:30 AM".to_string(),
}
}
}
});
// UI can send requests
signal_to_dispatcher.send(Request::FetchWeather("London".to_string())).unwrap();
// UI can handle responses
slot_from_dispatcher.start(|response| {
match response {
Response::Weather { city, temp } => {
println!("Temperature in {}: {}°C", city, temp);
}
Response::Time { zone, time } => {
println!("Time in {}: {}", zone, time);
}
}
});§Reactive System
The reactive system, including SignalRegistry, ValueExt, and Derived types,
is available in the separate egui_mobius_reactive crate. This includes:
- Value change notifications through the
ValueExttrait - Auto-updating computed values with
Derived - Signal registry for managing reactive dependencies
- Thread-safe reactive state management
See the egui_mobius_reactive crate documentation for comprehensive examples
and usage patterns.
§Module Overview
The framework consists of several key modules:
signals: Signal type for sending messagesslot: Slot type for receiving and processing messagesfactory: Utilities for creating signal-slot pairstypes: Core types likeValue<T>for state managementdispatching: Signal dispatching and routing system
The reactive system functionality is available in the separate egui_mobius_reactive crate.
Re-exports§
pub use dispatching::AsyncDispatcher;pub use dispatching::Dispatcher;pub use dispatching::SignalDispatcher;pub use factory::create_signal_slot;pub use runtime::EventRoute;pub use runtime::MobiusHandle;pub use runtime::MobiusRuntime;pub use signals::Signal;pub use types::Edge;pub use types::Value;pub use slot::Slot;
Modules§
- dispatching
- Dispatcher module for egui_mobius
- factory
- Factory module for creating signal-slot pairs in egui_mobius.
- runtime
- signals
- The Signal module provides a non-threaded mpsc (multi-producer, single-consumer) channel sender.
- slot
- The Slot module provides a self-contained threaded or async channel receiver.
- types
- The types module provides core types for implementing the design patterns and architecture of egui_mobius.