orby

Native MoonBit application and window host.

moonbit
windowing
win32
gtk
moon add Nanaloveyuki/orby@0.1.0-beta.5
Download zip
Version
0.1.0-beta.5
License
Apache-2.0
Last updated
2 days ago
Downloads
174

Dependencies

README

#orby

Orby is a native MoonBit application and window host for Win32 and GTK3/GDK. It owns windows and their UI event loop through the common Nanaloveyuki/orby package, then exposes embedding hosts for native consumers such as MoonView.

Nanaloveyuki/orby is not compatible with Tao, winit, or Tauri.

#API

Import Nanaloveyuki/orby; its App, EventLoop, ActiveApp, Window, events, monitor types, and WebViewHost are the cross-platform public API. The windows and linux packages are backend implementation details and are not application-facing APIs.

All Orby calls and callbacks belong to the UI thread. Workers can use EventLoop::proxy to submit copied byte messages; Orby delivers them through App::proxy_message on the UI thread.

Only one EventLoop may be active per process. Create another only after the previous run_app returns or its ExternalAppLoop has terminated.

App::started may raise AppError::StartupFailed. For asynchronous setup or runtime failures, call ActiveApp::fail(reason) from a UI callback. The loop stops, invokes App::exiting once, and EventLoop::run_app raises AppError::RuntimeFailed. Destroy a child runtime such as MoonView before its parent Window; Orby does not reverse application-owned teardown ordering.

#Usage

Import the root package and keep the created window until it is destroyed:

import {
"Nanaloveyuki/orby",
}

struct Example {
mut window : @orby.Window?
}

pub impl @orby.App for Example with fn started(self, app) {
self.window = Some(
try! app.create_window(@orby.WindowOptions::new(title="Orby example")),
)
}

pub impl @orby.App for Example with fn window_event(self, _app, _id, event) {
match event {
@orby.WindowEvent::CloseRequested =>
match self.window {
Some(window) => window.destroy()
None => ()
}
_ => ()
}
}

fn main raise {
let event_loop = @orby.EventLoop::new()
ignore(event_loop.run_app({ window: None }))
}

#Status

The 0.1.0-beta.0 pre-release targets a small application lifecycle, native windows, resize/DPI/focus/redraw events, and WebView-ready host containers. MoonView integration is validated on both supported platforms in CI. Public API changes remain possible before the stable 0.1.0 release.

CloseRequested is application-controlled: call Window::destroy from the event handler to accept it, or do nothing to cancel it. request_close uses the same path for programmatic closure. Destroying the final window ends the event loop automatically; ActiveApp::exit_with_code can set a process result. Calls on a destroyed Window are safe no-ops; use is_destroyed before retaining or reusing a window handle across lifecycle callbacks.

Commands on a destroyed window are no-ops. Queries, webview_host, and WebViewHost::native_handle require a live native window and raise WindowError::Destroyed. Destroy the MoonView instance before its Orby window.

Window Size values are physical client-area pixels. Use LogicalSize with a window's scale_factor for device-independent layout, and use inner_size / set_inner_size when sizing native content. set_outer_position is a request that may be ignored by a Wayland compositor.

Window::focus restores, shows, and requests activation for a live top-level window. Platform foreground policy remains authoritative.

Keyboard and pointer input arrive through WindowEvent. NativeKeyEvent.code is intentionally backend-native, while TextInput carries printable Unicode text and modifiers use Shift, Control, Alt, and Meta. IME composition, touch, and raw device events remain outside the current API.

#Documentation

  • Development: local setup, project requirements, and validation.
  • Root package guide: complete consumer API, lifecycle, window, input, display, and WebView-host documentation.
  • MoonView integration: versioned desktop host setup, lifecycle, resize, failure, and teardown requirements.
  • Contributing: pull request process.
  • Releasing: version and package release checklist.

#Orby Root Package

Nanaloveyuki/orby is the consumer-facing package for native MoonBit desktop applications. It creates and owns Win32 or GTK3/GDK windows, dispatches their events on the UI thread, and provides a native child host for embedding a runtime such as MoonView.

Import this package rather than Nanaloveyuki/orby/windows or Nanaloveyuki/orby/linux; those packages are backend implementation details.

#Platform and Thread Model

Orby currently supports Windows and Linux with GTK3/GDK. Create the EventLoop, create windows, and call every Window, WebViewHost, and ActiveApp method from the same UI thread. Workers can use EventLoop::proxy or ActiveApp::proxy to submit Bytes; App::proxy_message receives each message on the UI thread in FIFO order.

