use alloc::vec::Vec;
use crate::geom::{LogicalSize, OptionLogicalSize};
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum TransientAnchor {
#[default]
Bottom,
Top,
Left,
Right,
Cursor,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum TransientDismiss {
#[default]
Outside,
Escape,
None,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum TransientTearoff {
#[default]
None,
Free,
Zone,
}
impl TransientTearoff {
#[must_use]
pub fn parse(value: &str) -> Self {
let v = value.trim();
if matches!(v, "true" | "free" | "1" | "") {
Self::Free
} else if v == "zone" || v.starts_with("zone:") {
Self::Zone
} else {
Self::None
}
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum TransientDock {
#[default]
Popup,
Inline,
}
impl TransientDock {
#[must_use]
pub fn parse(value: &str) -> Self {
if value.trim() == "inline" {
Self::Inline
} else {
Self::Popup
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct TransientWindowConfig {
pub size: OptionLogicalSize,
pub anchor: TransientAnchor,
pub dismiss: TransientDismiss,
pub tearoff: TransientTearoff,
pub dock: TransientDock,
pub material: crate::window::WindowBackgroundMaterial,
pub open: bool,
pub torn: bool,
}
impl Default for TransientWindowConfig {
fn default() -> Self {
Self {
open: false,
anchor: TransientAnchor::Bottom,
dismiss: TransientDismiss::Outside,
size: OptionLogicalSize::None,
tearoff: TransientTearoff::None,
dock: TransientDock::Popup,
material: crate::window::WindowBackgroundMaterial::Opaque,
torn: false,
}
}
}
impl TransientWindowConfig {
#[must_use]
pub const fn closed() -> Self {
Self {
open: false,
anchor: TransientAnchor::Bottom,
dismiss: TransientDismiss::Outside,
size: OptionLogicalSize::None,
tearoff: TransientTearoff::None,
dock: TransientDock::Popup,
material: crate::window::WindowBackgroundMaterial::Opaque,
torn: false,
}
}
#[must_use]
pub const fn opened() -> Self {
Self {
open: true,
..Self::closed()
}
}
#[must_use]
pub const fn with_anchor(mut self, anchor: TransientAnchor) -> Self {
self.anchor = anchor;
self
}
#[must_use]
pub const fn with_dismiss(mut self, dismiss: TransientDismiss) -> Self {
self.dismiss = dismiss;
self
}
#[must_use]
pub const fn with_size(mut self, size: LogicalSize) -> Self {
self.size = OptionLogicalSize::Some(size);
self
}
#[must_use]
pub const fn with_tearoff(mut self, tearoff: TransientTearoff) -> Self {
self.tearoff = tearoff;
self
}
#[must_use]
pub const fn with_torn(mut self, torn: bool) -> Self {
self.torn = torn;
self
}
#[must_use]
pub const fn with_material(
mut self,
material: crate::window::WindowBackgroundMaterial,
) -> Self {
self.material = material;
self
}
#[must_use]
pub const fn with_dock(mut self, dock: TransientDock) -> Self {
self.dock = dock;
self
}
}
impl TransientWindowConfig {
pub fn apply_attr(&mut self, key: &str, value: &str) -> bool {
match key {
"open" => {
self.open = matches!(value.trim(), "true" | "open" | "1" | "");
true
}
"anchor" => {
self.anchor = TransientAnchor::parse(value);
true
}
"dismiss" => {
self.dismiss = TransientDismiss::parse(value);
true
}
"tearoff" => {
self.tearoff = TransientTearoff::parse(value);
true
}
"torn" => {
self.torn = matches!(value.trim(), "true" | "1" | "");
true
}
"dock" => {
self.dock = TransientDock::parse(value);
true
}
"material" => {
self.material = match value.trim() {
"transparent" => crate::window::WindowBackgroundMaterial::Transparent,
"sidebar" => crate::window::WindowBackgroundMaterial::Sidebar,
"menu" => crate::window::WindowBackgroundMaterial::Menu,
"hud" => crate::window::WindowBackgroundMaterial::HUD,
"titlebar" => crate::window::WindowBackgroundMaterial::Titlebar,
"mica-alt" => crate::window::WindowBackgroundMaterial::MicaAlt,
_ => crate::window::WindowBackgroundMaterial::Opaque,
};
true
}
"size" => {
self.size = match value.trim().split_once('x') {
Some((w, h)) => match (w.trim().parse::<f32>(), h.trim().parse::<f32>()) {
(Ok(w), Ok(h)) if w > 0.0 && h > 0.0 => {
OptionLogicalSize::Some(LogicalSize::new(w, h))
}
_ => OptionLogicalSize::None,
},
None => OptionLogicalSize::None, };
true
}
_ => false,
}
}
}
impl TransientAnchor {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Bottom => "bottom",
Self::Top => "top",
Self::Left => "left",
Self::Right => "right",
Self::Cursor => "cursor",
}
}
#[must_use]
pub fn parse(s: &str) -> Self {
match s.trim() {
"top" => Self::Top,
"left" => Self::Left,
"right" => Self::Right,
"cursor" => Self::Cursor,
_ => Self::Bottom,
}
}
}
impl TransientDismiss {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Outside => "outside",
Self::Escape => "escape",
Self::None => "none",
}
}
#[must_use]
pub fn parse(s: &str) -> Self {
match s.trim() {
"escape" => Self::Escape,
"none" => Self::None,
_ => Self::Outside,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_is_closed_and_anchored_below() {
let c = TransientWindowConfig::default();
assert!(!c.open);
assert_eq!(c.anchor, TransientAnchor::Bottom);
assert_eq!(c.dismiss, TransientDismiss::Outside);
assert!(
matches!(c.size, OptionLogicalSize::None),
"content-sized unless told otherwise"
);
}
#[test]
fn anchor_and_dismiss_round_trip_through_their_attribute_strings() {
for a in [
TransientAnchor::Bottom,
TransientAnchor::Top,
TransientAnchor::Left,
TransientAnchor::Right,
TransientAnchor::Cursor,
] {
assert_eq!(TransientAnchor::parse(a.as_str()), a);
}
for d in [
TransientDismiss::Outside,
TransientDismiss::Escape,
TransientDismiss::None,
] {
assert_eq!(TransientDismiss::parse(d.as_str()), d);
}
assert_eq!(TransientAnchor::parse("sideways"), TransientAnchor::Bottom);
assert_eq!(TransientDismiss::parse("maybe"), TransientDismiss::Outside);
}
#[test]
fn attributes_apply_onto_the_config() {
let mut c = TransientWindowConfig::closed();
assert!(c.apply_attr("open", "true"));
assert!(c.apply_attr("anchor", "right"));
assert!(c.apply_attr("dismiss", "escape"));
assert!(c.apply_attr("size", "320x240"));
assert!(c.apply_attr("tearoff", "true"));
assert!(c.apply_attr("torn", "true"));
assert!(c.apply_attr("material", "transparent"));
assert!(c.apply_attr("dock", "inline"));
assert!(
!c.apply_attr("class", "x"),
"not ours - the caller keeps it"
);
assert!(c.open);
assert_eq!(c.anchor, TransientAnchor::Right);
assert_eq!(c.dismiss, TransientDismiss::Escape);
assert!(
matches!(c.size, OptionLogicalSize::Some(s) if s.width == 320.0 && s.height == 240.0)
);
assert_eq!(c.tearoff, TransientTearoff::Free);
assert!(c.torn);
assert_eq!(
c.material,
crate::window::WindowBackgroundMaterial::Transparent
);
assert_eq!(c.dock, TransientDock::Inline);
c.apply_attr("dock", "popup");
assert_eq!(c.dock, TransientDock::Popup);
c.apply_attr("material", "opaque");
assert_eq!(c.material, crate::window::WindowBackgroundMaterial::Opaque);
c.apply_attr("tearoff", "zone:.sidebar");
assert_eq!(c.tearoff, TransientTearoff::Zone);
c.apply_attr("tearoff", "nope");
assert_eq!(c.tearoff, TransientTearoff::None);
c.apply_attr("size", "big");
assert!(matches!(c.size, OptionLogicalSize::None));
c.apply_attr("open", "false");
assert!(!c.open);
}
#[test]
fn the_config_does_not_grow_node_type() {
let cfg = core::mem::size_of::<TransientWindowConfig>();
let node = core::mem::size_of::<crate::dom::NodeType>();
assert!(
cfg < node,
"TransientWindowConfig is {cfg} bytes, NodeType is {node}: the config \
has become the largest payload and is now what sets NodeType's size"
);
}
}
#[must_use]
pub fn extract_subtree_as_dom(
styled_dom: &crate::styled_dom::StyledDom,
root: crate::id::NodeId,
) -> Option<crate::dom::Dom> {
use crate::dom::NodeType;
{
let nodes = styled_dom.node_data.as_container();
let root_data = nodes.get(root)?;
if !matches!(root_data.get_node_type(), NodeType::TransientWindow(_)) {
return None;
}
}
let mut dom = build_subtree(styled_dom, root, true, 0)?;
dom.root.set_node_type(NodeType::Div);
Some(dom)
}
fn build_subtree(
styled_dom: &crate::styled_dom::StyledDom,
id: crate::id::NodeId,
is_root: bool,
depth: usize,
) -> Option<crate::dom::Dom> {
use crate::styled_dom::NodeHierarchyItem;
if depth > 256 {
return None; }
let nodes = styled_dom.node_data.as_container();
let hierarchy = styled_dom.node_hierarchy.as_container();
let mut data = nodes.get(id)?.clone();
bake_resolved_style(styled_dom, id, is_root, &mut data);
let mut dom = crate::dom::Dom::create_from_data(data);
let mut child = hierarchy.get(id).and_then(|h| h.first_child_id(id));
while let Some(c) = child {
if let Some(cd) = build_subtree(styled_dom, c, false, depth + 1) {
dom.add_child(cd);
}
child = hierarchy
.get(c)
.and_then(NodeHierarchyItem::next_sibling_id);
}
Some(dom)
}
fn bake_resolved_style(
styled_dom: &crate::styled_dom::StyledDom,
id: crate::id::NodeId,
is_root: bool,
data: &mut crate::dom::NodeData,
) {
use azul_css::dynamic_selector::{CssPropertyWithConditions, DynamicSelector, PseudoStateType};
let cache = styled_dom.get_css_property_cache();
let i = id.index(); let with_state = |p: &crate::prop_cache::StatefulCssProperty| CssPropertyWithConditions {
property: p.property.clone(),
apply_if: if p.state == PseudoStateType::Normal {
Vec::new().into()
} else {
vec![DynamicSelector::PseudoState(p.state)].into()
},
};
if is_root {
{
for (prop_type, p) in cache.computed_values.values_for(i) {
if prop_type.is_inheritable() {
data.add_css_property(CssPropertyWithConditions {
property: p.property.clone(),
apply_if: Vec::new().into(),
});
}
}
}
}
for p in cache.css_props.get_slice(i) {
data.add_css_property(with_state(p));
}
}
#[cfg(test)]
#[path = "transient_test.rs"]
mod transient_test;