use alloc::string::String;
use alloc::vec::Vec;
use core::cell::{Cell, RefCell};
use serde::de::DeserializeOwned;
use crate::api::{self, Json};
use crate::bindings::ui as wit;
use crate::error::Result;
use crate::geometry::Geometry;
pub use crate::input::{Button, Key, KeyCode, KeyPhase, Modifiers, Pointer, PointerKind};
pub trait UiPlugin: Sized + 'static {
fn activate(cx: &mut Context) -> Self;
fn frame(&mut self, frame: &Frame);
fn event(&mut self, event: Event, cx: &mut Context) {
let _ = (event, cx);
}
fn deactivate(&mut self) {}
}
#[derive(Debug, Default)]
pub struct Context {
config: String,
}
impl Context {
pub const fn new() -> Self {
Self {
config: String::new(),
}
}
pub fn config_json(&self) -> Json {
Json(self.config.clone())
}
pub fn config<T: DeserializeOwned>(&self) -> Result<T> {
if self.config.trim().is_empty() {
return Json(String::from("null")).parse();
}
Json(self.config.clone()).parse()
}
pub fn values(&self) -> api::values::Values {
api::values()
}
pub fn live(&self) -> api::live::Live {
api::live()
}
pub fn events(&self) -> api::events::Events {
api::events()
}
pub fn calls(&self) -> api::calls::Calls {
api::calls()
}
pub fn claims(&self) -> api::claims::Claims {
api::claims()
}
pub fn is_driving(&self) -> bool {
api::view::is_driving()
}
pub fn capabilities(&self) -> api::capabilities::Capabilities {
api::capabilities::get()
}
pub fn request_frame(&self) {
crate::host::request_frame();
}
pub fn wake_at(&self, at_ms: u64) {
crate::host::wake_at(at_ms);
}
pub fn open(&self, surface: &str) -> crate::Result<()> {
crate::host::surface_open(surface)
}
pub fn focus_pane(&self, pane: &str) -> crate::Result<()> {
crate::host::focus_pane(pane)
}
pub fn open_url(&self, url: &str) -> crate::Result<()> {
crate::host::url_open(url)
}
pub fn close(&self, surface: &str) -> crate::Result<()> {
crate::host::surface_close(surface)
}
pub fn request_size(&self, surface: &str, cols: u32, rows: u32) -> crate::Result<()> {
crate::host::surface_request_size(surface, cols, rows)
}
pub fn set_label(&self, surface: &str, label: Option<&str>) -> crate::Result<()> {
crate::host::surface_set_label(surface, label)
}
pub fn set_caret(&self, surface: &str, caret: Option<(u32, u32)>) -> crate::Result<()> {
crate::host::surface_set_caret(surface, caret)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Power {
#[default]
Mains,
SavePower,
}
impl From<wit::Power> for Power {
fn from(power: wit::Power) -> Self {
match power {
wit::Power::Mains => Self::Mains,
wit::Power::SavePower => Self::SavePower,
}
}
}
#[derive(Debug)]
pub struct Frame {
now_ms: u64,
interval_ms: u32,
power: Power,
elapsed_ms: u64,
next: Cell<Option<u64>>,
}
impl Frame {
pub fn new(now_ms: u64, interval_ms: u32, power: Power) -> Self {
Self {
now_ms,
interval_ms,
power,
elapsed_ms: 0,
next: Cell::new(None),
}
}
pub fn elapsed(&self) -> u64 {
self.elapsed_ms
}
pub fn now_ms(&self) -> u64 {
self.now_ms
}
pub fn interval_ms(&self) -> u32 {
self.interval_ms
}
pub fn power(&self) -> Power {
self.power
}
pub fn request_frame(&self) {
self.wake_at(self.now_ms);
}
pub fn wake_at(&self, at_ms: u64) {
let next = self.next.get().map_or(at_ms, |next| next.min(at_ms));
self.next.set(Some(next));
}
pub fn wake_after(&self, after_ms: u64) {
self.wake_at(self.now_ms.saturating_add(after_ms));
}
pub fn next_wake(&self) -> Option<u64> {
self.next.get()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Event {
Resize { surface: String, geometry: Geometry },
Visibility { surface: String, visible: bool },
CapabilitiesChanged,
Driving(bool),
ValueChanged { key: String, value: Option<Json> },
Live { key: String, payload: Json },
LiveDeleted { key: String },
Plugin { name: String, payload: Json },
AccountChanged,
Key(Key),
Paste { surface: String, text: String },
Pointer(Pointer),
ThemeChanged,
Focus { surface: String, focused: bool },
Command(String),
CallResult {
id: crate::api::calls::CallId,
result: Result<Json>,
},
FileChanged { watch: u32, paths: Vec<String> },
WatchFailed { watch: u32, error: String },
ProcessOutput {
pid: u32,
stderr: bool,
bytes: Vec<u8>,
},
ProcessExited { pid: u32, status: i32 },
PaneChanged {
kind: crate::daemon::PaneChangeKind,
pane: crate::api::account::Pane,
},
Interest(crate::daemon::Interest),
}
impl From<wit::Event> for Event {
fn from(event: wit::Event) -> Self {
match event {
wit::Event::Resize((surface, g)) => Self::Resize {
surface,
geometry: Geometry {
cols: g.cols,
rows: g.rows,
px_w: g.px_w,
px_h: g.px_h,
cell_px_w: g.cell_px_w,
cell_px_h: g.cell_px_h,
},
},
wit::Event::Visibility((surface, visible)) => Self::Visibility { surface, visible },
wit::Event::CapabilitiesChanged => Self::CapabilitiesChanged,
wit::Event::Driving(driving) => Self::Driving(driving),
wit::Event::ValueChanged((key, value)) => Self::ValueChanged {
key,
value: value.map(Json),
},
wit::Event::Live((key, payload)) => Self::Live {
key,
payload: Json(payload),
},
wit::Event::LiveDeleted(key) => Self::LiveDeleted { key },
wit::Event::PluginEvent((name, payload)) => Self::Plugin {
name,
payload: Json(payload),
},
wit::Event::AccountChanged => Self::AccountChanged,
wit::Event::Key(key) => Self::Key(Key {
surface: key.surface,
code: KeyCode::parse(&key.code),
text: key.text,
modifiers: Modifiers(key.modifiers),
phase: match key.phase {
wit::standard::plugin::event_types::KeyPhase::Press => KeyPhase::Press,
wit::standard::plugin::event_types::KeyPhase::Repeat => KeyPhase::Repeat,
wit::standard::plugin::event_types::KeyPhase::Release => KeyPhase::Release,
},
}),
wit::Event::Paste((surface, text)) => Self::Paste { surface, text },
wit::Event::Pointer(pointer) => Self::Pointer(Pointer {
surface: pointer.surface,
x: pointer.x,
y: pointer.y,
col: pointer.col,
row: pointer.row,
button: Button::from_wire(pointer.button),
kind: match pointer.kind {
wit::standard::plugin::event_types::PointerKind::Down => PointerKind::Down,
wit::standard::plugin::event_types::PointerKind::Up => PointerKind::Up,
wit::standard::plugin::event_types::PointerKind::Move => PointerKind::Move,
wit::standard::plugin::event_types::PointerKind::Drag => PointerKind::Drag,
wit::standard::plugin::event_types::PointerKind::Wheel => PointerKind::Wheel,
wit::standard::plugin::event_types::PointerKind::Enter => PointerKind::Enter,
wit::standard::plugin::event_types::PointerKind::Leave => PointerKind::Leave,
},
modifiers: Modifiers(pointer.modifiers),
}),
wit::Event::ThemeChanged => Self::ThemeChanged,
wit::Event::Focus((surface, focused)) => Self::Focus { surface, focused },
wit::Event::Command(id) => Self::Command(id),
wit::Event::CallResult(answer) => Self::CallResult {
id: crate::api::calls::CallId(answer.id),
result: answer.outcome.map(Json).map_err(Into::into),
},
}
}
}
#[doc(hidden)]
pub struct UiRuntime<P> {
plugin: RefCell<Option<P>>,
cx: RefCell<Context>,
last_frame_ms: Cell<Option<u64>>,
visible: RefCell<alloc::collections::BTreeSet<String>>,
}
unsafe impl<P> Sync for UiRuntime<P> {}
impl<P> Default for UiRuntime<P> {
fn default() -> Self {
Self::new()
}
}
impl<P> UiRuntime<P> {
pub const fn new() -> Self {
Self {
plugin: RefCell::new(None),
cx: RefCell::new(Context::new()),
last_frame_ms: Cell::new(None),
visible: RefCell::new(alloc::collections::BTreeSet::new()),
}
}
}
impl<P: UiPlugin> UiRuntime<P> {
pub fn activate(&self, config: String) {
let mut cx = self.cx.borrow_mut();
cx.config = config;
let plugin = P::activate(&mut cx);
*self.plugin.borrow_mut() = Some(plugin);
}
pub fn frame(&self, now_ms: u64, interval_ms: u32, power: Power) -> Option<u64> {
let mut frame = Frame::new(now_ms, interval_ms, power);
frame.elapsed_ms = self
.last_frame_ms
.get()
.map_or(0, |last| now_ms.saturating_sub(last));
self.last_frame_ms.set(Some(now_ms));
if let Some(plugin) = self.plugin.borrow_mut().as_mut() {
plugin.frame(&frame);
}
frame.next_wake()
}
pub fn event(&self, event: Event) {
match &event {
Event::Resize { surface, .. } => crate::surface::rebind_surfaces(surface),
Event::Visibility { surface, visible } => {
let mut shown = self.visible.borrow_mut();
if *visible {
shown.insert(surface.clone());
} else {
shown.remove(surface);
if shown.is_empty() {
self.last_frame_ms.set(None);
}
}
}
_ => {}
}
let mut cx = self.cx.borrow_mut();
if let Some(plugin) = self.plugin.borrow_mut().as_mut() {
plugin.event(event, &mut cx);
}
}
pub fn deactivate(&self) {
let plugin = self.plugin.borrow_mut().take();
if let Some(mut plugin) = plugin {
plugin.deactivate();
}
}
pub fn with_plugin<R>(&self, f: impl FnOnce(&mut P) -> R) -> Option<R> {
self.plugin.borrow_mut().as_mut().map(f)
}
}