The proxy copies messages into a native queue. A message is limited to 1 MiB and the queue to 8 MiB; post returns MessageTooLarge, QueueFull, or Closed instead of blocking a worker.

EventLoop::new raises InitError when the native host cannot initialize. On Windows this requires an STA UI thread; on Linux it requires a graphical GTK3 session. Only one EventLoop may be active in a process; construct the next one only after run_app returns or ExternalAppLoop::terminate completes.

#Minimal Application

Keep each created Window in application state. A close request is only a request: destroy the window from window_event to accept it.

import {
"Nanaloveyuki/orby",
}

struct Example {
mut window : @orby.Window?
}

pub impl @orby.App for Example with fn started(self, app) {
self.window = Some(
try! app.create_window(
@orby.WindowOptions::new(
title="Orby example",
size=@orby.Size::new(width=960, height=640),
),
),
)
}

pub impl @orby.App for Example with fn window_event(self, _app, _id, event) {
match event {
@orby.WindowEvent::CloseRequested =>
match self.window {
Some(window) => window.destroy()
None => ()
}
_ => ()
}
}

fn main raise {
let loop = @orby.EventLoop::new()
ignore(loop.run_app({ window: None }))
}

Destroying the final native window exits the event loop. Call ActiveApp::exit or exit_with_code when application state, rather than a window, decides termination.

#Lifecycle and Failures

App receives callbacks in this order:

  1. started runs after native callback installation and can create windows.
  2. window_event receives per-window activity; about_to_wait runs after a native event batch.
  3. proxy_message receives worker-submitted byte messages on the UI thread.
  4. exiting runs exactly once after native loop cleanup.

Use AppError::StartupFailed(reason) from started when setup cannot continue. EventLoop::run_app finishes native cleanup, invokes exiting, and then raises the same startup error.

For a failure discovered asynchronously in a UI callback, call ActiveApp::fail(reason). The first reason wins, new application callbacks are suppressed, and run_app raises AppError::RuntimeFailed(reason) after calling exiting.

pub impl @orby.App for Example with fn about_to_wait(self, app) {
if startup_work_failed() {
app.fail("initial runtime setup failed")
}
}

fn run_example() raise {
let loop = @orby.EventLoop::new()
let app = { window: None }
try ignore(loop.run_app(app)) catch {
@orby.AppError::StartupFailed(reason) => println("startup: \{reason}")
@orby.AppError::RuntimeFailed(reason) => println("runtime: \{reason}")
}
}

Do not use a blocking operation from about_to_wait, a window callback, or any other UI-thread callback.

#Windows, Geometry, and Displays

WindowOptions configures title, physical client size, visibility, and resizability at creation. Size is always physical client pixels; LogicalSize::to_physical and Size::to_logical convert with an explicit, validated scale factor.

Live windows provide title, visibility, resizability, decoration, minimization, maximization, fullscreen, client-size constraints, redraw, close requests, and outer-position requests. A Wayland compositor may ignore an outer-position request. focus requests that a live window is restored, shown, and activated; the window manager may still limit foreground activation. set_fullscreen_on accepts a MonitorId from the current monitor snapshot.

EventLoop::available_monitors, primary_monitor, and monitor expose current display snapshots. A Window also has current_monitor and scale_factor queries.

Commands on a destroyed Window are no-ops. Queries and host acquisition are checked: inner_size, scale_factor, fullscreen/minimized/maximized state, current_monitor, webview_host, and WebViewHost::native_handle raise WindowError::Destroyed after destruction. create_window can raise WindowError::CreationFailed; set_fullscreen_on can raise WindowError::MonitorUnavailable.

#Events and Input

WindowEvent reports close and destruction, resize, scale-factor changes, focus, redraw requests, native key input, printable text input, pointer motion, pointer enter/leave, mouse buttons, and smooth wheel deltas.

NativeKeyEvent.code intentionally stays backend-native. Use TextInput for printable text. IME composition, touch, raw device input, cursor control, and drag-and-drop are not part of the current API.

#WebView Hosts

Call Window::webview_host while the window is live, then pass WebViewHost::native_handle to a child runtime that accepts a native parent handle. Resize the child in response to WindowEvent::Resized and destroy the child runtime before calling Window::destroy.

Orby owns neither MoonView configuration nor its asynchronous WebView event contract. This release resolves the published Nanaloveyuki/moonview@0.1.0-beta.3 package, which in turn resolves AJNI 0.2.0. Follow the MoonView integration guide for creation, event, resize, resource-limit, failure, and teardown handling.

