use crate::components::component::ComponentSpecification;
use crate::components::Props;
use crate::components::Event;
use crate::elements::element::{Element, ElementBoxed};
use crate::elements::element_data::ElementData;
use crate::elements::element_styles::ElementStyles;
use crate::layout::layout_context::LayoutContext;
use crate::elements::Container;
use crate::events::CraftMessage;
use crate::geometry::{Point, TrblRectangle};
use crate::reactive::element_state_store::{ElementStateStore, ElementStateStoreItem};
use crate::renderer::renderer::RenderList;
use crate::style::{AlignItems, Display, FlexDirection, Style, Unit};
use crate::generate_component_methods;
use peniko::Color;
use std::any::Any;
use std::sync::Arc;
use taffy::{NodeId, Position, TaffyTree, TraversePartialTree};
use winit::window::Window;
use crate::text::text_context::TextContext;
const DROPDOWN_LIST_INDEX: usize = 1;
#[derive(Clone, Default, Debug)]
pub struct Dropdown {
pub element_data: ElementData,
pseudo_dropdown_selection: Option<ElementBoxed>,
pseudo_dropdown_list_element: Container,
}
#[derive(Clone, Copy, Default)]
pub struct DropdownState {
is_open: bool,
selected_item: Option<usize>,
}
impl Element for Dropdown {
fn element_data(&self) -> &ElementData {
&self.element_data
}
fn element_data_mut(&mut self) -> &mut ElementData {
&mut self.element_data
}
fn children(&self) -> Vec<&dyn Element> {
self.element_data().children.iter().map(|x| x.internal.as_ref()).collect()
}
fn in_bounds(&self, point: Point) -> bool {
let element_data = self.element_data();
let transformed_border_rectangle = element_data.computed_box_transformed.border_rectangle();
let dropdown_selection_in_bounds = transformed_border_rectangle.contains(&point);
let mut dropdown_list_in_bounds = false;
for child in self.children() {
if child.in_bounds(point) {
dropdown_list_in_bounds = true;
break;
}
}
dropdown_selection_in_bounds || dropdown_list_in_bounds
}
fn name(&self) -> &'static str {
"Dropdown"
}
fn draw(
&mut self,
renderer: &mut RenderList,
text_context: &mut TextContext,
taffy_tree: &mut TaffyTree<LayoutContext>,
_root_node: NodeId,
element_state: &mut ElementStateStore,
pointer: Option<Point>,
window: Option<Arc<dyn Window>>,
) {
if !self.element_data.style.visible() {
return;
}
let is_open = self.get_state(element_state).is_open;
self.draw_borders(renderer, element_state);
self.maybe_start_layer(renderer);
{
if let Some(pseudo_dropdown_selection) = self.pseudo_dropdown_selection.as_mut() {
pseudo_dropdown_selection.internal.draw(
renderer,
text_context,
taffy_tree,
pseudo_dropdown_selection.internal.element_data().taffy_node_id.unwrap(),
element_state,
pointer,
window.clone(),
);
}
renderer.start_overlay();
if is_open && !self.children().is_empty() {
self.pseudo_dropdown_list_element.draw(
renderer,
text_context,
taffy_tree,
self.pseudo_dropdown_list_element.element_data.taffy_node_id.unwrap(),
element_state,
pointer,
window.clone(),
);
self.draw_children(renderer, text_context, taffy_tree, element_state, pointer, window.clone());
}
renderer.end_overlay();
}
self.maybe_end_layer(renderer);
}
fn compute_layout(
&mut self,
taffy_tree: &mut TaffyTree<LayoutContext>,
element_state: &mut ElementStateStore,
scale_factor: f64,
) -> Option<NodeId> {
self.merge_default_style();
let state = self.get_state(element_state);
let is_open = state.is_open;
let mut child_nodes: Vec<NodeId> = Vec::new();
self.pseudo_dropdown_selection = if let Some(selected_index) = state.selected_item {
if let Some(selected_element) = self.children_mut().get(selected_index) {
Some(selected_element.clone())
} else {
self.children_mut().first().cloned()
}
} else {
self.children_mut().first().cloned()
};
if let Some(selected_node) = self.pseudo_dropdown_selection.as_mut() {
child_nodes.push(selected_node.internal.compute_layout(taffy_tree, element_state, scale_factor).unwrap());
}
if is_open && !self.children().is_empty() {
let dropdown_list_child_nodes: Vec<NodeId> = self
.children_mut()
.iter_mut()
.filter_map(|child| child.internal.compute_layout(taffy_tree, element_state, scale_factor))
.collect();
self.pseudo_dropdown_list_element.element_data.style = Style::merge(
&Self::default_dropdown_list_style(),
&self.pseudo_dropdown_list_element.element_data.style,
);
let dropdown_list_node_id = taffy_tree
.new_with_children(
self.pseudo_dropdown_list_element.element_data.style.to_taffy_style_with_scale_factor(scale_factor),
&dropdown_list_child_nodes,
)
.unwrap();
child_nodes.push(dropdown_list_node_id);
}
let style: taffy::Style = self.element_data.style.to_taffy_style_with_scale_factor(scale_factor);
self.element_data_mut().taffy_node_id = Some(taffy_tree.new_with_children(style, &child_nodes).unwrap());
self.element_data().taffy_node_id
}
fn finalize_layout(
&mut self,
taffy_tree: &mut TaffyTree<LayoutContext>,
root_node: NodeId,
position: Point,
z_index: &mut u32,
transform: glam::Mat4,
element_state: &mut ElementStateStore,
pointer: Option<Point>,
text_context: &mut TextContext,
) {
let state = self.get_state(element_state);
let is_open = state.is_open;
let result = taffy_tree.layout(root_node).unwrap();
self.resolve_box(position, transform, result, z_index);
self.finalize_borders(element_state);
if let Some(dropdown_selection) = self.pseudo_dropdown_selection.as_mut() {
let dropdown_selection_taffy = dropdown_selection.internal.element_data().taffy_node_id.unwrap();
dropdown_selection.internal.finalize_layout(
taffy_tree,
dropdown_selection_taffy,
self.element_data.computed_box.position,
z_index,
transform,
element_state,
pointer,
text_context,
);
}
if is_open && !self.children().is_empty() {
let dropdown_list = taffy_tree.get_child_id(self.element_data.taffy_node_id.unwrap(), DROPDOWN_LIST_INDEX);
self.pseudo_dropdown_list_element.element_data.taffy_node_id = Some(dropdown_list);
self.pseudo_dropdown_list_element.finalize_layout(
taffy_tree,
dropdown_list,
self.element_data.computed_box.position,
z_index,
transform,
element_state,
pointer,
text_context,
);
for child in self.element_data.children.iter_mut() {
let taffy_child_node_id = child.internal.element_data().taffy_node_id;
if taffy_child_node_id.is_none() {
continue;
}
child.internal.finalize_layout(
taffy_tree,
taffy_child_node_id.unwrap(),
self.pseudo_dropdown_list_element.element_data.computed_box.position,
z_index,
transform,
element_state,
pointer,
text_context,
);
}
}
}
fn on_event(
&self,
message: &CraftMessage,
element_state: &mut ElementStateStore,
_text_context: &mut TextContext,
should_style: bool,
) -> Event {
let mut ret = Event::default();
self.on_style_event(message, element_state, should_style);
let base_state = self.get_base_state_mut(element_state);
let state = base_state.data.as_mut().downcast_mut::<DropdownState>().unwrap();
match message {
CraftMessage::PointerButtonEvent(pointer_button) => {
if !message.clicked() {
return Event::default();
}
for child in self.children().iter().enumerate() {
if child.1.in_bounds(pointer_button.position) {
state.selected_item = Some(child.0);
state.is_open = false;
ret.result_message(CraftMessage::DropdownItemSelected(state.selected_item.unwrap()));
return ret
}
}
let element_data = self.element_data();
let transformed_border_rectangle = element_data.computed_box_transformed.border_rectangle();
let dropdown_selection_in_bounds = transformed_border_rectangle.contains(&pointer_button.position);
if dropdown_selection_in_bounds {
state.is_open = !state.is_open;
ret.result_message(CraftMessage::DropdownToggled(state.is_open));
return ret;
}
}
CraftMessage::KeyboardInputEvent(_) => {}
_ => {}
}
Event::default()
}
fn initialize_state(&mut self, _scaling_factor: f64) -> ElementStateStoreItem {
ElementStateStoreItem {
base: Default::default(),
data: Box::new(DropdownState::default()),
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn default_style(&self) -> Style {
let mut default_style = Style::default();
*default_style.display_mut() = Display::Flex;
*default_style.align_items_mut() = Some(AlignItems::Center);
let vertical_padding = Unit::Px(8.0);
let horizontal_padding = Unit::Px(12.0);
*default_style.padding_mut() = TrblRectangle::new(vertical_padding, horizontal_padding, vertical_padding, horizontal_padding);
*default_style.min_width_mut() = Unit::Px(140.0);
*default_style.min_height_mut() = Unit::Px(45.0);
*default_style.background_mut() = Color::from_rgb8(240, 240, 240);
let border_color = Color::from_rgb8(180, 180, 180);
let border_radius = (6.0, 6.0);
let border_width = Unit::Px(1.0);
*default_style.border_radius_mut() = [border_radius, border_radius, border_radius, border_radius];
*default_style.border_color_mut() = TrblRectangle::new_all(border_color);
*default_style.border_width_mut() = TrblRectangle::new_all(border_width);
default_style
}
}
impl Dropdown {
pub fn dropdown_list_style(mut self, style: &Style) -> Self {
self.pseudo_dropdown_list_element.element_data.style = *style;
self
}
fn default_dropdown_list_style() -> Style {
let mut default_style = Style::default();
let vertical_padding = Unit::Px(8.0);
let horizontal_padding = Unit::Px(12.0);
*default_style.padding_mut() = TrblRectangle::new(vertical_padding, horizontal_padding, vertical_padding, horizontal_padding);
*default_style.min_width_mut() = Unit::Px(140.0);
*default_style.min_height_mut() = Unit::Px(45.0);
*default_style.background_mut() = Color::from_rgb8(220, 220, 220);
let border_color = Color::from_rgb8(160, 160, 160);
let border_radius = (6.0, 6.0);
let border_width = Unit::Px(1.0);
*default_style.border_radius_mut() = [border_radius, border_radius, border_radius, border_radius];
*default_style.border_color_mut() = TrblRectangle::new_all(border_color);
*default_style.border_width_mut() = TrblRectangle::new_all(border_width);
*default_style.display_mut() = Display::Flex;
*default_style.flex_direction_mut() = FlexDirection::Column;
*default_style.position_mut() = Position::Absolute;
default_style.inset_mut().top = Unit::Percentage(100.0);
default_style
}
#[allow(dead_code)]
fn get_state<'a>(&self, element_state: &'a ElementStateStore) -> &'a DropdownState {
element_state.storage.get(&self.element_data.component_id).unwrap().data.as_ref().downcast_ref().unwrap()
}
pub fn new() -> Dropdown {
Dropdown {
element_data: Default::default(),
pseudo_dropdown_selection: Default::default(),
pseudo_dropdown_list_element: Default::default(),
}
}
generate_component_methods!();
}
impl ElementStyles for Dropdown {
fn styles_mut(&mut self) -> &mut Style {
self.element_data.current_style_mut()
}
}