use bevy::prelude::*;
use bevy::ui_widgets::observe;
use bevy::window::{CursorIcon, SystemCursorIcon};
use crate::utils::update_focus_state_for_widgets_on_click;
use crate::{events::*, consts::*, utils::*};
use super::*;
#[derive(Component)]
pub struct MakaraLink;
#[derive(Component)]
pub struct MakaraLinkText;
pub struct LinkWidget<'a> {
pub class: &'a mut Class,
pub style: WidgetStyle<'a>,
pub text: ChildText<'a>
}
impl<'a> SetText for LinkWidget<'a> {
fn set_text(&mut self, text: &str) {
self.text.value.0 = text.to_string();
}
}
type IsLinkOnly = (
(
With<MakaraLink>,
Without<MakaraCheckbox>,
Without<MakaraCheckboxButton>,
Without<MakaraColumn>,
Without<MakaraRow>,
Without<MakaraRoot>,
Without<MakaraButton>,
Without<MakaraDropdown>,
Without<MakaraDropdownOverlay>,
Without<MakaraCircular>,
Without<MakaraImage>,
Without<MakaraModal>,
Without<MakaraModalBackdrop>,
),
(
Without<MakaraProgressBar>,
Without<MakaraRadio>,
Without<MakaraRadioGroup>,
Without<MakaraScroll>,
Without<MakaraScrollbar>,
Without<MakaraTextInput>,
Without<MakaraTextInputCursor>,
Without<MakaraSlider>,
Without<MakaraSliderThumb>,
Without<MakaraSelect>,
Without<MakaraSelectOverlay>,
)
);
type IsLinkTextOnly = (
With<MakaraLinkText>,
Without<MakaraCheckboxText>,
Without<MakaraButtonText>
);
#[derive(SystemParam)]
pub struct LinkQuery<'w, 's> {
pub id: Query<'w, 's, (Entity, &'static Id), With<MakaraLink>>,
pub class: Query<'w, 's, (Entity, &'static mut Class), IsLinkOnly>,
pub style: StyleQuery<'w, 's, IsLinkOnly>,
pub text: TextQueryAsChild<'w, 's, IsLinkTextOnly>,
pub children: Query<'w, 's, &'static Children>
}
impl<'w, 's> WidgetQuery<'w, 's> for LinkQuery<'w, 's> {
type WidgetView<'a> = LinkWidget<'a> where Self: 'a;
fn get_components<'a>(&'a mut self, entity: Entity) -> Option<Self::WidgetView<'a>> {
let LinkQuery { id: _, class, style, text, children } = self;
let children_list = children.get(entity).ok()?;
for child in children_list {
if text.query.get_mut(*child).is_err() {
continue;
}
let text_comp = text.query.get_mut(*child).unwrap();
let (text, text_font, text_layout, text_color) = text_comp;
let style_bundle = style.query.get_mut(entity).ok()?;
let (node, bg, border_color, shadow, z_index) = style_bundle;
return Some(LinkWidget {
class: class.get_mut(entity).ok()?.1.into_inner(),
style: WidgetStyle {
node: node.into_inner(),
background_color: bg.into_inner(),
border_color: border_color.into_inner(),
shadow: shadow.into_inner(),
z_index: z_index.into_inner(),
},
text: ChildText {
value: text.into_inner(),
font: text_font.into_inner(),
layout: text_layout.into_inner(),
color: text_color.into_inner(),
}
});
}
None
}
fn find_by_id<'a>(&'a mut self, target_id: &str) -> Option<Self::WidgetView<'a>> {
let entity = self.id.iter()
.find(|(_, id)| id.0 == target_id)
.map(|(e, _)| e)?;
self.get_components(entity)
}
fn find_by_entity<'a>(&'a mut self, target_entity: Entity) -> Option<Self::WidgetView<'a>> {
self.get_components(target_entity)
}
fn find_by_class(&self, target_class: &str) -> Vec<Entity> {
self.class.iter()
.filter(|(_, class)| class.0.split(" ").any(|word| word == target_class))
.map(|(e, _)| e)
.collect()
}
}
#[derive(Bundle)]
pub struct LinkBundle {
pub id_class: IdAndClass,
pub text_bundle: TextBundle,
pub style: ContainerStyle,
pub tooltip_bundle: TooltipBundle
}
impl Default for LinkBundle {
fn default() -> Self {
let mut text_bundle = TextBundle::default();
text_bundle.text_style.color.0 = DEFAULT_LINK_COLOR;
let style = ContainerStyle {
shadow: BoxShadow::default(),
border_color: BorderColor {
bottom: DEFAULT_LINK_COLOR,
..default()
},
node: Node {
border: UiRect::bottom(px(1)),
padding: UiRect::bottom(px(-2)),
..default()
},
..default()
};
let tooltip_bundle = TooltipBundle::default();
let id_class = IdAndClass::default();
Self { text_bundle, style, tooltip_bundle, id_class }
}
}
impl LinkBundle {
pub fn text_style(mut self, style: TextStyle) -> Self {
self.text_bundle.text_style = style;
self
}
pub fn underline(mut self, value: bool) -> Self {
if !value {
self.style.border_color.bottom = Color::NONE;
}
else {
self.style.border_color.bottom = DEFAULT_LINK_COLOR;
}
self
}
pub fn color(mut self, color: impl IntoColor) -> Self {
let color = color.into_color();
self.text_bundle.text_style.color.0 = color;
if self.style.border_color.bottom != Color::NONE {
self.style.border_color.bottom = color;
}
self
}
pub fn font_size(mut self, size: f32) -> Self {
self.text_bundle.text_style.font.font_size = size;
self
}
}
impl Widget for LinkBundle {
fn build(mut self) -> impl Bundle {
process_built_in_spacing_class(&self.id_class.class, &mut self.style.node);
(
self.id_class,
self.style,
MakaraLink,
MakaraWidget,
WidgetFocus(false),
children![
(self.text_bundle, MakaraText, MakaraLinkText),
self.tooltip_bundle.build()
],
observe(on_link_click),
observe(on_link_mouse_over),
observe(on_mouse_out)
)
}
}
impl SetContainerStyle for LinkBundle {
fn container_style(&mut self) -> &mut ContainerStyle {
&mut self.style
}
}
impl SetToolTip for LinkBundle {
fn set_tooltip(&mut self) -> &mut TooltipBundle {
&mut self.tooltip_bundle
}
}
impl SetIdAndClass for LinkBundle {
fn id_and_class(&mut self) -> &mut IdAndClass {
&mut self.id_class
}
}
pub fn link(text: &str) -> LinkBundle {
let mut bundle = LinkBundle::default();
bundle.text_bundle.text.0 = text.to_string();
bundle
}
fn on_link_click(
mut click: On<Pointer<Click>>,
mut commands: Commands,
mut widgets: Query<(Entity, &mut WidgetFocus)>,
links: Query<&Children, With<MakaraLink>>,
link_texts: Query<&Text>
) {
for children in links.iter() {
for child in children {
if let Ok(text) = link_texts.get(*child) {
let _ = webbrowser::open(&text.0);
break;
}
}
}
update_focus_state_for_widgets_on_click(click.entity, &mut widgets);
commands.trigger(Clicked { entity: click.entity });
click.propagate(false);
}
fn on_link_mouse_over(
mut over: On<Pointer<Over>>,
mut commands: Commands,
mut tooltips: Query<
(&mut Node, &ComputedNode, &TooltipPosition, &UseTooltip),
With<MakaraTooltip>
>,
links: Query<
(&Children, &UiTransform, &ComputedNode),
With<MakaraLink>
>,
window: Single<Entity, With<Window>>,
) {
if let Ok((children, transform, computed)) = links.get(over.entity) {
show_or_hide_tooltip(true, &mut tooltips, Some(computed), Some(transform), children);
}
let cursor_icon = CursorIcon::System(SystemCursorIcon::Pointer);
commands.entity(*window).insert(cursor_icon);
commands.trigger(MouseOver { entity: over.entity });
over.propagate(false);
}
pub(crate) fn detect_link_built(
mut commands: Commands,
q: Query<Entity, Added<MakaraLink>>
) {
for entity in q.iter() {
commands.trigger(WidgetBuilt {
entity
});
}
}