Orby has no Android backend. Android hosts should use MoonView's Android API directly rather than treating an Orby WebViewHost as an Android surface.

#Public API Map

  • EventLoop initializes the host, runs App, and queries displays.
  • ActiveApp creates windows, controls loop scheduling, exits, or reports a checked runtime failure.
  • Window owns one native window; WebViewHost exposes its live child host.
  • WindowOptions, Size, LogicalSize, Position, Monitor, and MonitorId describe creation and display state.
  • WindowEvent, NativeKeyEvent, Modifiers, MouseButton, and ScrollDelta describe input and native window activity.
  • InitError, GeometryError, WindowError, and AppError describe checked failures.

#
App

pub(open) trait App {
fn started(Self, ActiveApp) -> Unit raise AppError = _
fn window_event(Self, ActiveApp, WindowId, WindowEvent) -> Unit = _
fn proxy_message(Self, ActiveApp, Bytes) -> Unit = _
fn about_to_wait(Self, ActiveApp) -> Unit = _
fn exiting(Self, ActiveApp) -> Unit = _
}

Application callbacks run only on Orby's UI thread.

#
AppError

pub(all) suberror AppError {
StartupFailed(String)
RuntimeFailed(String)
} derive(
Debug
)

#
GeometryError

pub(all) suberror GeometryError {
InvalidScaleFactor(Double)
} derive(
Debug
)

#
InitError

pub(all) suberror InitError {
AlreadyActive
UnsupportedHost(String)
PlatformFailure(String)
} derive(
Debug
)

#
WindowError

pub(all) suberror WindowError {
CreationFailed(String)
MonitorUnavailable(MonitorId)
Destroyed
} derive(
Debug
)

#
ActiveApp

pub struct ActiveApp {
// private fields
} derive(
Debug
)

#
ActiveApp::create_window

fn ActiveApp::create_window(self : ActiveApp, options : WindowOptions) -> Window raise WindowError

#
ActiveApp::exit

fn ActiveApp::exit(self : ActiveApp) -> Unit

#
ActiveApp::exit_with_code

fn ActiveApp::exit_with_code(self : ActiveApp, code : Int) -> Unit

#
ActiveApp::fail

fn ActiveApp::fail(self : ActiveApp, reason : String) -> Unit

Requests a checked application failure from a UI callback. The first reason wins. Callers must destroy child runtimes before their parent windows.

#
ActiveApp::proxy

fn ActiveApp::proxy(self : ActiveApp) -> EventLoopProxy

Returns a sender associated with this running application.

#
ActiveApp::set_control_flow

fn ActiveApp::set_control_flow(self : ActiveApp, control_flow : ControlFlow) -> Unit

#
ControlFlow

pub(all) enum ControlFlow {
Poll
Wait
} derive(Eq,
Debug
)

#
EventLoop

pub struct EventLoop {
// private fields
} derive(
Debug
)

#
EventLoop::available_monitors

fn EventLoop::available_monitors(self : EventLoop) -> Array[Monitor]

#
EventLoop::monitor

fn EventLoop::monitor(self : EventLoop, id : MonitorId) -> Monitor?

#
EventLoop::new

fn EventLoop::new() -> EventLoop raise InitError

#
EventLoop::primary_monitor

fn EventLoop::primary_monitor(self : EventLoop) -> Monitor?

#
EventLoop::proxy

fn EventLoop::proxy(self : EventLoop) -> EventLoopProxy

Returns a sender that can be moved to a worker thread.

Only byte messages cross the thread boundary. Their handlers still run on Orby's UI thread through App::proxy_message.

#
EventLoop::run_app

fn[A : App] EventLoop::run_app(self : EventLoop, app : A) -> Int raise AppError

#
EventLoop::start_external_app

fn[A : App] EventLoop::start_external_app(self : EventLoop, app : A) -> ExternalAppLoop[A] raise AppError

Starts an application without taking ownership of the native event loop.

Call poll repeatedly from the embedding event loop, then call terminate exactly once after the embedding runtime has stopped. This is intended for integrations such as moonbitlang/async's external event loop support.

#
EventLoopProxy

pub struct EventLoopProxy {
// private fields
} derive(
Debug
)

A thread-safe sender for byte messages delivered to App::proxy_message.

The native queue copies every message, so callers may release the source bytes immediately. It accepts at most 1 MiB per message and 8 MiB in total.

#
EventLoopProxy::is_closed

fn EventLoopProxy::is_closed(self : EventLoopProxy) -> Bool

Returns whether this event-loop proxy can no longer accept messages.

