Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions crates/xilem_web/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use std::marker::PhantomData;

use xilem_core::{Id, MessageResult};

use crate::{interfaces::sealed::Sealed, AttributeValue, ChangeFlags, Cx, View, ViewMarker};
use crate::{AttributeValue, ChangeFlags, Cx, View, ViewMarker};

use super::interfaces::Element;
use super::interfaces::{Element, ElementProps as _};

pub struct Attr<E, T, A> {
pub(crate) element: E,
Expand All @@ -15,15 +15,15 @@ pub struct Attr<E, T, A> {
}

impl<E, T, A> ViewMarker for Attr<E, T, A> {}
impl<E, T, A> Sealed for Attr<E, T, A> {}

impl<E: Element<T, A>, T, A> View<T, A> for Attr<E, T, A> {
type State = E::State;
type Element = E::Element;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
cx.add_attr_to_element(&self.name, &self.value);
self.element.build(cx)
let (id, mut state, element) = self.element.build(cx);
state.set_attribute(Some(element.as_ref()), &self.name, &self.value);
(id, state, element)
}

fn rebuild(
Expand All @@ -34,7 +34,7 @@ impl<E: Element<T, A>, T, A> View<T, A> for Attr<E, T, A> {
state: &mut Self::State,
element: &mut Self::Element,
) -> ChangeFlags {
cx.add_attr_to_element(&self.name, &self.value);
state.set_attribute(None, &self.name, &self.value);
self.element.rebuild(cx, &prev.element, id, state, element)
}

Expand Down
10 changes: 5 additions & 5 deletions crates/xilem_web/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{borrow::Cow, marker::PhantomData};
use xilem_core::{Id, MessageResult};

use crate::{
interfaces::{sealed::Sealed, Element},
interfaces::{Element, ElementProps as _},
ChangeFlags, Cx, View, ViewMarker,
};

Expand Down Expand Up @@ -83,17 +83,17 @@ pub struct Class<E, T, A> {
}

impl<E, T, A> ViewMarker for Class<E, T, A> {}
impl<E, T, A> Sealed for Class<E, T, A> {}

impl<E: Element<T, A>, T, A> View<T, A> for Class<E, T, A> {
type State = E::State;
type Element = E::Element;

fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
let (id, mut state, el) = self.element.build(cx);
for class_name in &self.class_names {
cx.add_class_to_element(class_name);
state.set_class(Some(el.as_ref()), class_name.clone());
}
self.element.build(cx)
(id, state, el)
}

fn rebuild(
Expand All @@ -105,7 +105,7 @@ impl<E: Element<T, A>, T, A> View<T, A> for Class<E, T, A> {
element: &mut Self::Element,
) -> ChangeFlags {
for class_name in &self.class_names {
cx.add_class_to_element(class_name);
state.set_class(None, class_name.clone());
}
self.element.rebuild(cx, &prev.element, id, state, element)
}
Expand Down
264 changes: 1 addition & 263 deletions crates/xilem_web/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,217 +1,14 @@
use std::any::Any;

use crate::{app::AppRunner, view::DomNode, Message, Pod};
use bitflags::bitflags;
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::Document;

use xilem_core::{Id, IdPath};

use crate::{
app::AppRunner,
diff::{diff_kv_iterables, Diff},
vecmap::VecMap,
view::DomNode,
AttributeValue, Message, Pod,
};

type CowStr = std::borrow::Cow<'static, str>;

#[derive(Debug, Default)]
pub struct HtmlProps {
pub(crate) attributes: VecMap<CowStr, AttributeValue>,
pub(crate) classes: VecMap<CowStr, ()>,
pub(crate) styles: VecMap<CowStr, CowStr>,
}

impl HtmlProps {
fn apply(&mut self, el: &web_sys::Element) -> Self {
let attributes = self.apply_attributes(el);
let classes = self.apply_classes(el);
let styles = self.apply_styles(el);
Self {
attributes,
classes,
styles,
}
}

fn apply_attributes(&mut self, element: &web_sys::Element) -> VecMap<CowStr, AttributeValue> {
let mut attributes = VecMap::default();
std::mem::swap(&mut attributes, &mut self.attributes);
for (name, value) in attributes.iter() {
set_attribute(element, name, &value.serialize());
}
attributes
}

fn apply_classes(&mut self, element: &web_sys::Element) -> VecMap<CowStr, ()> {
let mut classes = VecMap::default();
std::mem::swap(&mut classes, &mut self.classes);
for (class_name, ()) in classes.iter() {
set_class(element, class_name);
}
classes
}

fn apply_styles(&mut self, element: &web_sys::Element) -> VecMap<CowStr, CowStr> {
let mut styles = VecMap::default();
std::mem::swap(&mut styles, &mut self.styles);
for (name, value) in styles.iter() {
set_style(element, name, value);
}
styles
}

fn apply_changes(&mut self, element: &web_sys::Element, props: &mut HtmlProps) -> ChangeFlags {
self.apply_attribute_changes(element, &mut props.attributes)
| self.apply_class_changes(element, &mut props.classes)
| self.apply_style_changes(element, &mut props.styles)
}

pub(crate) fn apply_attribute_changes(
&mut self,
element: &web_sys::Element,
attributes: &mut VecMap<CowStr, AttributeValue>,
) -> ChangeFlags {
let mut changed = ChangeFlags::empty();
// update attributes
for itm in diff_kv_iterables(&*attributes, &self.attributes) {
match itm {
Diff::Add(name, value) | Diff::Change(name, value) => {
set_attribute(element, name, &value.serialize());
changed |= ChangeFlags::OTHER_CHANGE;
}
Diff::Remove(name) => {
remove_attribute(element, name);
changed |= ChangeFlags::OTHER_CHANGE;
}
}
}
std::mem::swap(attributes, &mut self.attributes);
self.attributes.clear();
changed
}

pub(crate) fn apply_class_changes(
&mut self,
element: &web_sys::Element,
classes: &mut VecMap<CowStr, ()>,
) -> ChangeFlags {
let mut changed = ChangeFlags::empty();
// update attributes
for itm in diff_kv_iterables(&*classes, &self.classes) {
match itm {
Diff::Add(class_name, ()) | Diff::Change(class_name, ()) => {
set_class(element, class_name);
changed |= ChangeFlags::OTHER_CHANGE;
}
Diff::Remove(class_name) => {
remove_class(element, class_name);
changed |= ChangeFlags::OTHER_CHANGE;
}
}
}
std::mem::swap(classes, &mut self.classes);
self.classes.clear();
changed
}

pub(crate) fn apply_style_changes(
&mut self,
element: &web_sys::Element,
styles: &mut VecMap<CowStr, CowStr>,
) -> ChangeFlags {
let mut changed = ChangeFlags::empty();
// update attributes
for itm in diff_kv_iterables(&*styles, &self.styles) {
match itm {
Diff::Add(name, value) | Diff::Change(name, value) => {
set_style(element, name, value);
changed |= ChangeFlags::OTHER_CHANGE;
}
Diff::Remove(name) => {
remove_style(element, name);
changed |= ChangeFlags::OTHER_CHANGE;
}
}
}
std::mem::swap(styles, &mut self.styles);
self.styles.clear();
changed
}
}

fn set_attribute(element: &web_sys::Element, name: &str, value: &str) {
// we have to special-case `value` because setting the value using `set_attribute`
// doesn't work after the value has been changed.
if name == "value" {
let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw();
element.set_value(value);
} else if name == "checked" {
let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw();
element.set_checked(true);
} else {
element.set_attribute(name, value).unwrap_throw();
}
}

fn remove_attribute(element: &web_sys::Element, name: &str) {
// we have to special-case `checked` because setting the value using `set_attribute`
// doesn't work after the value has been changed.
if name == "checked" {
let element: &web_sys::HtmlInputElement = element.dyn_ref().unwrap_throw();
element.set_checked(false);
} else {
element.remove_attribute(name).unwrap_throw();
}
}

fn set_class(element: &web_sys::Element, class_name: &str) {
debug_assert!(
!class_name.is_empty(),
"class names cannot be the empty string"
);
debug_assert!(
!class_name.contains(' '),
"class names cannot contain the ascii space character"
);
element.class_list().add_1(class_name).unwrap_throw();
}

fn remove_class(element: &web_sys::Element, class_name: &str) {
debug_assert!(
!class_name.is_empty(),
"class names cannot be the empty string"
);
debug_assert!(
!class_name.contains(' '),
"class names cannot contain the ascii space character"
);
element.class_list().remove_1(class_name).unwrap_throw();
}

fn set_style(element: &web_sys::Element, name: &str, value: &str) {
if let Some(el) = element.dyn_ref::<web_sys::HtmlElement>() {
el.style().set_property(name, value).unwrap_throw();
} else if let Some(el) = element.dyn_ref::<web_sys::SvgElement>() {
el.style().set_property(name, value).unwrap_throw();
}
}

fn remove_style(element: &web_sys::Element, name: &str) {
if let Some(el) = element.dyn_ref::<web_sys::HtmlElement>() {
el.style().remove_property(name).unwrap_throw();
} else if let Some(el) = element.dyn_ref::<web_sys::SvgElement>() {
el.style().remove_property(name).unwrap_throw();
}
}

// Note: xilem has derive Clone here. Not sure.
pub struct Cx {
id_path: IdPath,
document: Document,
// TODO There's likely a cleaner more robust way to propagate the attributes to an element
pub(crate) current_element_props: HtmlProps,
app_ref: Option<Box<dyn AppRunner>>,
}

Expand All @@ -234,7 +31,6 @@ impl Cx {
id_path: Vec::new(),
document: crate::document(),
app_ref: None,
current_element_props: Default::default(),
}
}

Expand Down Expand Up @@ -306,64 +102,6 @@ impl Cx {
&self.document
}

pub(crate) fn build_element(&mut self, ns: &str, name: &str) -> (web_sys::Element, HtmlProps) {
let el = self
.document
.create_element_ns(Some(ns), name)
.expect("could not create element");
let props = self.current_element_props.apply(&el);
(el, props)
}

pub(crate) fn rebuild_element(
&mut self,
element: &web_sys::Element,
props: &mut HtmlProps,
) -> ChangeFlags {
self.current_element_props.apply_changes(element, props)
}

// TODO Not sure how multiple attribute definitions with the same name should be handled (e.g. `e.attr("class", "a").attr("class", "b")`)
// Currently the outer most (in the example above "b") defines the attribute (when it isn't `None`, in that case the inner attr defines the value)
pub(crate) fn add_attr_to_element(&mut self, name: &CowStr, value: &Option<AttributeValue>) {
// Panic in dev if "class" is used as an attribute. In production the result is undefined.
debug_assert!(
name != "class",
"classes should be set using the `class` method"
);
// Panic in dev if "style" is used as an attribute. In production the result is undefined.
debug_assert!(
name != "style",
"styles should be set using the `style` method"
);

if let Some(value) = value {
// could be slightly optimized via something like this: `new_attrs.entry(name).or_insert_with(|| value)`
if !self.current_element_props.attributes.contains_key(name) {
self.current_element_props
.attributes
.insert(name.clone(), value.clone());
}
}
}

pub(crate) fn add_class_to_element(&mut self, class_name: &CowStr) {
// Don't strictly need this check but I assume its better for perf (might not be though)
if !self.current_element_props.classes.contains_key(class_name) {
self.current_element_props
.classes
.insert(class_name.clone(), ());
}
}

pub(crate) fn add_style_to_element(&mut self, name: &CowStr, value: &CowStr) {
if !self.current_element_props.styles.contains_key(name) {
self.current_element_props
.styles
.insert(name.clone(), value.clone());
}
}

pub fn message_thunk(&self) -> MessageThunk {
MessageThunk {
id_path: self.id_path.clone(),
Expand Down
Loading