#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub use oxiui_core::{ButtonResponse, Color, FontSpec, Palette, Theme, UiCtx, UiError};
pub mod runner;
pub mod multiwindow;
pub use multiwindow::SecondaryWindow;
pub mod dialog;
pub use dialog::{DialogId, DialogKind, DialogQueue, DialogResponse};
pub mod menu;
pub use menu::{Menu, MenuBar, MenuBarBuilder, MenuItem};
#[cfg(feature = "egui")]
#[cfg_attr(docsrs, doc(cfg(feature = "egui")))]
pub use runner::EguiRunner;
#[cfg(feature = "iced")]
#[cfg_attr(docsrs, doc(cfg(feature = "iced")))]
pub use runner::IcedRunner;
pub use runner::{BackendRunner, LifecycleConfig};
#[cfg(feature = "tracing")]
#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
pub mod logging;
#[cfg(feature = "egui")]
pub(crate) mod icon;
pub mod theme_picker;
pub use theme_picker::{by_name as theme_by_name, theme_picker, BUILTIN_THEMES};
pub mod theme {
pub use oxiui_theme::{cooljapan_default, dark, light};
}
#[cfg(feature = "table")]
#[cfg_attr(docsrs, doc(cfg(feature = "table")))]
pub mod table {
pub use oxiui_table::*;
}
#[cfg(feature = "a11y")]
#[cfg_attr(docsrs, doc(cfg(feature = "a11y")))]
pub mod accessibility {
pub use oxiui_accessibility::{A11yNode, A11yTree, WidgetRole};
}
#[cfg(feature = "a11y")]
#[cfg_attr(docsrs, doc(cfg(feature = "a11y")))]
pub mod recording;
#[cfg(feature = "a11y")]
#[cfg_attr(docsrs, doc(cfg(feature = "a11y")))]
pub use recording::{RecordingEntry, RecordingUiCtx};
#[cfg(feature = "software")]
#[cfg_attr(docsrs, doc(cfg(feature = "software")))]
pub mod render {
pub use oxiui_render_soft::{
render_headless_once, render_headless_scene, Framebuffer, RgbaBuffer,
};
}
pub mod text {
pub use oxiui_core::{FontFeature, FontSpec, FontStyle};
}
pub mod solver {
pub use oxiui_core::{
Constraint, Expression, RelOp, Solver, SolverError, Strength, Term, Variable,
};
}
pub mod tray;
pub use tray::{TrayConfig, TrayHandle, TrayMenuItem};
pub mod native_dialog;
pub use native_dialog::{DialogResult, MessageLevel};
pub mod prelude {
pub use crate::{App, AppConfig, AppExit, Backend, HotkeyConflict, Notification, Plugin};
pub use oxiui_core::{AlignContent, FlexWrap, RichTextSpan};
pub use oxiui_core::{ButtonResponse, Color, UiCtx, UiError};
pub use oxiui_core::{Computed, ReactiveError, ReactiveRuntime, Signal};
pub use oxiui_core::{Point, Rect, Size};
pub use oxiui_theme::CooljapanTheme;
}
pub mod core {
pub use oxiui_core::*;
}
pub mod reactive {
pub use oxiui_core::{Computed, ReactiveError, ReactiveRuntime, Signal};
}
pub mod app_config;
pub use app_config::AppConfig;
pub mod notification;
pub use notification::{Notification, NotificationQueue};
pub mod command;
pub use command::{Command, CommandPalette};
#[derive(Clone, Debug, Default)]
pub enum Backend {
#[default]
Egui,
#[cfg(feature = "iced")]
Iced,
#[cfg(feature = "slint")]
Slint,
#[cfg(feature = "dioxus")]
Dioxus,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AppExit {
Ok,
Error(String),
RequestedByUser,
Programmatic(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HotkeyConflict {
pub message: String,
}
impl std::fmt::Display for HotkeyConflict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HotkeyConflict: {}", self.message)
}
}
impl std::error::Error for HotkeyConflict {}
type ContentFn = Box<dyn FnMut(&mut dyn oxiui_core::UiCtx) + Send>;
type HookFn = Box<dyn FnMut(&mut dyn oxiui_core::UiCtx) + Send + Sync>;
#[cfg(feature = "egui")]
type EguiFrameHook = Box<dyn FnMut(&egui::Context) + Send>;
pub trait Plugin: Send + Sync {
fn init(&mut self, ctx: &mut dyn UiCtx);
fn update(&mut self, ctx: &mut dyn UiCtx);
fn priority(&self) -> i32 {
0
}
}
use oxiui_core::events::{Key, Modifiers};
pub struct HotkeyBinding {
pub id: String,
pub modifiers: Modifiers,
pub key: Key,
pub action: Box<dyn Fn() + Send + Sync>,
}
pub struct HotkeyRegistry {
bindings: Vec<HotkeyBinding>,
}
impl HotkeyRegistry {
pub fn new() -> Self {
Self {
bindings: Vec::new(),
}
}
pub fn register(
&mut self,
id: impl Into<String>,
mods: Modifiers,
key: Key,
action: impl Fn() + Send + Sync + 'static,
) -> Result<(), String> {
if self.conflict_check(mods, key.clone()) {
return Err(format!("hotkey conflict: {mods:?}+{key:?}"));
}
self.bindings.push(HotkeyBinding {
id: id.into(),
modifiers: mods,
key,
action: Box::new(action),
});
Ok(())
}
pub fn conflict_check(&self, mods: Modifiers, key: Key) -> bool {
self.bindings
.iter()
.any(|b| b.modifiers == mods && b.key == key)
}
pub fn len(&self) -> usize {
self.bindings.len()
}
pub fn is_empty(&self) -> bool {
self.bindings.is_empty()
}
}
impl Default for HotkeyRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "iced")]
mod iced_backend;
#[cfg(feature = "iced")]
use iced_backend as iced_app;
pub struct App {
config: AppConfig,
theme: Box<dyn oxiui_core::Theme>,
content: Option<ContentFn>,
backend: Backend,
on_init: Vec<HookFn>,
on_frame: Vec<HookFn>,
on_close: Vec<HookFn>,
on_resize: Vec<HookFn>,
on_focus: Vec<HookFn>,
plugins: Vec<Box<dyn Plugin>>,
hotkeys: HotkeyRegistry,
commands: CommandPalette,
notifications: NotificationQueue,
frame_skip: bool,
#[cfg(feature = "egui")]
egui_frame_hooks: Vec<EguiFrameHook>,
window_registry: multiwindow::WindowRegistry,
dialogs: dialog::DialogQueue,
menu_bar: Option<menu::MenuBar>,
}
impl App {
pub fn new(config: AppConfig) -> Self {
Self {
config,
theme: oxiui_theme::cooljapan_default(),
content: None,
backend: Backend::default(),
on_init: Vec::new(),
on_frame: Vec::new(),
on_close: Vec::new(),
on_resize: Vec::new(),
on_focus: Vec::new(),
plugins: Vec::new(),
hotkeys: HotkeyRegistry::new(),
commands: CommandPalette::new(),
notifications: NotificationQueue::new(),
frame_skip: false,
#[cfg(feature = "egui")]
egui_frame_hooks: Vec::new(),
window_registry: multiwindow::WindowRegistry::new(),
dialogs: dialog::DialogQueue::new(),
menu_bar: None,
}
}
pub fn theme(mut self, theme: Box<dyn oxiui_core::Theme>) -> Self {
self.theme = theme;
self
}
pub fn content<F>(mut self, f: F) -> Self
where
F: FnMut(&mut dyn oxiui_core::UiCtx) + Send + 'static,
{
self.content = Some(Box::new(f));
self
}
pub fn backend(mut self, backend: Backend) -> Self {
self.backend = backend;
self
}
pub fn min_size(mut self, w: f32, h: f32) -> Self {
self.config.min_size = Some((w, h));
self
}
pub fn max_size(mut self, w: f32, h: f32) -> Self {
self.config.max_size = Some((w, h));
self
}
pub fn decorations(mut self, d: bool) -> Self {
self.config.decorations = d;
self
}
pub fn transparent(mut self, t: bool) -> Self {
self.config.transparent = t;
self
}
pub fn always_on_top(mut self, a: bool) -> Self {
self.config.always_on_top = a;
self
}
pub fn icon(mut self, bytes: Vec<u8>) -> Self {
self.config.icon = Some(bytes);
self
}
pub fn position(mut self, x: f32, y: f32) -> Self {
self.config.position = Some((x, y));
self
}
pub fn with_font(mut self, family_name: impl Into<String>, bytes: Vec<u8>) -> Self {
self.config.extra_fonts.push((family_name.into(), bytes));
self
}
pub fn with_state<State: Send + 'static>(
mut self,
state: State,
mut content: impl FnMut(&mut dyn oxiui_core::UiCtx, &mut State) + Send + 'static,
) -> Self {
let mut inner_state = state;
let content_fn = move |ui: &mut dyn oxiui_core::UiCtx| {
content(ui, &mut inner_state);
};
self.content = Some(Box::new(content_fn));
self
}
pub fn with_frame_skip(mut self, enabled: bool) -> Self {
self.frame_skip = enabled;
self
}
#[cfg(feature = "egui")]
#[cfg_attr(docsrs, doc(cfg(feature = "egui")))]
pub fn with_egui_ctx(mut self, f: impl FnMut(&egui::Context) + Send + 'static) -> Self {
self.egui_frame_hooks.push(Box::new(f));
self
}
#[cfg(feature = "table")]
#[cfg_attr(docsrs, doc(cfg(feature = "table")))]
pub fn table<S: oxiui_table::RowSource + Send + 'static>(mut self, source: S) -> Self {
let source = std::sync::Arc::new(std::sync::Mutex::new(source));
self = self.content(move |ui| {
if let Ok(src) = source.lock() {
for col in src.column_defs() {
ui.label(col.name.as_str());
}
let row_count = src.row_count();
for i in 0..row_count {
let cells = src.row(i);
for cell in &cells {
ui.label(&cell.to_string());
}
}
}
});
self
}
pub fn on_init<F>(mut self, f: F) -> Self
where
F: FnMut(&mut dyn UiCtx) + Send + Sync + 'static,
{
self.on_init.push(Box::new(f));
self
}
pub fn on_frame<F>(mut self, f: F) -> Self
where
F: FnMut(&mut dyn UiCtx) + Send + Sync + 'static,
{
self.on_frame.push(Box::new(f));
self
}
pub fn on_close<F>(mut self, f: F) -> Self
where
F: FnMut(&mut dyn UiCtx) + Send + Sync + 'static,
{
self.on_close.push(Box::new(f));
self
}
pub fn on_resize<F>(mut self, f: F) -> Self
where
F: FnMut(&mut dyn UiCtx) + Send + Sync + 'static,
{
self.on_resize.push(Box::new(f));
self
}
pub fn on_focus<F>(mut self, f: F) -> Self
where
F: FnMut(&mut dyn UiCtx) + Send + Sync + 'static,
{
self.on_focus.push(Box::new(f));
self
}
pub fn plugin<P: Plugin + 'static>(mut self, p: P) -> Self {
self.plugins.push(Box::new(p));
self
}
pub fn notify(
mut self,
title: impl Into<String>,
body: impl Into<String>,
urgency: u8,
) -> Self {
self.notifications.enqueue(title, body, urgency);
self
}
pub fn try_hotkey(
mut self,
mods: Modifiers,
key: Key,
action: impl Into<String>,
) -> Result<Self, HotkeyConflict> {
let action_str: String = action.into();
self.hotkeys
.register(action_str.clone(), mods, key, move || {})
.map_err(|message| HotkeyConflict { message })?;
Ok(self)
}
pub fn register_command(mut self, name: impl Into<String>, shortcut: Option<String>) -> Self {
let name: String = name.into();
self.commands
.register_with_shortcut(name.clone(), name, shortcut, || {});
self
}
pub fn command_matches(&self, query: &str) -> Vec<String> {
self.commands
.search(query)
.into_iter()
.map(|c| c.label.clone())
.collect()
}
pub fn screenshot(&self) -> Result<Vec<u8>, UiError> {
#[cfg(feature = "software")]
{
let w = if self.config.width > 0.0 {
self.config.width as u32
} else {
800
};
let h = if self.config.height > 0.0 {
self.config.height as u32
} else {
600
};
let buf = oxiui_render_soft::headless::render_headless_once(w, h);
let tmp_path = std::env::temp_dir().join(format!("oxiui_screenshot_{w}x{h}.png"));
buf.save_png(&tmp_path)
.map_err(|e| UiError::Backend(e.to_string()))?;
let bytes = std::fs::read(&tmp_path).map_err(|e| UiError::Backend(e.to_string()))?;
let _ = std::fs::remove_file(&tmp_path);
Ok(bytes)
}
#[cfg(not(feature = "software"))]
Err(UiError::Unsupported(
"App::screenshot() requires the `software` feature to be enabled.".to_string(),
))
}
pub fn run_with_return<T>(
self,
content: impl FnOnce(&mut dyn UiCtx) -> T + 'static,
) -> Result<T, UiError> {
struct NullUiCtx;
impl UiCtx for NullUiCtx {
fn heading(&mut self, _text: &str) {}
fn label(&mut self, _text: &str) {}
fn button(&mut self, _label: &str) -> ButtonResponse {
ButtonResponse::default()
}
}
let mut null = NullUiCtx;
let result = content(&mut null);
Ok(result)
}
pub fn notifications(&self) -> &NotificationQueue {
&self.notifications
}
pub fn hotkeys(&self) -> &HotkeyRegistry {
&self.hotkeys
}
pub fn extra_fonts(&self) -> &[(String, Vec<u8>)] {
&self.config.extra_fonts
}
pub fn with_design_tokens(mut self, tokens: oxiui_theme::DesignTokens) -> Self {
self.config.design_tokens = Some(tokens);
self
}
pub fn with_typography(mut self, typography: oxiui_theme::TypographyScale) -> Self {
self.config.typography = Some(typography);
self
}
pub fn design_tokens(&self) -> oxiui_theme::DesignTokens {
self.config.design_tokens.clone().unwrap_or_default()
}
pub fn typography(&self) -> oxiui_theme::TypographyScale {
self.config.typography.unwrap_or_default()
}
#[cfg(feature = "software")]
#[cfg_attr(docsrs, doc(cfg(feature = "software")))]
pub fn soft_renderer(&self) -> oxiui_render_soft::SoftRenderer {
oxiui_render_soft::SoftRenderer::new()
}
#[cfg(feature = "persist")]
#[cfg_attr(docsrs, doc(cfg(feature = "persist")))]
pub fn with_persistent_state<State>(
self,
initial: State,
storage_path: std::path::PathBuf,
content: impl FnMut(&mut dyn oxiui_core::UiCtx, &mut State) + Send + 'static,
) -> Self
where
State: oxicode::Encode + oxicode::Decode + Send + 'static,
{
let loaded: State = (|| -> Result<State, Box<dyn std::error::Error>> {
let bytes = std::fs::read(&storage_path)?;
let state = oxicode::decode_value::<State>(&bytes)?;
Ok(state)
})()
.unwrap_or_else(|e| {
eprintln!("oxiui: state load from {}: {e}", storage_path.display());
initial
});
let mut content_fn = content;
let path = storage_path.clone();
self.with_state(loaded, move |ui, s| {
content_fn(ui, s);
})
.on_close(move |_ui| {
let _ = path; })
}
pub fn with_tray(self, config: tray::TrayConfig) -> Result<Self, String> {
let _ = tray::TrayHandle::mount(config)?;
Ok(self)
}
pub fn open_window(
&mut self,
config: oxiui_core::window::WindowConfig,
) -> oxiui_core::window::WindowId {
self.window_registry.open_window(config)
}
pub fn close_window(&mut self, id: oxiui_core::window::WindowId) -> Option<SecondaryWindow> {
self.window_registry.close_window(id)
}
pub fn secondary_windows(&self) -> &[SecondaryWindow] {
self.window_registry.secondary_windows()
}
pub fn window_channel(&self) -> &oxiui_core::window::WindowChannel {
self.window_registry.channel()
}
pub fn file_dialog(
&mut self,
title: impl Into<String>,
filters: Vec<(String, String)>,
multiple: bool,
) -> DialogId {
self.dialogs.request(DialogKind::FileOpen {
title: title.into(),
filters,
multiple,
})
}
pub fn file_save_dialog(
&mut self,
title: impl Into<String>,
default_name: Option<String>,
filters: Vec<(String, String)>,
) -> DialogId {
self.dialogs.request(DialogKind::FileSave {
title: title.into(),
default_name,
filters,
})
}
pub fn message_dialog(
&mut self,
title: impl Into<String>,
message: impl Into<String>,
) -> DialogId {
self.dialogs.request(DialogKind::Alert {
title: title.into(),
message: message.into(),
})
}
pub fn confirm_dialog(
&mut self,
title: impl Into<String>,
message: impl Into<String>,
) -> DialogId {
self.dialogs.request(DialogKind::Confirm {
title: title.into(),
message: message.into(),
})
}
pub fn prompt_dialog(
&mut self,
title: impl Into<String>,
message: impl Into<String>,
default_text: Option<String>,
) -> DialogId {
self.dialogs.request(DialogKind::Prompt {
title: title.into(),
message: message.into(),
default_text,
})
}
pub fn poll_dialog(&mut self, id: DialogId) -> Option<DialogResponse> {
self.dialogs.pop_response(id)
}
pub fn respond_dialog(&mut self, id: DialogId, response: DialogResponse) {
self.dialogs.respond(id, response);
}
pub fn dialog_queue(&mut self) -> &mut DialogQueue {
&mut self.dialogs
}
pub fn file_dialog_native(
&self,
title: impl AsRef<str>,
filters: &[(&str, &str)],
multiple: bool,
) -> DialogResult {
native_dialog::open_file_dialog(title.as_ref(), filters, multiple)
}
pub fn message_dialog_native(
&self,
title: impl AsRef<str>,
message: impl AsRef<str>,
level: MessageLevel,
) -> DialogResult {
native_dialog::message_dialog(title.as_ref(), message.as_ref(), level)
}
pub fn menu_bar<F>(mut self, build: F) -> Self
where
F: FnOnce(&mut MenuBarBuilder),
{
self.menu_bar = Some(MenuBar::build(build));
self
}
pub fn with_menu_bar(mut self, bar: MenuBar) -> Self {
self.menu_bar = Some(bar);
self
}
pub fn get_menu_bar(&self) -> Option<&MenuBar> {
self.menu_bar.as_ref()
}
pub fn startup_clock() -> std::time::Instant {
std::time::Instant::now()
}
pub fn run(self) -> Result<AppExit, UiError> {
#[cfg(feature = "iced")]
if let Backend::Iced = &self.backend {
return self.run_iced();
}
#[cfg(feature = "slint")]
if let Backend::Slint = &self.backend {
return self.run_slint_backend();
}
#[cfg(feature = "dioxus")]
if let Backend::Dioxus = &self.backend {
return self.run_dioxus_backend();
}
self.run_egui_or_fallback()
}
#[cfg(feature = "slint")]
fn run_slint_backend(mut self) -> Result<AppExit, UiError> {
use oxiui_slint::run_slint;
let theme_ref = self.theme.as_ref();
if let Some(content) = self.content.take() {
let mut content_fn = content;
run_slint(theme_ref, move |ui| content_fn(ui)).map(|()| AppExit::Ok)
} else {
run_slint(theme_ref, |_ui| {}).map(|()| AppExit::Ok)
}
}
#[cfg(feature = "dioxus")]
fn run_dioxus_backend(mut self) -> Result<AppExit, UiError> {
use oxiui_dioxus::run_dioxus;
let theme_ref = self.theme.as_ref();
if let Some(content) = self.content.take() {
let mut content_fn = content;
run_dioxus(theme_ref, move |ui| content_fn(ui)).map(|()| AppExit::Ok)
} else {
run_dioxus(theme_ref, |_ui| {}).map(|()| AppExit::Ok)
}
}
#[cfg(feature = "iced")]
fn run_iced(self) -> Result<AppExit, UiError> {
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use oxiui_iced::palette_to_iced_theme;
let iced_theme = {
let palette = self.theme.palette().clone();
palette_to_iced_theme(&palette)
};
let mut plugins = self.plugins;
plugins.sort_by_key(|p| p.priority());
let state = iced_app::OxiIcedState {
title: self.config.title.clone(),
content: RefCell::new(self.content),
pending_clicks: RefCell::new(HashSet::new()),
widget_state: RefCell::new(HashMap::new()),
on_init: RefCell::new(self.on_init),
on_frame: RefCell::new(self.on_frame),
plugins: RefCell::new(plugins),
initialised: Cell::new(false),
};
iced_app::run(state, iced_theme, self.config.width, self.config.height)
.map(|()| AppExit::Ok)
.map_err(|e| UiError::Backend(e.to_string()))
}
#[cfg(all(feature = "egui", not(target_arch = "wasm32")))]
fn run_egui_or_fallback(mut self) -> Result<AppExit, UiError> {
use eframe::NativeOptions;
use oxiui_egui::palette_to_egui_visuals;
let palette = self.theme.palette().clone();
let title = self.config.title.clone();
let width = self.config.width;
let height = self.config.height;
let visuals = palette_to_egui_visuals(&palette);
let content_fn = self.content.take();
let extra_fonts = std::mem::take(&mut self.config.extra_fonts);
self.plugins.sort_by_key(|p| p.priority());
let icon_data: Option<std::sync::Arc<egui::IconData>> =
if let Some(icon_bytes) = &self.config.icon {
match crate::icon::decode_icon(icon_bytes) {
Ok(data) => Some(std::sync::Arc::new(data)),
Err(e) => {
eprintln!("oxiui: failed to decode window icon: {e}");
None
}
}
} else {
None
};
let mut vp = egui::ViewportBuilder::default()
.with_title(&title)
.with_inner_size([width, height])
.with_resizable(self.config.resizable)
.with_decorations(self.config.decorations)
.with_transparent(self.config.transparent);
if self.config.always_on_top {
vp = vp.with_always_on_top();
}
if let Some((min_w, min_h)) = self.config.min_size {
vp = vp.with_min_inner_size([min_w, min_h]);
}
if let Some((max_w, max_h)) = self.config.max_size {
vp = vp.with_max_inner_size([max_w, max_h]);
}
if let Some((px, py)) = self.config.position {
vp = vp.with_position([px, py]);
}
if let Some(icon) = icon_data {
vp = vp.with_icon(icon);
}
let native_opts = NativeOptions {
viewport: vp,
..Default::default()
};
let frame_skip = self.frame_skip;
let egui_frame_hooks = std::mem::take(&mut self.egui_frame_hooks);
eframe::run_native(
&title,
native_opts,
Box::new(move |cc| {
cc.egui_ctx.set_visuals(visuals.clone());
if !extra_fonts.is_empty() {
let refs: Vec<(&str, Vec<u8>)> = extra_fonts
.iter()
.map(|(n, b)| (n.as_str(), b.clone()))
.collect();
let _ = oxiui_egui::load_fonts_into_egui(&refs, &cc.egui_ctx);
}
Ok(Box::new(OxiEguiApp {
content: content_fn,
on_init: self.on_init,
on_frame: self.on_frame,
plugins: self.plugins,
initialised: false,
frame_skip,
egui_frame_hooks,
}))
}),
)
.map(|()| AppExit::Ok)
.map_err(|e| UiError::Backend(e.to_string()))
}
#[cfg(all(feature = "egui", target_arch = "wasm32"))]
fn run_egui_or_fallback(self) -> Result<AppExit, UiError> {
let _ = &self.config;
let _ = &self.theme;
let _ = &self.content;
let _ = &self.backend;
let _ = &self.on_init;
let _ = &self.on_frame;
let _ = &self.plugins;
let _ = &self.frame_skip;
let _ = &self.egui_frame_hooks;
Err(UiError::Unsupported(
"On wasm32, use `oxiui_web::mount(canvas_id)` instead of App::run().".to_string(),
))
}
#[cfg(not(feature = "egui"))]
fn run_egui_or_fallback(self) -> Result<AppExit, UiError> {
let _ = &self.config;
let _ = &self.theme;
let _ = &self.content;
let _ = &self.backend;
let _ = &self.on_init;
let _ = &self.on_frame;
let _ = &self.plugins;
let _ = &self.frame_skip;
Err(UiError::Unsupported(
"No UI backend enabled. Use default features or enable `egui`.".to_string(),
))
}
pub fn run_headless_once(mut self) -> Result<AppExit, UiError> {
struct NullUiCtx;
impl UiCtx for NullUiCtx {
fn heading(&mut self, _text: &str) {}
fn label(&mut self, _text: &str) {}
fn button(&mut self, _label: &str) -> ButtonResponse {
ButtonResponse::default()
}
}
self.plugins.sort_by_key(|p| p.priority());
let mut null = NullUiCtx;
for hook in self.on_init.iter_mut() {
hook(&mut null);
}
for plugin in self.plugins.iter_mut() {
plugin.init(&mut null);
}
if let Some(ref mut f) = self.content {
f(&mut null);
}
for hook in self.on_frame.iter_mut() {
hook(&mut null);
}
for plugin in self.plugins.iter_mut() {
plugin.update(&mut null);
}
Ok(AppExit::Ok)
}
#[cfg(feature = "a11y")]
pub fn build_a11y_snapshot(
&mut self,
window_id: oxiui_accessibility::WindowA11yId,
) -> oxiui_accessibility::A11yTree {
let mut recorder = recording::RecordingUiCtx::new();
if let Some(ref mut f) = self.content {
f(&mut recorder);
}
recorder.build_a11y_tree(window_id)
}
}
#[cfg(all(feature = "egui", not(target_arch = "wasm32")))]
mod egui_backend;
#[cfg(all(feature = "egui", not(target_arch = "wasm32")))]
use egui_backend::OxiEguiApp;
pub fn process_rss_bytes() -> Option<u64> {
#[cfg(target_os = "linux")]
{
let status = std::fs::read_to_string("/proc/self/status").ok()?;
for line in status.lines() {
if let Some(rest) = line.strip_prefix("VmRSS:") {
let kb: u64 = rest.split_whitespace().next()?.parse().ok()?;
return Some(kb * 1024);
}
}
None
}
#[cfg(not(target_os = "linux"))]
{
None
}
}