Winio is a single-threaded asynchronous GUI runtime.
The GUI part is powered by native backends, and it is compatible with compio.
All IO requests could be issued in the same thread as GUI, without blocking the user interface!
Read the example and learn more!
| Backend | Light | Dark |
|---|---|---|
| Win32 | ![]() |
![]() |
| WinUI 3 | ![]() |
![]() |
| Qt 6 | ![]() |
![]() |
| GTK 4 | ![]() |
![]() |
| AppKit | ![]() |
![]() |
| UIKit (Mac Catalyst) | ![]() |
![]() |
| UIKit (iOS) | ![]() |
![]() |
| Android View | ![]() |
![]() |
Winio follows ELM-like design, inspired by yew and relm4.
The application starts with a root Component:
use winio::prelude::*;
struct MainModel {
window: Child<Window>,
}
enum MainMessage {
Noop,
Close,
}
impl Component for MainModel {
type Error = Error;
type Event = ();
type Init<'a> = ();
type Message = MainMessage;
async fn init(_init: Self::Init<'_>, _sender: &ComponentSender<Self>) -> Result<Self> {
// create & initialize the window
init! {
window: Window = (()) => {
text: "Example",
size: Size::new(800.0, 600.0),
}
}
window.show()?;
Ok(Self { window })
}
async fn start(&mut self, sender: &ComponentSender<Self>) -> ! {
// listen to events
start! {
sender, default: MainMessage::Noop,
self.window => {
WindowEvent::Close => MainMessage::Close,
}
}
}
async fn update_children(&mut self) -> Result<bool> {
// update the window
update_children!(self.window)
}
async fn update(&mut self, message: Self::Message, sender: &ComponentSender<Self>) -> Result<bool> {
// deal with custom messages
match message {
MainMessage::Noop => Ok(false),
MainMessage::Close => {
// the root component output stops the application
sender.output(());
// need not to call `render`
Ok(false)
}
}
}
fn render(&mut self, _sender: &ComponentSender<Self>) -> Result<()> {
let csize = self.window.client_size()?;
// adjust layout and draw widgets here
Ok(())
}
fn render_children(&mut self) -> Result<()> {
self.window.render()
}
}It is recommended to set the lib name to "main" for convenience.
[lib]
name = "main"All platforms except Android start with main. You should add the code below to main.rs:
#[cfg(not(target_os = "android"))]
fn main() -> winio::Result<()> {
use main::MainModel;
use winio::prelude::*;
App::builder()
.name("rs.compio.winio.example")
.build()?
.block_on(MainModel::run_until_event(()))
}
#[cfg(target_os = "android")]
fn main() {
unreachable!("Android entry point is `android_main` in `android.rs`")
}[!NOTE] iOS notes
WindowEvent::Closewill never be emitted, and the application will exit if the window (Mac Catalyst) or the app (iOS) closes.block_ondoesn't return in that case.
The Android entry point is the android_main method:
#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {
let app = App::builder()
.android_app(app)
.build()
.expect("cannot create app");
app.spawn(|| async {
if let Err(e) = MainModel::run_until_event(()).await {
compio_log::error!("App error: {e:?}");
}
})
}[!NOTE] Android notes
android_mainmight be called multiple times, but the lifetime of each calling don't overlap.android_mainruns on a dedicate thread, while all code ofwinioexecute on the main thread.- You have to do the following to create a complete Android project with
winio.
To integrate the winio app into an Android app with id "rs.compio.winio.example", the main activity should inherit rs.compio.winio.Activity:
package rs.compio.winio.example;
import rs.compio.winio.Activity;
public class MainActivity extends Activity {
static {
System.loadLibrary("main");
}
}Add the following to the <activity> section of AndroidManifest.xml:
<meta-data android:name="android.app.lib_name" android:value="main" />Put the project folder "android" beside the "src" folder of the rust project, and modify the "dependencyResolutionManagement" part of android/settings.gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = findWinioUiAndroidProject()
metadataSources.artifact()
}
}
}
// ...
import groovy.json.JsonSlurper
String findWinioUiAndroidProject() {
def dependencyText = providers.exec {
it.workingDir = new File("../")
commandLine("cargo", "metadata", "--format-version", "1", "--manifest-path", "Cargo.toml")
}.standardOutput.asText.get()
def dependencyJson = new JsonSlurper().parseText(dependencyText)
def manifestPath = file(dependencyJson.packages.find { it.name == "winio-ui-android" }.manifest_path)
return new File(manifestPath.parentFile, "maven").path
}and gradle/libs.versions.toml
[libraries]
# ...
winio = { module = "compio:winio", version = "latest.release" }and android/app/build.gradle
dependencies {
// ...
implementation libs.winio
}
[
Debug : null,
Profile: '--release',
Release: '--release'
].each {
def taskPostfix = it.key
def profileMode = it.value
tasks.configureEach { task ->
if (task.name == "assemble$taskPostfix") {
task.dependsOn "cargoBuild$taskPostfix"
}
}
tasks.register("cargoBuild$taskPostfix", Exec) {
workingDir "../.."
environment 'CARGO_TERM_COLOR', 'always'
def cmdArgs = [
'cargo', 'ndk',
'-o', 'android/app/src/main/jniLibs',
'-t', 'arm64-v8a',
'-t', 'armeabi-v7a',
'-t', 'x86_64',
'rustc',
'--crate-type', 'cdylib', '--lib',
]
if (profileMode != null) {
cmdArgs.add(profileMode)
}
commandLine cmdArgs
}
}














