Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This is just the changelog for the core `egui` crate. Every crate in this reposi
* [`epaint` changelog](crates/epaint/CHANGELOG.md)
* [`egui-winit` changelog](crates/egui-winit/CHANGELOG.md)
* [`egui-wgpu` changelog](crates/egui-wgpu/CHANGELOG.md)
* [`egui-ios` changelog](crates/egui-ios/CHANGELOG.md)
* [`egui_kittest` changelog](crates/egui_kittest/CHANGELOG.md)
* [`egui_glow` changelog](crates/egui_glow/CHANGELOG.md)
* [`ecolor` changelog](crates/ecolor/CHANGELOG.md)
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/egui_demo_lib",
"crates/egui_extras",
"crates/egui_glow",
"crates/egui-ios",
"crates/egui_kittest",
"crates/egui-wgpu",
"crates/egui-winit",
Expand Down Expand Up @@ -65,6 +66,7 @@ egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-feature
egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false }
egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false }
egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false }
egui-ios = { version = "0.1.0", path = "crates/egui-ios", default-features = false }
egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.33.3", path = "crates/eframe", default-features = false }

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ egui aims to be the easiest-to-use Rust GUI library, and the simplest way to mak

egui can be used anywhere you can draw textured triangles, which means you can easily integrate it into your game engine of choice.

[`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe) is the official egui framework, which supports writing apps for Web, Linux, Mac, Windows, and Android.
[`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe) is the official egui framework, which supports writing apps for Web, Linux, Mac, Windows, Android, and iOS.


## Example
Expand Down
14 changes: 14 additions & 0 deletions crates/egui-ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Changelog for egui-ios
All notable changes to the `egui-ios` crate will be noted in this file.

This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.


## 0.33.3 - 2025-12-11
### ⭐ Added
* Initial release of `egui-ios` crate
* `InputEvent` - FFI type for touch, keyboard, and lifecycle events from Swift to egui
* `OutputState` - FFI type for cursor, keyboard, and IME state from egui to Swift
* `CursorIcon` - Cursor icons mapped to iOS equivalents
* Swift bindings generated via swift-bridge
25 changes: 25 additions & 0 deletions crates/egui-ios/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "egui-ios"
version = "0.1.0"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "iOS FFI bindings for egui via swift-bridge"
repository = "https://github.com/emilk/egui"
keywords = ["egui", "ios", "swift", "ffi"]
categories = ["gui", "rendering"]
readme = "README.md"

[dependencies]
egui.workspace = true
swift-bridge = "0.1"

[build-dependencies]
swift-bridge-build = "0.1"

# Override workspace lints - FFI code requires unsafe
[lints.rust]
unsafe_code = "allow"

[features]
default = []
98 changes: 98 additions & 0 deletions crates/egui-ios/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# egui-ios

