use crate::element::{Element, ElementId, ElementProps, ElementWidget};
use crate::events::{ElementClick, ElementHover};
use crate::widgets::checkbox::{CheckboxCheckmark, CheckboxState, CheckboxWidget};
use crate::widgets::dropdown::{DropdownLabelText, DropdownMenu, DropdownState, DropdownTrigger};
use crate::widgets::progress_bar::{ProgressBarFill, ProgressBarState, ProgressBarTrack};
use crate::widgets::scroll_view::{ScrollViewArea, ScrollViewContent, ScrollViewThumb};
use crate::widgets::slider::{SliderFill, SliderThumb, SliderTrack};
use crate::widgets::text_input::{TextInputCursor, TextInputState, TextInputText, TextInputWidget};
use crate::widgets::tooltip::{TooltipPopup, TooltipText};
use bevy::asset::AssetServer;
use bevy::color::Color;
use bevy::ecs::query::QueryData;
use bevy::prelude::*;
use bevy::text::{TextColor, TextFont};
use bevy::ui::{BackgroundColor, BorderColor, BorderRadius, Display, Node, UiRect, Val};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, SystemSet)]
pub struct PageSystemSet;
#[derive(QueryData)]
#[query_data(mutable)]
pub(crate) struct ChildPartsQuery {
pub bg: Option<&'static mut BackgroundColor>,
pub node: Option<&'static mut Node>,
pub text: Option<&'static mut Text>,
pub text_color: Option<&'static mut TextColor>,
pub text_font: Option<&'static mut TextFont>,
pub children: Option<&'static Children>,
pub slider_track: Option<&'static SliderTrack>,
pub slider_fill: Option<&'static SliderFill>,
pub slider_thumb: Option<&'static SliderThumb>,
pub progress_track: Option<&'static ProgressBarTrack>,
pub progress_fill: Option<&'static ProgressBarFill>,
pub checkbox_checkmark: Option<&'static CheckboxCheckmark>,
pub text_input_text: Option<&'static TextInputText>,
pub text_input_cursor: Option<&'static TextInputCursor>,
pub tooltip_popup: Option<&'static TooltipPopup>,
pub tooltip_text: Option<&'static TooltipText>,
pub scroll_area: Option<&'static ScrollViewArea>,
pub scroll_content: Option<&'static ScrollViewContent>,
pub scroll_thumb: Option<&'static ScrollViewThumb>,
pub dropdown_trigger: Option<&'static DropdownTrigger>,
pub dropdown_menu: Option<&'static DropdownMenu>,
pub dropdown_label_text: Option<&'static DropdownLabelText>,
}
pub(crate) fn interactions(
mut commands: Commands,
assets: Res<AssetServer>,
mut entities: Query<
(
(
Entity,
&Interaction,
&Element,
Option<&ElementId>,
&mut Node,
&mut BackgroundColor,
&mut BorderColor,
),
(
Option<&mut Text>,
Option<&mut TextColor>,
Option<&mut TextFont>,
Option<&mut ImageNode>,
Option<&mut ProgressBarState>,
Option<&mut CheckboxState>,
Option<&mut CheckboxWidget>,
Option<&mut TextInputState>,
Option<&mut TextInputWidget>,
Option<&mut DropdownState>,
Option<&Children>,
),
),
Changed<Interaction>,
>,
mut child_parts: Query<ChildPartsQuery, Without<Element>>,
) {
for (
(e, i, el, id, mut node, mut bg_color, mut border_color),
(
text,
text_color,
text_font,
image,
progress_bar,
checkbox_state,
checkbox_widget,
text_input_state,
text_input_widget,
dropdown_state,
children,
),
) in entities.iter_mut()
{
let overrides = el.overrides();
let target_override = match i {
Interaction::Pressed => {
commands.trigger(ElementClick {
entity: e,
id: id.cloned(),
});
&overrides.on_click
}
Interaction::Hovered => {
commands.trigger(ElementHover {
entity: e,
id: id.cloned(),
});
&overrides.on_hover
}
Interaction::None => &overrides.default,
};
apply_element_props(
&el.props,
target_override,
&assets,
&mut node,
&mut bg_color,
&mut border_color,
text,
text_color,
text_font,
image,
progress_bar,
checkbox_state,
checkbox_widget,
text_input_state,
text_input_widget,
dropdown_state,
children,
&mut child_parts,
);
}
}
fn apply_element_props(
base_props: &ElementProps,
props_override: &ElementProps,
assets: &AssetServer,
target_node: &mut Node,
target_bg: &mut BackgroundColor,
target_border: &mut BorderColor,
mut target_text: Option<Mut<Text>>,
mut target_text_color: Option<Mut<TextColor>>,
mut target_text_font: Option<Mut<TextFont>>,
mut target_image: Option<Mut<ImageNode>>,
mut target_progress_bar: Option<Mut<ProgressBarState>>,
target_checkbox_state: Option<Mut<CheckboxState>>,
mut target_checkbox_widget: Option<Mut<CheckboxWidget>>,
mut target_text_input_state: Option<Mut<TextInputState>>,
mut target_text_input_widget: Option<Mut<TextInputWidget>>,
_target_dropdown_state: Option<Mut<DropdownState>>,
children: Option<&Children>,
child_parts: &mut Query<ChildPartsQuery, Without<Element>>,
) {
*target_node = props_override.node.clone();
target_bg.0 = props_override
.bg_color
.or(base_props.bg_color)
.unwrap_or(Color::NONE);
*target_border = props_override
.border_color
.or(base_props.border_color)
.unwrap_or_default();
let base_slider = match &base_props.widget {
ElementWidget::Slider(base) => Some(base),
_ => None,
};
let base_progress_bar = match &base_props.widget {
ElementWidget::ProgressBar(base) => Some(base),
_ => None,
};
let base_checkbox = match &base_props.widget {
ElementWidget::Checkbox(base) => Some(base),
_ => None,
};
let base_text_input = match &base_props.widget {
ElementWidget::TextInput(base) => Some(base),
_ => None,
};
let base_tooltip = match &base_props.widget {
ElementWidget::Tooltip(base) => Some(base),
_ => None,
};
let base_divider = match &base_props.widget {
ElementWidget::Divider(base) => Some(base),
_ => None,
};
let base_scroll_view = match &base_props.widget {
ElementWidget::ScrollView(base) => Some(base),
_ => None,
};
let base_dropdown = match &base_props.widget {
ElementWidget::Dropdown(base) => Some(base),
_ => None,
};
match &props_override.widget {
ElementWidget::Node(..) | ElementWidget::Button(..) => {}
ElementWidget::Dropdown(widget) => {
let bg = widget
.bg_color
.or_else(|| base_dropdown.and_then(|b| b.bg_color))
.unwrap_or(Color::srgb(0.2, 0.2, 0.2));
let menu_bg = widget
.menu_bg_color
.or_else(|| base_dropdown.and_then(|b| b.menu_bg_color))
.unwrap_or(Color::srgb(0.15, 0.15, 0.15));
let active_bg = props_override
.bg_color
.or(base_props.bg_color)
.unwrap_or(bg);
if let Some(children) = children {
for child in children.iter() {
if let Ok(mut child_item) = child_parts.get_mut(child) {
if child_item.dropdown_trigger.is_some() {
if let Some(ref mut trigger_bg) = child_item.bg {
trigger_bg.0 = active_bg;
}
} else if child_item.dropdown_menu.is_some()
&& let Some(ref mut menu_bg_ref) = child_item.bg
{
menu_bg_ref.0 = menu_bg;
}
}
}
}
}
ElementWidget::ScrollView(widget) => {
let bg = widget
.bg_color
.or_else(|| base_scroll_view.and_then(|b| b.bg_color))
.unwrap_or(Color::NONE);
let active_bg = props_override
.bg_color
.or(base_props.bg_color)
.unwrap_or(bg);
target_bg.0 = active_bg;
if let Some(children) = children {
for child in children.iter() {
let mut area_entities = Vec::new();
if let Ok(child_item) = child_parts.get(child) {
if child_item.scroll_area.is_some() {
area_entities.push(child);
} else if let Some(sub_children) = child_item.children {
for sub_child in sub_children.iter() {
if let Ok(sub_item) = child_parts.get(sub_child)
&& sub_item.scroll_area.is_some()
{
area_entities.push(sub_child);
}
}
}
}
for area_e in area_entities {
if let Ok(mut item) = child_parts.get_mut(area_e)
&& let Some(ref mut area_bg) = item.bg
{
area_bg.0 = active_bg;
}
}
}
}
}
ElementWidget::Divider(widget) => {
let color = widget
.color
.or_else(|| base_divider.and_then(|b| b.color))
.unwrap_or(Color::srgb(0.25, 0.25, 0.28));
target_bg.0 = props_override
.bg_color
.or(base_props.bg_color)
.unwrap_or(color);
}
ElementWidget::Text(widget) => {
if let Some(ref mut text) = target_text {
**text = Text::new(&widget.content);
}
if let Some(ref mut text_color) = target_text_color {
text_color.0 = widget.color.unwrap_or(Color::WHITE);
}
if let Some(ref mut text_font) = target_text_font {
text_font.font = widget
.font
.as_ref()
.map(|f| FontSource::Handle(assets.load(f)))
.unwrap_or_default();
text_font.font_size = widget.font_size.unwrap_or_default();
text_font.weight = widget.font_weight.unwrap_or_default();
text_font.width = widget.font_width.unwrap_or_default();
text_font.style = widget.font_style.unwrap_or_default();
}
}
ElementWidget::Image(widget) => {
if let Some(ref mut image) = target_image {
image.image = assets.load(&widget.src);
image.color = widget.color.unwrap_or(Color::WHITE);
image.flip_x = widget.flip_x.unwrap_or_default();
image.flip_y = widget.flip_y.unwrap_or_default();
image.rect = widget.rect;
image.image_mode = widget.mode.clone().unwrap_or_default();
image.visual_box = widget.visual_box.unwrap_or_default();
}
}
ElementWidget::Checkbox(widget) => {
if let Some(ref mut cb_widget) = target_checkbox_widget {
**cb_widget = widget.clone();
}
if let Some(children) = children {
let check_color = widget
.check_color
.or_else(|| base_checkbox.and_then(|b| b.check_color))
.unwrap_or(Color::WHITE);
let is_checked = target_checkbox_state
.as_ref()
.map(|s| s.0)
.unwrap_or(widget.state);
for child in children.iter() {
if let Ok(mut child_item) = child_parts.get_mut(child)
&& child_item.checkbox_checkmark.is_some()
{
if let Some(ref mut text) = child_item.text {
**text = Text::new(&widget.symbol);
}
if let Some(ref mut text_color) = child_item.text_color {
text_color.0 = check_color;
}
if let Some(ref mut node) = child_item.node {
node.display = if is_checked {
Display::Flex
} else {
Display::None
};
}
}
}
}
}
ElementWidget::TextInput(widget) => {
if let Some(ref mut ti_widget) = target_text_input_widget {
**ti_widget = widget.clone();
}
if let Some(ref mut ti_state) = target_text_input_state {
ti_state.placeholder = widget.placeholder.clone();
}
if let Some(children) = children {
let text_color = widget
.text_color
.or_else(|| base_text_input.and_then(|b| b.text_color))
.unwrap_or(Color::WHITE);
let placeholder_color = widget
.placeholder_color
.or_else(|| base_text_input.and_then(|b| b.placeholder_color))
.unwrap_or(Color::srgb(0.5, 0.5, 0.5));
let is_empty = target_text_input_state
.as_ref()
.map(|s| s.value.is_empty())
.unwrap_or_else(|| widget.initial_value.is_empty());
let active_text_color = if is_empty {
placeholder_color
} else {
text_color
};
for child in children.iter() {
if let Ok(mut child_item) = child_parts.get_mut(child) {
if child_item.text_input_text.is_some() {
if let Some(ref mut color) = child_item.text_color {
color.0 = active_text_color;
}
if let Some(ref mut font) = child_item.text_font {
font.font_size = FontSize::Px(widget.font_size);
}
if is_empty && let Some(ref mut text) = child_item.text {
**text = Text::new(&widget.placeholder);
}
} else if child_item.text_input_cursor.is_some() {
if let Some(ref mut cursor_node) = child_item.node {
cursor_node.height = Val::Px(widget.font_size);
}
if let Some(ref mut cursor_bg) = child_item.bg {
cursor_bg.0 = text_color;
}
}
}
}
}
}
ElementWidget::Slider(widget) => {
if let Some(children) = children {
let track_color = widget
.track_color
.or_else(|| base_slider.and_then(|b| b.track_color))
.unwrap_or(Color::srgb(0.16, 0.17, 0.20));
let fill_color = widget
.fill_color
.or_else(|| base_slider.and_then(|b| b.fill_color))
.unwrap_or(Color::srgb(0.38, 0.69, 0.94));
let thumb_color = widget
.thumb_color
.or_else(|| base_slider.and_then(|b| b.thumb_color))
.unwrap_or(Color::WHITE);
let track_h = widget
.track_height
.or_else(|| base_slider.and_then(|b| b.track_height));
let thumb_s = widget
.thumb_size
.or_else(|| base_slider.and_then(|b| b.thumb_size));
for child in children.iter() {
if let Ok(mut child_item) = child_parts.get_mut(child) {
if child_item.slider_track.is_some() {
if let Some(ref mut bg) = child_item.bg {
bg.0 = track_color;
}
if let (Some(node), Some(h)) = (&mut child_item.node, track_h) {
node.height = Val::Px(h);
node.border_radius = BorderRadius::all(Val::Px(h / 2.0));
}
} else if child_item.slider_fill.is_some() {
if let Some(ref mut bg) = child_item.bg {
bg.0 = fill_color;
}
if let (Some(node), Some(h)) = (&mut child_item.node, track_h) {
node.height = Val::Px(h);
node.border_radius = BorderRadius::all(Val::Px(h / 2.0));
}
} else if child_item.slider_thumb.is_some() {
if let Some(ref mut bg) = child_item.bg {
bg.0 = thumb_color;
}
if let (Some(node), Some(s)) = (&mut child_item.node, thumb_s) {
node.width = Val::Px(s);
node.height = Val::Px(s);
node.border_radius = BorderRadius::all(Val::Px(s / 2.0));
node.margin = UiRect::left(Val::Px(-s / 2.0));
}
}
}
}
}
}
ElementWidget::ProgressBar(widget) => {
if let Some(ref mut pb) = target_progress_bar {
pb.min = widget.min;
pb.max = widget.max;
pb.value = widget.value;
}
if let Some(children) = children {
let track_color = widget
.track_color
.or_else(|| base_progress_bar.and_then(|b| b.track_color))
.unwrap_or(Color::srgb(0.16, 0.17, 0.20));
let fill_color = widget
.fill_color
.or_else(|| base_progress_bar.and_then(|b| b.fill_color))
.unwrap_or(Color::srgb(0.38, 0.69, 0.94));
let track_h = widget
.track_height
.or_else(|| base_progress_bar.and_then(|b| b.track_height));
for child in children.iter() {
if let Ok(mut child_item) = child_parts.get_mut(child) {
if child_item.progress_track.is_some() {
if let Some(ref mut bg) = child_item.bg {
bg.0 = track_color;
}
if let (Some(node), Some(h)) = (&mut child_item.node, track_h) {
node.height = Val::Px(h);
node.border_radius = BorderRadius::all(Val::Px(h / 2.0));
}
} else if child_item.progress_fill.is_some() {
if let Some(ref mut bg) = child_item.bg {
bg.0 = fill_color;
}
if let (Some(node), Some(h)) = (&mut child_item.node, track_h) {
node.height = Val::Px(h);
node.border_radius = BorderRadius::all(Val::Px(h / 2.0));
}
}
}
}
}
}
ElementWidget::Tooltip(widget) => {
if let Some(children) = children {
let bg_color = widget
.bg_color
.or_else(|| base_tooltip.and_then(|b| b.bg_color))
.unwrap_or(Color::srgb(0.1, 0.1, 0.12));
let text_color = widget
.text_color
.or_else(|| base_tooltip.and_then(|b| b.text_color))
.unwrap_or(Color::WHITE);
for child in children.iter() {
if let Ok(mut child_item) = child_parts.get_mut(child) {
if child_item.tooltip_popup.is_some() {
if let Some(ref mut popup_bg) = child_item.bg {
popup_bg.0 = bg_color;
}
} else if child_item.tooltip_text.is_some() {
if let Some(ref mut text) = child_item.text {
**text = Text::new(&widget.text);
}
if let Some(ref mut color) = child_item.text_color {
color.0 = text_color;
}
if let Some(ref mut font) = child_item.text_font {
font.font_size = widget.font_size;
}
}
}
}
}
}
}
}