use crate::input::UiEvent;
use crate::key::Key;
use crate::runtime::Extension;
use crate::tree::OriginId;
use crate::ui::Ui;
use crate::value::Value;
#[derive(Clone, Copy, Debug)]
pub struct Slot<'a> {
pub name: &'a str,
pub namespace: &'a str,
pub params: &'a Value,
pub key: Key,
}
pub const ROOT_SLOT: &str = "root";
pub const NAMESPACE_SEPARATOR: char = '/';
pub static NULL_PARAMS: Value = Value::Null;
pub const MAX_REPLY_HOPS: usize = 16;
impl Slot<'_> {
pub fn root() -> Slot<'static> {
Slot {
name: ROOT_SLOT,
namespace: "",
params: &NULL_PARAMS,
key: Key::ROOT.str(ROOT_SLOT),
}
}
pub fn full_name(&self) -> String {
full_name(self.namespace, self.name)
}
}
pub fn full_name(namespace: &str, name: &str) -> String {
if namespace.is_empty() {
name.to_owned()
} else {
format!("{namespace}{NAMESPACE_SEPARATOR}{name}")
}
}
pub const ANY_SLOT: &str = "*";
pub fn split_name(full: &str) -> (&str, &str) {
match full.rfind(NAMESPACE_SEPARATOR) {
Some(i) => (&full[..i], &full[i + 1..]),
None => ("", full),
}
}
pub trait Fill {
fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>);
fn finish(&mut self, ui: &mut Ui<'_>);
fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
let _ = ext;
Err(format!(
"cannot load `{namespace}`: this frame was begun with something other than an \
extension list to fill its slots"
))
}
}
struct Entry {
namespace: String,
ext: Option<Box<dyn Extension>>,
name: String,
slots: Vec<String>,
asked_by: OriginId,
}
#[derive(Default)]
pub struct Extensions {
list: Vec<Entry>,
}
impl Extensions {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, ext: Box<dyn Extension>) -> Result<(), String> {
self.push_as(String::new(), ext)
}
pub fn push_as(
&mut self,
namespace: impl Into<String>,
ext: Box<dyn Extension>,
) -> Result<(), String> {
self.insert(namespace.into(), ext).map(|_| ())
}
fn insert(&mut self, namespace: String, ext: Box<dyn Extension>) -> Result<OriginId, String> {
let namespace = if namespace.is_empty() {
ext.name().to_owned()
} else {
namespace
};
if namespace.is_empty() {
return Err(
"extension needs a namespace: it names itself nothing and none was given"
.to_owned(),
);
}
if let Some(taken) = self.list.iter().find(|e| e.namespace == namespace) {
return Err(format!(
"namespace `{namespace}` is already `{}`'s; give `{}` another with `push_as`",
taken.name,
ext.name()
));
}
if let Some(bad) = ext
.slots()
.iter()
.find(|s| s.is_empty() || s.contains(NAMESPACE_SEPARATOR))
{
return Err(format!(
"extension `{}` lists slot {bad:?}: a slot name is one word without `{}` — the \
host adds the namespace",
ext.name(),
NAMESPACE_SEPARATOR
));
}
let origin = OriginId(self.list.len() as u16 + 1);
self.list.push(Entry {
namespace,
name: ext.name().to_owned(),
slots: ext.slots().to_vec(),
ext: Some(ext),
asked_by: OriginId::HOST,
});
Ok(origin)
}
pub fn len(&self) -> usize {
self.list.len()
}
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
pub fn by_origin(&mut self, origin: OriginId) -> Option<&mut (dyn Extension + 'static)> {
let i = (origin.0 as usize).checked_sub(1)?;
self.list.get_mut(i)?.ext.as_deref_mut()
}
pub fn namespace_of(&self, origin: OriginId) -> Option<&str> {
let i = (origin.0 as usize).checked_sub(1)?;
self.list.get(i).map(|e| e.namespace.as_str())
}
pub fn asked_by(&self, origin: OriginId) -> OriginId {
let Some(i) = (origin.0 as usize).checked_sub(1) else {
return OriginId::HOST;
};
self.list.get(i).map_or(OriginId::HOST, |e| e.asked_by)
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &dyn Extension)> {
self.list
.iter()
.filter_map(|e| Some((e.namespace.as_str(), &**e.ext.as_ref()?)))
}
pub fn route(
&mut self,
events: impl IntoIterator<Item = UiEvent>,
mut to_host: impl FnMut(UiEvent),
) {
if self.list.is_empty() {
events.into_iter().for_each(to_host);
return;
}
let mut queue: std::collections::VecDeque<(OriginId, usize, UiEvent)> =
events.into_iter().map(|ev| (ev.origin, 0, ev)).collect();
while let Some((to, depth, ev)) = queue.pop_front() {
if to == OriginId::HOST {
to_host(ev);
continue;
}
let Some(ext) = self.by_origin(to) else {
to_host(ev);
continue;
};
let replies = ext.on_event(&ev);
if replies.is_empty() {
continue;
}
let up = self.asked_by(to);
for payload in replies {
let reply = UiEvent {
origin: to,
window: ev.window,
key: ev.key,
payload,
slot: ev.slot,
};
if up == OriginId::HOST || depth + 1 >= MAX_REPLY_HOPS {
to_host(reply);
} else {
queue.push_back((up, depth + 1, reply));
}
}
}
}
fn fill_one(&mut self, i: usize, name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
let ns = self.list[i].namespace.clone();
let ext_name = self.list[i].name.clone();
let Some(mut ext) = self.list[i].ext.take() else {
let full = full_name(&ns, name);
ui.core()
.warn(crate::diag::recursive_slot(&ext_name, &full, key));
return;
};
self.list[i].asked_by = ui.origin();
let slot = Slot {
name,
namespace: &ns,
params,
key,
};
let origin = OriginId(i as u16 + 1);
ui.fill_within(origin, &slot, self, |ui| {
if let Err(err) = ext.view(&slot, ui) {
ui.core().warn(crate::diag::extension_view_error(
&ext_name,
&slot.full_name(),
slot.key,
&err,
));
ui.text(
&format!("[{ext_name}] {err}"),
crate::spec::TextStyle::new(13.0)
.color(crate::color::Color::rgb8(0xe8, 0x5d, 0x5d)),
);
}
});
self.list[i].ext = Some(ext);
}
}
impl TryFrom<Vec<Box<dyn Extension>>> for Extensions {
type Error = String;
fn try_from(exts: Vec<Box<dyn Extension>>) -> Result<Self, String> {
let mut out = Self::new();
for ext in exts {
out.push(ext)?;
}
Ok(out)
}
}
impl Fill for Extensions {
fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
let (ns, name) = split_name(full_name);
let Some(i) = self.list.iter().position(|e| e.namespace == ns) else {
return;
};
let slots = &self.list[i].slots;
let wants = slots.iter().any(|s| s == ANY_SLOT)
|| if name == ROOT_SLOT {
slots.is_empty()
} else {
slots.iter().any(|s| s == name)
};
if wants {
self.fill_one(i, name, key, params, ui);
}
}
fn finish(&mut self, ui: &mut Ui<'_>) {
let mut i = 0;
while i < self.list.len() {
let ns = self.list[i].namespace.clone();
if self.list[i].slots.is_empty() {
let full = full_name(&ns, ROOT_SLOT);
if !ui.slot_declared(&full)
&& let Some(key) = ui.core().begin_slot(&full)
{
self.fill_one(i, ROOT_SLOT, key, &NULL_PARAMS, ui);
}
} else {
for name in self.list[i].slots.clone() {
if name == ANY_SLOT {
continue;
}
let full = full_name(&ns, &name);
if ui.slot_declared(&full) {
continue;
}
let ext_name = self.list[i].name.clone();
ui.core()
.warn(crate::diag::unknown_slot(&ext_name, &ns, &name));
}
}
i += 1;
}
}
fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
self.insert(namespace.to_owned(), ext)
}
}
impl<F: Fill + ?Sized> Fill for &mut F {
fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
F::fill(&mut **self, full_name, key, params, ui);
}
fn finish(&mut self, ui: &mut Ui<'_>) {
F::finish(&mut **self, ui);
}
fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
F::add(&mut **self, namespace, ext)
}
}
impl<F: Fill + ?Sized> Fill for Box<F> {
fn fill(&mut self, full_name: &str, key: Key, params: &Value, ui: &mut Ui<'_>) {
F::fill(&mut **self, full_name, key, params, ui);
}
fn finish(&mut self, ui: &mut Ui<'_>) {
F::finish(&mut **self, ui);
}
fn add(&mut self, namespace: &str, ext: Box<dyn Extension>) -> Result<OriginId, String> {
F::add(&mut **self, namespace, ext)
}
}