use slate_renderer::Lpx;
use slate_renderer::scene::RectInstance;
use crate::context::{LayoutCtx, PaintCtx, PrepaintCtx};
use crate::element::Element;
use crate::event::{Handlers, KeyHandlers};
use crate::focus::FocusableEntry;
use crate::hit_test::{CursorStyle, HitRegion};
use crate::layout::resolve_child_bounds;
use crate::types::{
AccessibilityInfo, AccessibilityRole, Bounds, ElementId, LayoutId, NodeContext,
};
use super::{Div, DivLayoutState, DivPaintState};
impl Element for Div {
type LayoutState = DivLayoutState;
type PaintState = DivPaintState;
fn request_layout(&mut self, cx: &mut LayoutCtx) -> (LayoutId, Self::LayoutState) {
let mut child_nodes = Vec::with_capacity(self.children.len());
for child in &mut self.children {
let layout_id = child.request_layout(cx);
child_nodes.push(layout_id.0);
}
let taffy_style = taffy::Style::from(&self.layout_style);
let node_id = match cx.taffy.new_with_children(taffy_style, &child_nodes) {
Ok(id) => id,
Err(e) => {
log::error!("Div: failed to create Taffy node: {e}; rendering empty");
match cx.taffy.new_leaf(taffy::Style::default()) {
Ok(id) => id,
Err(e2) => {
log::error!("Div: Taffy new_leaf also failed ({e2}) — pathological state");
taffy::NodeId::from(u64::MAX)
}
}
}
};
if let Err(e) = cx.taffy.set_node_context(node_id, Some(NodeContext::None)) {
log::error!("Div: failed to set node context: {e}; layout proceeds without context");
}
(LayoutId(node_id), DivLayoutState { node_id })
}
fn prepaint(
&mut self,
bounds: Bounds,
layout_state: &mut Self::LayoutState,
cx: &mut PrepaintCtx,
) -> Self::PaintState {
if let Some(k) = self.user_key.take() {
cx.set_next_key(k);
}
let element_id = cx.allocate_id::<Div>();
self.last_id = Some(element_id);
cx.register_handlers(
element_id,
Handlers {
on_click: self.on_click.clone(),
on_mouse_down: self.on_mouse_down.clone(),
on_mouse_up: self.on_mouse_up.clone(),
on_mouse_move: self.on_mouse_move.clone(),
on_mouse_scrolled: self.on_mouse_scrolled.clone(),
on_pointer_event: self.on_pointer_event.clone(),
on_pointer_enter: self.on_pointer_enter.clone(),
on_pointer_leave: self.on_pointer_leave.clone(),
},
);
cx.register_key_handlers(
element_id,
KeyHandlers {
on_key_down: self.on_key_down.clone(),
on_key_up: self.on_key_up.clone(),
on_text_input: self.on_text_input.clone(),
},
);
if self.ime_capable {
cx.register_ime_state(element_id);
cx.register_ime_handlers(
element_id,
crate::event::ImeHandlers {
on_ime_preedit: self.on_ime_preedit.clone(),
on_ime_commit: self.on_ime_commit.clone(),
on_ime_enabled: None,
on_ime_disabled: None,
},
);
}
if self.focusable {
cx.register_focusable(
FocusableEntry {
id: element_id,
tab_index: self.tab_index,
focus_ring: self.focus_ring,
},
bounds,
self.visual.corner_radius,
);
}
let has_any_handler = self.on_click.is_some()
|| self.on_mouse_down.is_some()
|| self.on_mouse_up.is_some()
|| self.on_mouse_move.is_some()
|| self.on_mouse_scrolled.is_some()
|| self.on_pointer_event.is_some()
|| self.on_pointer_enter.is_some()
|| self.on_pointer_leave.is_some();
if self.visual.background.is_some() || has_any_handler {
cx.register_hit_region(
HitRegion::new(element_id, bounds, 0).with_cursor(CursorStyle::Arrow),
);
}
let opened_a11y = if let Some(info) = self.accessibility() {
cx.prepaint_node_open(element_id, bounds, info);
true
} else {
false
};
cx.push_frame(element_id);
for (i, child) in self.children.iter_mut().enumerate() {
if let Some(child_bounds) =
resolve_child_bounds(cx.taffy, layout_state.node_id, i, bounds.origin)
{
child.prepaint(child_bounds, cx);
}
}
cx.pop_frame();
if opened_a11y {
cx.prepaint_node_close();
}
DivPaintState
}
fn paint(
&mut self,
bounds: Bounds,
layout_state: &mut Self::LayoutState,
_paint_state: &mut Self::PaintState,
cx: &mut PaintCtx,
) {
if let Some(color) = self.visual.background {
cx.scene.push_rect(RectInstance {
rect: [
Lpx(bounds.origin.x),
Lpx(bounds.origin.y),
Lpx(bounds.size.width),
Lpx(bounds.size.height),
],
color,
corner_radius: Lpx(self.visual.corner_radius),
_pad: [0.0; 3],
});
}
for (i, child) in self.children.iter_mut().enumerate() {
if let Some(child_bounds) =
resolve_child_bounds(cx.taffy, layout_state.node_id, i, bounds.origin)
{
child.paint(child_bounds, cx);
}
}
}
fn accessibility(&self) -> Option<AccessibilityInfo> {
Some(AccessibilityInfo {
role: AccessibilityRole::Group,
..Default::default()
})
}
fn id(&self) -> Option<ElementId> {
self.last_id
}
}