#
EventLoopProxy::post

fn EventLoopProxy::post(self : EventLoopProxy, message : Bytes) -> Result[Unit, ProxyError]

Copies a byte message into the native queue and wakes the UI event loop.

A successful submission is delivered in FIFO order. Delivery stops when the event loop exits; callers must handle Closed as a normal shutdown result.

#
ExternalAppLoop

pub struct ExternalAppLoop[A] {
// private fields
}

A manually pumped application loop for integration with a foreign runtime.

poll and terminate must run on Orby's UI thread. The callback returned by wakeup_callback_for_foreign_thread is the sole exception: it is safe to invoke from a foreign thread because it calls directly into the native backend without accessing MoonBit-managed application state.

#
ExternalAppLoop::poll

fn[A : App] ExternalAppLoop::poll(self : ExternalAppLoop[A], timeout? : Int) -> ExternalPoll raise AppError

Pumps pending native events, then waits for no longer than timeout milliseconds. Omitting timeout waits indefinitely. Exited(code) means native cleanup and App::exiting have already run.

#
ExternalAppLoop::terminate

fn[A : App] ExternalAppLoop::terminate(self : ExternalAppLoop[A]) -> Int raise AppError

Releases native event-loop state and invokes the application's exiting callback. It is idempotent so cleanup paths can safely call it after an initialization or runtime failure.

#
ExternalAppLoop::wakeup_callback_for_foreign_thread

fn[A] ExternalAppLoop::wakeup_callback_for_foreign_thread(self : ExternalAppLoop[A]) -> FuncRef[() -> Unit]

Returns a wakeup callback suitable for a native foreign thread.

The callback does not inspect application state and only invokes the native backend wake primitive. Do not wrap it in another MoonBit callback that captures MoonBit-managed values before passing it to a foreign thread.

#
ExternalPoll

pub(all) enum ExternalPoll {
Continue
Exited(Int)
} derive(Eq,
Debug
)

Result of one external event-loop pump.

#
InputState

pub(all) enum InputState {
Pressed
Released
} derive(Eq,
Debug
)

#
LogicalSize

pub(all) struct LogicalSize {
width : Double
height : Double
} derive(Eq,
Debug
)

#
LogicalSize::new

fn LogicalSize::new(width~ : Double, height~ : Double) -> LogicalSize

#
LogicalSize::to_physical

fn LogicalSize::to_physical(self : LogicalSize, scale_factor : Double) -> Size raise GeometryError

#
Modifiers

pub(all) struct Modifiers {
shift : Bool
control : Bool
alt : Bool
meta : Bool
} derive(Eq,
Debug
)

#
Modifiers::new

fn Modifiers::new(shift? : Bool, control? : Bool, alt? : Bool, meta? : Bool) -> Modifiers

#
Monitor

pub(all) struct Monitor {
id : MonitorId
x : Int
y : Int
width : Int
height : Int
work_x : Int
work_y : Int
work_width : Int
work_height : Int
scale_factor : Double
} derive(Eq,
Debug
)

#
Monitor::new

fn Monitor::new(id~ : MonitorId, x~ : Int, y~ : Int, width~ : Int, height~ : Int, work_x~ : Int, work_y~ : Int, work_width~ : Int, work_height~ : Int, scale_factor~ : Double) -> Monitor

#
MonitorId

pub struct MonitorId {
// private fields
} derive(Eq,
Debug
)

#
MonitorId::from_index

fn MonitorId::from_index(index : Int) -> MonitorId?

#
MonitorId::index

fn MonitorId::index(self : MonitorId) -> Int

#
MouseButton

pub(all) enum MouseButton {
Left
Right
Middle
Back
Forward
Other(Int)
} derive(Eq,
Debug
)

#
NativeKeyEvent

pub(all) struct NativeKeyEvent {
code : Int
state : InputState
repeat : Bool
modifiers : Modifiers
} derive(Eq,
Debug
)

#
NativeKeyEvent::new

fn NativeKeyEvent::new(code~ : Int, state~ : InputState, repeat? : Bool, modifiers? : Modifiers) -> NativeKeyEvent

#
Position

pub(all) struct Position {
x : Int
y : Int
} derive(Eq,
Debug
)

#
Position::new

fn Position::new(x~ : Int, y~ : Int) -> Position

#
ProxyError

pub(all) enum ProxyError {
Closed
MessageTooLarge
QueueFull
} derive(Eq,
Debug
)

A rejected event-loop proxy submission.

#
ScrollDelta

pub(all) struct ScrollDelta {
x : Double
y : Double
} derive(Eq,
Debug
)

