Skip to main content

makara/widgets/
column.rs

1//! A container with flex direction set to column.
2
3use bevy::prelude::*;
4
5use crate::{ContainerStyle, SetContainerStyle, Widget, events::*, utils::*};
6use super::*;
7
8/// Marker component for `column`.
9#[derive(Component)]
10pub struct MakaraColumn;
11
12/// A struct used to mutate components attached to `column` widget.
13pub struct ColumnWidget<'a, 'w, 's> {
14    pub entity: Entity,
15    pub class: &'a mut Class,
16    pub style: WidgetStyle<'a>,
17    pub(crate) commands: &'a mut Commands<'w, 's>,
18    pub(crate) child_entities: Vec<Entity>
19}
20
21type IsColumnOnly = (
22    (
23        With<MakaraColumn>,
24        Without<MakaraCheckbox>,
25        Without<MakaraCheckboxButton>,
26        Without<MakaraCircular>,
27        Without<MakaraRow>,
28        Without<MakaraRoot>,
29        Without<MakaraButton>,
30        Without<MakaraDropdown>,
31        Without<MakaraDropdownOverlay>,
32        Without<MakaraImage>,
33        Without<MakaraLink>,
34        Without<MakaraModal>,
35        Without<MakaraModalBackdrop>,
36    ),
37    (
38        Without<MakaraProgressBar>,
39        Without<MakaraRadio>,
40        Without<MakaraRadioGroup>,
41        Without<MakaraScroll>,
42        Without<MakaraScrollbar>,
43        Without<MakaraTextInput>,
44        Without<MakaraTextInputCursor>,
45        Without<MakaraSlider>,
46        Without<MakaraSliderThumb>,
47        Without<MakaraSelect>,
48        Without<MakaraSelectOverlay>,
49    )
50);
51
52/// `column` system param.
53#[derive(SystemParam)]
54pub struct ColumnQuery<'w, 's> {
55    pub id: Query<'w, 's, (Entity, &'static Id), With<MakaraColumn>>,
56    pub class: Query<'w, 's, (Entity, &'static mut Class), IsColumnOnly>,
57    pub style: StyleQuery<'w, 's, IsColumnOnly>,
58    pub children: Query<'w, 's, &'static Children, With<MakaraColumn>>,
59    pub commands: Commands<'w, 's>
60}
61
62impl<'a, 'w, 's> WidgetChildren for ColumnWidget<'a, 'w, 's> {
63    fn add_child(&mut self, child_bundle: impl Bundle) {
64        let child_entity = self.commands.spawn(child_bundle).id();
65        self.commands.entity(self.entity).add_child(child_entity);
66    }
67
68    fn add_children(&mut self, bundles: impl IntoIterator<Item = impl Bundle>) {
69        let mut child_entities = Vec::new();
70
71        for bundle in bundles {
72            let child_entity = self.commands.spawn(bundle).id();
73            child_entities.push(child_entity);
74        }
75        self.commands.entity(self.entity).add_children(&child_entities);
76    }
77
78    fn insert_at(
79        &mut self,
80        index: usize,
81        bundles: impl IntoIterator<Item = impl Bundle>
82    ) {
83        let mut child_entities = Vec::new();
84
85        for bundle in bundles {
86            let child_entity = self.commands.spawn(bundle).id();
87            child_entities.push(child_entity);
88        }
89        self.commands
90            .entity(self.entity)
91            .insert_children(index, &child_entities);
92    }
93
94    fn insert_first(&mut self, bundles: impl IntoIterator<Item = impl Bundle>) {
95        self.insert_at(0, bundles);
96    }
97
98    fn insert_last(&mut self, bundles: impl IntoIterator<Item = impl Bundle>) {
99        let last_index = self.child_entities.len();
100        self.insert_at(last_index, bundles);
101    }
102
103    fn remove_at(&mut self, index: usize) {
104        if let Some(entity) = self.child_entities.get(index) {
105            self.commands.entity(self.entity).detach_child(*entity);
106            self.commands.entity(*entity).despawn();
107        }
108    }
109
110    fn remove_first(&mut self) {
111        self.remove_at(0);
112    }
113
114    fn remove_last(&mut self) {
115        // if list is empty, does nothing.
116        if let Some(last_index) = self.child_entities.len().checked_sub(1) {
117            self.remove_at(last_index);
118        }
119    }
120}
121
122impl<'w, 's> WidgetQuery<'w, 's> for ColumnQuery<'w, 's> {
123    type WidgetView<'a> = ColumnWidget<'a, 'w, 's> where Self: 'a;
124
125    fn get_components<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>> {
126        let ColumnQuery { id: _, class, style, children, commands } = self;
127
128        let entities = children.get(entity).ok()?
129            .iter()
130            .map(|e| e)
131            .collect::<Vec<Entity>>();
132
133        let style_bundle = style.query.get_mut(entity).ok()?;
134        let (node, bg, border_color, shadow, z_index) = style_bundle;
135
136        return Some(ColumnWidget {
137            entity,
138            class: class.get_mut(entity).ok()?.1.into_inner(),
139            style: WidgetStyle {
140                node: node.into_inner(),
141                background_color: bg.into_inner(),
142                border_color: border_color.into_inner(),
143                shadow: shadow.into_inner(),
144                z_index: z_index.into_inner(),
145            },
146            child_entities: entities,
147            commands: commands
148        });
149    }
150
151    fn find_by_id<'a>(&'a mut self, target_id: &str) -> Option<Self::WidgetView<'a>> {
152        let entity = self.id.iter()
153            .find(|(_, id)| id.0 == target_id)
154            .map(|(e, _)| e)?;
155
156        self.get_components(entity)
157    }
158
159    fn find_by_entity<'a>(&'a mut self, target_entity: Entity) -> Option<Self::WidgetView<'a>> {
160        self.get_components(target_entity)
161    }
162
163    fn find_by_class(&self, target_class: &str) -> Vec<Entity> {
164        self.class.iter()
165            .filter(|(_, class)| class.0.split(" ").any(|word| word == target_class))
166            .map(|(e, _)| e)
167            .collect()
168    }
169}
170
171/// Bundle for creating `column`.
172#[derive(Bundle)]
173pub struct ColumnBundle {
174    pub id_class: IdAndClass,
175    pub style: ContainerStyle
176}
177
178impl Default for ColumnBundle {
179    fn default() -> Self {
180        let style = ContainerStyle {
181            node: Node {
182                width: percent(100),
183                height: auto(),
184                flex_direction: FlexDirection::Column,
185                display: Display::Flex,
186                justify_content: JustifyContent::Center,
187                align_items: AlignItems::Start,
188                ..default()
189            },
190            background_color: BackgroundColor(Color::NONE),
191            shadow: BoxShadow::default(),
192            ..default()
193        };
194
195        Self { style, id_class: IdAndClass::default() }
196    }
197}
198
199impl Widget for ColumnBundle {
200    /// Build `column`.
201    fn build(mut self) -> impl Bundle {
202        process_built_in_spacing_class(&self.id_class.class, &mut self.style.node);
203        (
204            self.id_class,
205            self.style,
206            MakaraColumn,
207        )
208    }
209}
210
211impl SetContainerStyle for ColumnBundle {
212    fn container_style(&mut self) -> &mut ContainerStyle {
213        &mut self.style
214    }
215}
216
217impl SetIdAndClass for ColumnBundle {
218    fn id_and_class(&mut self) -> &mut IdAndClass {
219        &mut self.id_class
220    }
221}
222
223pub(crate) fn detect_column_built(
224    mut commands: Commands,
225    q: Query<Entity, Added<MakaraColumn>>
226) {
227    for entity in q.iter() {
228        commands.trigger(WidgetBuilt {
229            entity
230        });
231    }
232}
233
234pub(crate) fn detect_column_class_change_for_built_in(
235    mut columns: Query<(&Class, &mut Node), IsColumnOnly>
236) {
237    for (class, mut node) in columns.iter_mut() {
238        process_built_in_spacing_class(class, &mut node);
239    }
240}
241
242/// Create column widget.
243pub fn column() -> ColumnBundle {
244    ColumnBundle::default()
245}