use crate::{composition_strategies, utils, ComposedArea, CompositionStrategy};
use std::{
collections::HashSet,
ops::Deref,
sync::{Arc, Mutex},
};
use web_sys::wasm_bindgen::JsValue;
const GLOBAL_STYLES_ID: &str = "hframe-global-styles";
pub(crate) struct CompositionContext {
pub(crate) egui_ctx: egui::Context,
composed_areas: Vec<ComposedArea>,
composed_areas_since_last_sync: HashSet<egui::Id>,
composition_strategy: Option<Box<dyn CompositionStrategy>>,
}
impl CompositionContext {
pub(crate) fn new(egui_ctx: &egui::Context) -> Self {
let document = web_sys::window().unwrap().document().unwrap();
let style = document.get_element_by_id(GLOBAL_STYLES_ID);
if style.is_none() {
document
.head()
.expect("No head element found in the document")
.insert_adjacent_html(
"beforeend",
&format!(
"<style id=\"{}\">{}</style>",
GLOBAL_STYLES_ID,
include_str!("hframe.css")
),
)
.unwrap();
}
let composition_strategy: Box<dyn CompositionStrategy> =
Box::new(composition_strategies::SvgDataMask::new());
web_sys::console::debug_2(
&JsValue::from("Using composition strategy:"),
&JsValue::from(composition_strategy.name()),
);
Self {
egui_ctx: egui_ctx.clone(),
composed_areas: Vec::new(),
composed_areas_since_last_sync: HashSet::new(),
composition_strategy: Some(composition_strategy),
}
}
pub(crate) fn put_composed_area(&mut self, area: ComposedArea) {
let (new, prev) = utils::vec::insert_or_replace(&mut self.composed_areas, area, |a| a.id);
self.composed_areas_since_last_sync.insert(new.id);
if let Some(new_html) = &new.html {
let did_content_change = prev
.as_ref()
.map(|prev| {
prev.html
.as_ref()
.expect("Non HTML area turned into HTML area")
.content
!= new_html.content
})
.unwrap_or(true);
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let element = document.get_element_by_id(&new_html.id).unwrap_or_else(|| {
let body = document.body().unwrap();
let element = document.create_element("div").unwrap();
body.append_child(&element).unwrap();
element
});
if did_content_change {
element.set_outer_html(&new_html.to_outer_html());
} else {
element
.set_attribute("style", &new_html.to_styles())
.unwrap();
}
}
}
fn purge_composed_areas(&mut self) {
self.composed_areas.retain(|a| {
if !self.composed_areas_since_last_sync.contains(&a.id) {
if let Some(html) = &a.html {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let element = document.get_element_by_id(&html.id).unwrap();
element.remove();
}
false
} else {
true
}
});
self.composed_areas_since_last_sync.clear();
}
fn sort_composed_areas(&mut self) {
let layer_ids: Vec<_> = self.egui_ctx.memory(|mem| mem.layer_ids().collect());
let mut composed_areas = std::mem::take(&mut self.composed_areas);
self.composed_areas = layer_ids
.iter()
.filter_map(|layer_id| {
composed_areas
.iter()
.position(|area| area.id == layer_id.id)
.map(|pos| composed_areas.swap_remove(pos))
})
.collect();
}
fn compose(&mut self) {
if let Some(mut strategy) = self.composition_strategy.take() {
strategy.compose(self);
self.composition_strategy = Some(strategy);
}
}
pub(crate) fn sync(&mut self) {
self.purge_composed_areas();
self.sort_composed_areas();
self.compose();
}
pub(crate) fn get_composed_areas(&self) -> &[ComposedArea] {
&self.composed_areas
}
pub(crate) fn get_composed_areas_on_top_of<'cmp>(
&'cmp self,
of: &'cmp ComposedArea,
) -> impl Iterator<Item = &'cmp ComposedArea> + 'cmp {
let index = self
.composed_areas
.iter()
.position(|area| area.id == of.id)
.expect("The are is not known in this composition context");
self.composed_areas[index + 1..]
.iter()
.filter(|area| area.rect.intersects(of.rect))
}
pub(crate) fn get_composed_area_being_dragged(&self) -> Option<&ComposedArea> {
let egui_ctx = &self.egui_ctx;
let dragging = egui_ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary));
if !dragging {
return None;
}
let top_layer_id = egui_ctx.top_layer_id()?.id;
self.composed_areas
.iter()
.find(|area| area.id == top_layer_id)
}
}
impl Drop for CompositionContext {
fn drop(&mut self) {
let document = web_sys::window().unwrap().document().unwrap();
let style = document.get_element_by_id(GLOBAL_STYLES_ID);
if let Some(style) = style {
style.remove();
}
}
}
#[derive(Clone)]
pub(crate) struct WrappedCompositionContext(pub(crate) Arc<Mutex<CompositionContext>>);
impl Deref for WrappedCompositionContext {
type Target = Arc<Mutex<CompositionContext>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub(crate) fn get_composition_context(ctx: &egui::Context) -> WrappedCompositionContext {
ctx.memory_mut(|mem| {
match mem
.data
.get_temp::<WrappedCompositionContext>(egui::Id::NULL)
{
Some(cmp) => cmp,
None => {
let cmp = CompositionContext::new(ctx);
let cmp = WrappedCompositionContext(Arc::new(Mutex::new(cmp)));
mem.data.insert_temp(egui::Id::NULL, cmp.clone());
cmp
}
}
})
}
pub fn sync(ctx: &egui::Context) {
let cmp = get_composition_context(ctx);
let mut cmp = cmp.lock().unwrap();
cmp.sync();
}