#
ScrollDelta::new

fn ScrollDelta::new(x~ : Double, y~ : Double) -> ScrollDelta

#
Size

pub(all) struct Size {
width : Int
height : Int
} derive(Eq,
Debug
)

Physical client-area size in native pixels.

#
Size::new

fn Size::new(width~ : Int, height~ : Int) -> Size

#
Size::to_logical

fn Size::to_logical(self : Size, scale_factor : Double) -> LogicalSize raise GeometryError

#
WebViewHost

pub struct WebViewHost {
// private fields
} derive(
Debug
)

#
WebViewHost::native_handle

fn WebViewHost::native_handle(self : WebViewHost) -> UInt64 raise WindowError

#
Window

pub struct Window {
// private fields
} derive(
Debug
)

impl Eq for Window

#
Window::current_monitor

fn Window::current_monitor(self : Window) -> Monitor? raise WindowError

#
Window::destroy

fn Window::destroy(self : Window) -> Unit

#
Window::focus

fn Window::focus(self : Window) -> Unit

Requests that this window is shown and activated on the UI thread. Platform foreground-stealing rules may limit activation, but the request always restores a minimized window when the host permits it.

#
Window::id

fn Window::id(self : Window) -> WindowId

#
Window::inner_size

fn Window::inner_size(self : Window) -> Size raise WindowError

#
Window::is_destroyed

fn Window::is_destroyed(self : Window) -> Bool

#
Window::is_fullscreen

fn Window::is_fullscreen(self : Window) -> Bool raise WindowError

#
Window::is_maximized

fn Window::is_maximized(self : Window) -> Bool raise WindowError

#
Window::is_minimized

fn Window::is_minimized(self : Window) -> Bool raise WindowError

#
Window::request_close

fn Window::request_close(self : Window) -> Unit

#
Window::request_redraw

fn Window::request_redraw(self : Window) -> Unit

#
Window::scale_factor

fn Window::scale_factor(self : Window) -> Double raise WindowError

#
Window::set_decorated

fn Window::set_decorated(self : Window, decorated : Bool) -> Unit

#
Window::set_fullscreen

fn Window::set_fullscreen(self : Window, fullscreen : Bool) -> Unit

#
Window::set_fullscreen_on

fn Window::set_fullscreen_on(self : Window, monitor : MonitorId) -> Unit raise WindowError

#
Window::set_inner_size

fn Window::set_inner_size(self : Window, size : Size) -> Unit

#
Window::set_max_inner_size

fn Window::set_max_inner_size(self : Window, size : Size?) -> Unit

#
Window::set_maximized

fn Window::set_maximized(self : Window, maximized : Bool) -> Unit

#
Window::set_min_inner_size

fn Window::set_min_inner_size(self : Window, size : Size?) -> Unit

#
Window::set_minimized

fn Window::set_minimized(self : Window, minimized : Bool) -> Unit

#
Window::set_outer_position

fn Window::set_outer_position(self : Window, position : Position) -> Unit

#
Window::set_resizable

fn Window::set_resizable(self : Window, resizable : Bool) -> Unit

#
Window::set_title

fn Window::set_title(self : Window, title : String) -> Unit

#
Window::set_visible

fn Window::set_visible(self : Window, visible : Bool) -> Unit

#
Window::webview_host

fn Window::webview_host(self : Window) -> WebViewHost raise WindowError

#
WindowEvent

pub(all) enum WindowEvent {
CloseRequested
Destroyed
Resized(Size)
ScaleFactorChanged(Double)
FocusChanged(Bool)
RedrawRequested
KeyInput(NativeKeyEvent)
TextInput(String)
PointerMoved(Position)
PointerEntered
PointerLeft
MouseInput(MouseButton, InputState, Modifiers)
MouseWheel(ScrollDelta, Modifiers)
} derive(
Debug
)

#
WindowId

pub(all) struct WindowId {
raw : Int
} derive(Eq,
Debug
)

#
WindowId::from_raw

fn WindowId::from_raw(raw : Int) -> WindowId

#
WindowId::raw

fn WindowId::raw(self : WindowId) -> Int

#
WindowOptions

pub(all) struct WindowOptions {
title : String
size : Size
visible : Bool
resizable : Bool
} derive(Eq,
Debug
)

#
WindowOptions::new

fn WindowOptions::new(title? : String, size? : Size, visible? : Bool, resizable? : Bool) -> WindowOptions

#
validate_scale_factor

fn validate_scale_factor(scale_factor : Double) -> Bool

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io