Newer
Older
//! This example demonstrates how to use the provider/consumer pattern for passing props down
//! to multiple descendants.
//!
//! The problem we'll be solving here is adding support for theming.
//!
//! One reason the provider/consumer pattern might be favored over a global state is that it allows
//! for better specificity and makes local contexts much easier to manage. In the case of theming,
//! this allows us to have multiple active themes, even if they are nested within each other!
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
use kayak_ui::prelude::{widgets::*, KStyle, *};
/// The color theme struct we will be using across our demo widgets
#[derive(Component, Debug, Default, Clone, PartialEq)]
struct Theme {
name: String,
primary: Color,
secondary: Color,
background: Color,
}
impl Theme {
fn vampire() -> Self {
Self {
name: "Vampire".to_string(),
primary: Color::rgba(1.0, 0.475, 0.776, 1.0),
secondary: Color::rgba(0.641, 0.476, 0.876, 1.0),
background: Color::rgba(0.157, 0.165, 0.212, 1.0),
}
}
fn solar() -> Self {
Self {
name: "Solar".to_string(),
primary: Color::rgba(0.514, 0.580, 0.588, 1.0),
secondary: Color::rgba(0.149, 0.545, 0.824, 1.0),
background: Color::rgba(0.026, 0.212, 0.259, 1.0),
}
}
fn vector() -> Self {
Self {
name: "Vector".to_string(),
primary: Color::rgba(0.533, 1.0, 0.533, 1.0),
secondary: Color::rgba(0.098, 0.451, 0.098, 1.0),
background: Color::rgba(0.004, 0.059, 0.004, 1.0),
}
}
}
#[derive(Component, Debug, Default, Clone, PartialEq)]
struct ThemeButton {
pub theme: Theme,
}
impl Widget for ThemeButton {}
#[derive(Bundle)]
pub struct ThemeButtonBundle {
theme_button: ThemeButton,
widget_name: WidgetName,
}
impl Default for ThemeButtonBundle {
fn default() -> Self {
Self {
theme_button: Default::default(),
widget_name: ThemeButton::default().get_name(),
}
}
}
fn update_theme_button(
In((widget_context, theme_button_entity)): In<(KayakWidgetContext, Entity)>,
mut commands: Commands,
query: Query<&ThemeButton>,
mut context_query: Query<&mut Theme>,
if let Ok(theme_button) = query.get(theme_button_entity) {
if let Some(theme_context_entity) =
widget_context.get_context_entity::<Theme>(theme_button_entity)
{
if let Ok(theme) = context_query.get_mut(theme_context_entity) {
let mut box_style = KStyle {
width: StyleProp::Value(Units::Pixels(30.0)),
height: StyleProp::Value(Units::Pixels(30.0)),
background_color: StyleProp::Value(theme_button.theme.primary),
..Default::default()
};
if theme_button.theme.name == theme.name {
box_style.top = StyleProp::Value(Units::Pixels(3.0));
box_style.left = StyleProp::Value(Units::Pixels(3.0));
box_style.bottom = StyleProp::Value(Units::Pixels(3.0));
box_style.right = StyleProp::Value(Units::Pixels(3.0));
box_style.width = StyleProp::Value(Units::Pixels(24.0));
box_style.height = StyleProp::Value(Units::Pixels(24.0));
}
let parent_id = Some(theme_button_entity);
rsx! {
<BackgroundBundle
styles={box_style}
on_event={OnEvent::new(
move |In((event_dispatcher_context, _, event, _entity)): In<(
EventDispatcherContext,
WidgetState,
Event,
Entity,
)>,
query: Query<&ThemeButton>,
mut context_query: Query<&mut Theme>,
| {
match event.event_type {
EventType::Click(..) => {
if let Ok(button) = query.get(theme_button_entity) {
if let Ok(mut context_theme) = context_query.get_mut(theme_context_entity) {
*context_theme = button.theme.clone();
}
},
_ => {}
}
(event_dispatcher_context, event)
},
)}
/>
#[derive(Component, Debug, Default, Clone, PartialEq)]
struct ThemeSelector;
impl Widget for ThemeSelector {}
#[derive(Bundle)]
pub struct ThemeSelectorBundle {
theme_selector: ThemeSelector,
widget_name: WidgetName,
}
impl Default for ThemeSelectorBundle {
fn default() -> Self {
Self {
theme_selector: Default::default(),
styles: KStyle {
height: StyleProp::Value(Units::Auto),
padding_bottom: Units::Pixels(40.0).into(),
..Default::default()
},
widget_name: ThemeSelector::default().get_name(),
}
}
}
fn update_theme_selector(
In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
query: Query<&ThemeSelector>,
if query.get(entity).is_ok() {
let button_container_style = KStyle {
layout_type: StyleProp::Value(LayoutType::Row),
width: StyleProp::Value(Units::Stretch(1.0)),
height: StyleProp::Value(Units::Auto),
top: StyleProp::Value(Units::Pixels(5.0)),
..Default::default()
};
let vampire_theme = Theme::vampire();
let solar_theme = Theme::solar();
let vector_theme = Theme::vector();
let parent_id = Some(entity);
rsx! {
<ElementBundle styles={button_container_style}>
<ThemeButtonBundle theme_button={ThemeButton { theme: vampire_theme }} />
<ThemeButtonBundle theme_button={ThemeButton { theme: solar_theme }} />
<ThemeButtonBundle theme_button={ThemeButton { theme: vector_theme }} />
</ElementBundle>
#[derive(Component, Debug, Default, Clone, PartialEq, Eq)]
pub struct ThemeDemo {
is_root: bool,
context_entity: Option<Entity>,
}
impl Widget for ThemeDemo {}
#[derive(Bundle)]
pub struct ThemeDemoBundle {
theme_demo: ThemeDemo,
widget_name: WidgetName,
}
impl Default for ThemeDemoBundle {
fn default() -> Self {
Self {
theme_demo: Default::default(),
styles: KStyle::default(),
widget_name: ThemeDemo::default().get_name(),
}
}
}
fn update_theme_demo(
In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
mut query_set: Query<&mut ThemeDemo>,
if let Ok(mut theme_demo) = query_set.get_mut(entity) {
if let Some(theme_context_entity) = widget_context.get_context_entity::<Theme>(entity) {
if let Ok(theme) = theme_context.get(theme_context_entity) {
let select_lbl = if theme_demo.is_root {
format!("Select Theme (Current: {})", theme.name)
} else {
format!("Select A Different Theme (Current: {})", theme.name)
};
if theme_demo.is_root && theme_demo.context_entity.is_none() {
let theme_entity = commands.spawn(Theme::vector()).id();
theme_demo.context_entity = Some(theme_entity);
let context_entity = if let Some(entity) = theme_demo.context_entity {
entity
} else {
Entity::from_raw(1000000)
};
let text_styles = KStyle {
color: StyleProp::Value(theme.primary),
height: StyleProp::Value(Units::Pixels(28.0)),
..Default::default()
};
let btn_style = KStyle {
background_color: StyleProp::Value(theme.secondary),
width: StyleProp::Value(Units::Stretch(0.75)),
height: StyleProp::Value(Units::Pixels(32.0)),
top: StyleProp::Value(Units::Pixels(5.0)),
left: StyleProp::Value(Units::Stretch(1.0)),
right: StyleProp::Value(Units::Stretch(1.0)),
..Default::default()
};
let parent_id = Some(entity);
rsx! {
styles={KStyle {
height: StyleProp::Value(Units::Pixels(28.0)),
..Default::default()
}}
text={TextProps {
content: select_lbl,
size: 14.0,
line_height: Some(28.0),
..Default::default()
}}
/>
<ThemeSelectorBundle />
<BackgroundBundle
styles={KStyle {
background_color: StyleProp::Value(theme.background),
top: StyleProp::Value(Units::Pixels(15.0)),
width: StyleProp::Value(Units::Stretch(1.0)),
height: StyleProp::Value(Units::Stretch(1.0)),
..Default::default()
}}
>
content: "Lorem ipsum dolor...".into(),
size: 12.0,
button={KButton { text: "BUTTON".into(), ..Default::default() }}
styles={btn_style}
{
if theme_demo.is_root {
widget_context.set_context_entity::<Theme>(
parent_id,
context_entity,
);
constructor! {
<ElementBundle
styles={KStyle {
top: StyleProp::Value(Units::Pixels(10.0)),
left: StyleProp::Value(Units::Pixels(10.0)),
bottom: StyleProp::Value(Units::Pixels(10.0)),
right: StyleProp::Value(Units::Pixels(10.0)),
..Default::default()
}}
>
<ThemeDemoBundle />
</ElementBundle>
}
fn startup(
mut commands: Commands,
mut font_mapping: ResMut<FontMapping>,
asset_server: Res<AssetServer>,
) {
font_mapping.set_default(asset_server.load("roboto.kayak_font"));
// Camera 2D forces a clear pass in bevy.
// We do this because our scene is not rendering anything else.
commands.spawn(Camera2dBundle::default());
let mut widget_context = KayakRootContext::new();
widget_context.add_plugin(KayakWidgetsContextPlugin);
widget_context.add_widget_data::<ThemeDemo, EmptyState>();
widget_context.add_widget_data::<ThemeButton, EmptyState>();
widget_context.add_widget_data::<ThemeSelector, EmptyState>();
widget_context.add_widget_system(
ThemeDemo::default().get_name(),
widget_update_with_context::<ThemeDemo, EmptyState, Theme>,
update_theme_demo,
);
widget_context.add_widget_system(
ThemeButton::default().get_name(),
widget_update_with_context::<ThemeButton, EmptyState, Theme>,
update_theme_button,
);
widget_context.add_widget_system(
ThemeSelector::default().get_name(),
widget_update_with_context::<ThemeSelector, EmptyState, Theme>,
update_theme_selector,
);
let parent_id = None;
rsx! {
<KayakAppBundle>
{
let theme_entity = commands.spawn(Theme::vampire()).id();
widget_context.set_context_entity::<Theme>(parent_id, theme_entity);
}
<WindowBundle
window={KWindow {
title: "Context Example".into(),
draggable: true,
initial_position: Vec2::ZERO,
size: Vec2::new(350.0, 400.0),
..Default::default()
}}
>
<ThemeDemoBundle
theme_demo={ThemeDemo {
is_root: true,
context_entity: None,
}}
/>
</WindowBundle>
</KayakAppBundle>
commands.spawn(UICameraBundle::new(widget_context));
.add_plugin(KayakContextPlugin)
.add_plugin(KayakWidgets)
.add_startup_system(startup)
.run()
}