iOS FFI bindings for [egui](https://github.com/emilk/egui) via [swift-bridge](https://github.com/chinedufn/swift-bridge).

**Minimum iOS version: 13.0**

This crate provides Swift-compatible types for embedding egui in iOS apps:

- `InputEvent` - Touch, keyboard, and lifecycle events from Swift to egui
- `OutputState` - Cursor, keyboard, and IME state from egui to Swift
- `CursorIcon` - Cursor icons mapped to iOS equivalents

## Usage

Add to your `Cargo.toml`:

```toml
[dependencies]
egui-ios = "0.1"
```

In your Rust code:

```rust
use egui_ios::{InputEvent, OutputState, CursorIcon};

// Convert input events to egui events
let egui_events: Vec<egui::Event> = input_events
.into_iter()
.filter_map(|e| e.into_egui_event())
.collect();

// Run egui frame
let full_output = ctx.run(raw_input, |ctx| {
// Your UI code
});

// Create output state for Swift
let output = OutputState::with_keyboard_state(
full_output.platform_output.cursor_icon.into(),
ctx.wants_keyboard_input(),
full_output.platform_output.ime.as_ref().map(|ime| ime.rect),
);
```

## Swift Integration

Pre-generated Swift bindings are included in `generated/`. Add these files to your Xcode project:

- `generated/SwiftBridgeCore.swift` - swift-bridge runtime
- `generated/SwiftBridgeCore.h` - C header for runtime
- `generated/egui-ios/egui-ios.swift` - Generated Swift bindings
- `generated/egui-ios/egui-ios.h` - C header for bindings

The generated files are committed to the repository so Swift developers don't need to run the Rust build step.

### Integration Overview

A typical SwiftUI integration involves:
1. Creating a `MTKView` for Metal rendering
2. Forwarding touch events via `InputEvent::from_*` methods
3. Running egui each frame and checking `OutputState` for keyboard/IME state
4. Using a native `UITextField` overlay for text input with autocomplete/autocorrect

## Input Events

| Event | Description |
|-------|-------------|
| `from_pointer_moved(x, y)` | Touch/pointer position |
| `from_left_mouse_down(x, y, pressed)` | Primary touch |
| `from_mouse_wheel(x, y)` | Scroll gesture |
| `from_text_commit(text)` | Committed text after autocomplete |
| `from_ime_preedit(text)` | IME composition text |
| `from_virtual_key(code, pressed)` | Special keys (backspace, enter, etc.) |
| `from_scene_phase_changed(phase)` | iOS scene lifecycle |

## Virtual Key Codes

| Code | Key |
|------|-----|
| 0 | Backspace |
| 1 | Enter |
| 2 | Tab |
| 3 | Escape |
| 4-7 | Arrow keys (up, down, left, right) |

## Output State

Check `OutputState` after each frame:

- `wants_keyboard()` - Show/hide iOS keyboard
- `has_ime_rect()` / `get_ime_rect_*()` - Keyboard positioning
- `get_cursor_icon()` - Cursor for pointer interactions

## Known Limitations

- **Clipboard**: The `arboard` crate doesn't support iOS yet, so `egui-winit` disables system clipboard on iOS. Apps should implement clipboard via `UIPasteboard` in Swift and relay it through input events.
- **Safe area**: Uses a custom objc2 bridge in `egui-winit` until winit 0.31+ adds native `Window::safe_area()` support.
14 changes: 14 additions & 0 deletions crates/egui-ios/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::path::PathBuf;

fn main() {
let out_dir = PathBuf::from("./generated");

let bridges = vec!["src/ffi.rs"];

for bridge in &bridges {
println!("cargo:rerun-if-changed={bridge}");
}

swift_bridge_build::parse_bridges(bridges)
.write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"));
}
164 changes: 164 additions & 0 deletions crates/egui-ios/generated/SwiftBridgeCore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#include <stdint.h>
#include <stdbool.h>
typedef struct RustStr { uint8_t* const start; uintptr_t len; } RustStr;
typedef struct __private__FfiSlice { void* const start; uintptr_t len; } __private__FfiSlice;
void* __swift_bridge__null_pointer(void);


typedef struct __private__OptionU8 { uint8_t val; bool is_some; } __private__OptionU8;
typedef struct __private__OptionI8 { int8_t val; bool is_some; } __private__OptionI8;
typedef struct __private__OptionU16 { uint16_t val; bool is_some; } __private__OptionU16;
typedef struct __private__OptionI16 { int16_t val; bool is_some; } __private__OptionI16;
typedef struct __private__OptionU32 { uint32_t val; bool is_some; } __private__OptionU32;
typedef struct __private__OptionI32 { int32_t val; bool is_some; } __private__OptionI32;
typedef struct __private__OptionU64 { uint64_t val; bool is_some; } __private__OptionU64;
typedef struct __private__OptionI64 { int64_t val; bool is_some; } __private__OptionI64;
typedef struct __private__OptionUsize { uintptr_t val; bool is_some; } __private__OptionUsize;
typedef struct __private__OptionIsize { intptr_t val; bool is_some; } __private__OptionIsize;
typedef struct __private__OptionF32 { float val; bool is_some; } __private__OptionF32;
typedef struct __private__OptionF64 { double val; bool is_some; } __private__OptionF64;
typedef struct __private__OptionBool { bool val; bool is_some; } __private__OptionBool;

void* __swift_bridge__$Vec_u8$new();
void __swift_bridge__$Vec_u8$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_u8$len(void* const vec);
void __swift_bridge__$Vec_u8$push(void* const vec, uint8_t val);
__private__OptionU8 __swift_bridge__$Vec_u8$pop(void* const vec);
__private__OptionU8 __swift_bridge__$Vec_u8$get(void* const vec, uintptr_t index);
__private__OptionU8 __swift_bridge__$Vec_u8$get_mut(void* const vec, uintptr_t index);
uint8_t const * __swift_bridge__$Vec_u8$as_ptr(void* const vec);

void* __swift_bridge__$Vec_u16$new();
void __swift_bridge__$Vec_u16$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_u16$len(void* const vec);
void __swift_bridge__$Vec_u16$push(void* const vec, uint16_t val);
__private__OptionU16 __swift_bridge__$Vec_u16$pop(void* const vec);
__private__OptionU16 __swift_bridge__$Vec_u16$get(void* const vec, uintptr_t index);
__private__OptionU16 __swift_bridge__$Vec_u16$get_mut(void* const vec, uintptr_t index);
uint16_t const * __swift_bridge__$Vec_u16$as_ptr(void* const vec);

void* __swift_bridge__$Vec_u32$new();
void __swift_bridge__$Vec_u32$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_u32$len(void* const vec);
void __swift_bridge__$Vec_u32$push(void* const vec, uint32_t val);
__private__OptionU32 __swift_bridge__$Vec_u32$pop(void* const vec);
__private__OptionU32 __swift_bridge__$Vec_u32$get(void* const vec, uintptr_t index);
__private__OptionU32 __swift_bridge__$Vec_u32$get_mut(void* const vec, uintptr_t index);
uint32_t const * __swift_bridge__$Vec_u32$as_ptr(void* const vec);

void* __swift_bridge__$Vec_u64$new();
void __swift_bridge__$Vec_u64$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_u64$len(void* const vec);
void __swift_bridge__$Vec_u64$push(void* const vec, uint64_t val);
__private__OptionU64 __swift_bridge__$Vec_u64$pop(void* const vec);
__private__OptionU64 __swift_bridge__$Vec_u64$get(void* const vec, uintptr_t index);
__private__OptionU64 __swift_bridge__$Vec_u64$get_mut(void* const vec, uintptr_t index);
uint64_t const * __swift_bridge__$Vec_u64$as_ptr(void* const vec);

void* __swift_bridge__$Vec_usize$new();
void __swift_bridge__$Vec_usize$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_usize$len(void* const vec);
void __swift_bridge__$Vec_usize$push(void* const vec, uintptr_t val);
__private__OptionUsize __swift_bridge__$Vec_usize$pop(void* const vec);
__private__OptionUsize __swift_bridge__$Vec_usize$get(void* const vec, uintptr_t index);
__private__OptionUsize __swift_bridge__$Vec_usize$get_mut(void* const vec, uintptr_t index);
uintptr_t const * __swift_bridge__$Vec_usize$as_ptr(void* const vec);

void* __swift_bridge__$Vec_i8$new();
void __swift_bridge__$Vec_i8$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_i8$len(void* const vec);
void __swift_bridge__$Vec_i8$push(void* const vec, int8_t val);
__private__OptionI8 __swift_bridge__$Vec_i8$pop(void* const vec);
__private__OptionI8 __swift_bridge__$Vec_i8$get(void* const vec, uintptr_t index);
__private__OptionI8 __swift_bridge__$Vec_i8$get_mut(void* const vec, uintptr_t index);
int8_t const * __swift_bridge__$Vec_i8$as_ptr(void* const vec);

void* __swift_bridge__$Vec_i16$new();
void __swift_bridge__$Vec_i16$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_i16$len(void* const vec);
void __swift_bridge__$Vec_i16$push(void* const vec, int16_t val);
__private__OptionI16 __swift_bridge__$Vec_i16$pop(void* const vec);
__private__OptionI16 __swift_bridge__$Vec_i16$get(void* const vec, uintptr_t index);
__private__OptionI16 __swift_bridge__$Vec_i16$get_mut(void* const vec, uintptr_t index);
int16_t const * __swift_bridge__$Vec_i16$as_ptr(void* const vec);

void* __swift_bridge__$Vec_i32$new();
void __swift_bridge__$Vec_i32$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_i32$len(void* const vec);
void __swift_bridge__$Vec_i32$push(void* const vec, int32_t val);
__private__OptionI32 __swift_bridge__$Vec_i32$pop(void* const vec);
__private__OptionI32 __swift_bridge__$Vec_i32$get(void* const vec, uintptr_t index);
__private__OptionI32 __swift_bridge__$Vec_i32$get_mut(void* const vec, uintptr_t index);
int32_t const * __swift_bridge__$Vec_i32$as_ptr(void* const vec);

void* __swift_bridge__$Vec_i64$new();
void __swift_bridge__$Vec_i64$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_i64$len(void* const vec);
void __swift_bridge__$Vec_i64$push(void* const vec, int64_t val);
__private__OptionI64 __swift_bridge__$Vec_i64$pop(void* const vec);
__private__OptionI64 __swift_bridge__$Vec_i64$get(void* const vec, uintptr_t index);
__private__OptionI64 __swift_bridge__$Vec_i64$get_mut(void* const vec, uintptr_t index);
int64_t const * __swift_bridge__$Vec_i64$as_ptr(void* const vec);

void* __swift_bridge__$Vec_isize$new();
void __swift_bridge__$Vec_isize$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_isize$len(void* const vec);
void __swift_bridge__$Vec_isize$push(void* const vec, intptr_t val);
__private__OptionIsize __swift_bridge__$Vec_isize$pop(void* const vec);
__private__OptionIsize __swift_bridge__$Vec_isize$get(void* const vec, uintptr_t index);
__private__OptionIsize __swift_bridge__$Vec_isize$get_mut(void* const vec, uintptr_t index);
intptr_t const * __swift_bridge__$Vec_isize$as_ptr(void* const vec);

void* __swift_bridge__$Vec_bool$new();
void __swift_bridge__$Vec_bool$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_bool$len(void* const vec);
void __swift_bridge__$Vec_bool$push(void* const vec, bool val);
__private__OptionBool __swift_bridge__$Vec_bool$pop(void* const vec);
__private__OptionBool __swift_bridge__$Vec_bool$get(void* const vec, uintptr_t index);
__private__OptionBool __swift_bridge__$Vec_bool$get_mut(void* const vec, uintptr_t index);
bool const * __swift_bridge__$Vec_bool$as_ptr(void* const vec);

void* __swift_bridge__$Vec_f32$new();
void __swift_bridge__$Vec_f32$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_f32$len(void* const vec);
void __swift_bridge__$Vec_f32$push(void* const vec, float val);
__private__OptionF32 __swift_bridge__$Vec_f32$pop(void* const vec);
__private__OptionF32 __swift_bridge__$Vec_f32$get(void* const vec, uintptr_t index);
__private__OptionF32 __swift_bridge__$Vec_f32$get_mut(void* const vec, uintptr_t index);
float const * __swift_bridge__$Vec_f32$as_ptr(void* const vec);

void* __swift_bridge__$Vec_f64$new();
void __swift_bridge__$Vec_f64$_free(void* const vec);
uintptr_t __swift_bridge__$Vec_f64$len(void* const vec);
void __swift_bridge__$Vec_f64$push(void* const vec, double val);
__private__OptionF64 __swift_bridge__$Vec_f64$pop(void* const vec);
__private__OptionF64 __swift_bridge__$Vec_f64$get(void* const vec, uintptr_t index);
__private__OptionF64 __swift_bridge__$Vec_f64$get_mut(void* const vec, uintptr_t index);
double const * __swift_bridge__$Vec_f64$as_ptr(void* const vec);

#include <stdint.h>
typedef struct RustString RustString;
void __swift_bridge__$RustString$_free(void* self);

void* __swift_bridge__$Vec_RustString$new(void);
void __swift_bridge__$Vec_RustString$drop(void* vec_ptr);
void __swift_bridge__$Vec_RustString$push(void* vec_ptr, void* item_ptr);
void* __swift_bridge__$Vec_RustString$pop(void* vec_ptr);
void* __swift_bridge__$Vec_RustString$get(void* vec_ptr, uintptr_t index);
void* __swift_bridge__$Vec_RustString$get_mut(void* vec_ptr, uintptr_t index);
uintptr_t __swift_bridge__$Vec_RustString$len(void* vec_ptr);
void* __swift_bridge__$Vec_RustString$as_ptr(void* vec_ptr);

void* __swift_bridge__$RustString$new(void);
void* __swift_bridge__$RustString$new_with_str(struct RustStr str);
uintptr_t __swift_bridge__$RustString$len(void* self);
struct RustStr __swift_bridge__$RustString$as_str(void* self);
struct RustStr __swift_bridge__$RustString$trim(void* self);
bool __swift_bridge__$RustStr$partial_eq(struct RustStr lhs, struct RustStr rhs);


void __swift_bridge__$call_boxed_fn_once_no_args_no_return(void* boxed_fnonce);
void __swift_bridge__$free_boxed_fn_once_no_args_no_return(void* boxed_fnonce);


struct __private__ResultPtrAndPtr { bool is_ok; void* ok_or_err; };
Loading