Skip to content
Snippets Groups Projects
Commit e2b79416 authored by MrGVSV's avatar MrGVSV
Browse files

Removed add/remove tabs due to bugs

Main bug is that removals or additions to the widget tree are not
properly reflected in the focuss tree (i.e. out of order)
parent d6263f15
No related branches found
No related tags found
No related merge requests found
...@@ -2,7 +2,7 @@ use kayak_ui::{ ...@@ -2,7 +2,7 @@ use kayak_ui::{
core::{ core::{
render_command::RenderCommand, render_command::RenderCommand,
styles::{LayoutType, Style, StyleProp, Units}, styles::{LayoutType, Style, StyleProp, Units},
Bound, EventType, Handler, KeyCode, OnEvent, rsx, use_state, widget, Bound, EventType, OnEvent, rsx, use_state, widget,
}, },
widgets::{Background, Text}, widgets::{Background, Text},
}; };
...@@ -16,17 +16,17 @@ enum TabHoverState { ...@@ -16,17 +16,17 @@ enum TabHoverState {
Active, Active,
} }
/// The actual tab, displayed in a [TabBar](crate::tab_bar::TabBar)
#[widget] #[widget]
pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_request_remove: Handler) { pub fn Tab(context: &mut KayakContext, content: String, selected: bool) {
let theme = context.create_consumer::<TabTheme>().unwrap_or_default(); let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
let (focus_state, set_focus_state, ..) = use_state!(false);
let (hover_state, set_hover_state, ..) = use_state!(TabHoverState::None); let (hover_state, set_hover_state, ..) = use_state!(TabHoverState::None);
match hover_state { match hover_state {
TabHoverState::Inactive if selected => set_hover_state(TabHoverState::Active), TabHoverState::Inactive if selected => set_hover_state(TabHoverState::Active),
TabHoverState::Active if !selected => set_hover_state(TabHoverState::Inactive), TabHoverState::Active if !selected => set_hover_state(TabHoverState::Inactive),
_ => {} _ => {}
}; };
let (focus_state, set_focus_state, ..) = use_state!(false);
let (is_exit_hovered, set_is_exit_hovered, ..) = use_state!(false);
let event_handler = OnEvent::new(move |_, event| { let event_handler = OnEvent::new(move |_, event| {
match event.event_type { match event.event_type {
...@@ -50,36 +50,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque ...@@ -50,36 +50,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque
} }
}); });
let exit_btn_event_handler = OnEvent::new(move |_, event| {
match event.event_type {
EventType::Hover => {
set_is_exit_hovered(true);
}
EventType::MouseOut => {
set_is_exit_hovered(false);
}
EventType::Focus => {
set_is_exit_hovered(true);
}
EventType::Blur => {
set_is_exit_hovered(false);
}
EventType::Click => {
// Stop propagation so we don't select a deleted tab!
event.stop_propagation();
on_request_remove.call(());
}
EventType::KeyDown(evt) => {
if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space {
// Stop propagation so we don't select a deleted tab!
event.stop_propagation();
on_request_remove.call(());
}
}
_ => {}
}
});
let tab_color = match hover_state { let tab_color = match hover_state {
TabHoverState::None if selected => theme.get().active_tab.normal, TabHoverState::None if selected => theme.get().active_tab.normal,
TabHoverState::None => theme.get().inactive_tab.normal, TabHoverState::None => theme.get().inactive_tab.normal,
...@@ -96,7 +66,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque ...@@ -96,7 +66,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque
..Default::default() ..Default::default()
}; };
let border_width = Units::Pixels(2.0); let border_width = Units::Pixels(2.0);
let border_styles = Style { let border_styles = Style {
background_color: if focus_state { background_color: if focus_state {
...@@ -125,32 +94,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque ...@@ -125,32 +94,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque
..Default::default() ..Default::default()
}; };
let exit_styles = Style {
background_color: if is_exit_hovered {
let mut darkened = theme.get().inactive_tab.hovered;
darkened.r -= 0.025;
darkened.g -= 0.025;
darkened.b -= 0.025;
StyleProp::Value(darkened)
} else {
StyleProp::Value(tab_color)
},
width: StyleProp::Value(Units::Pixels(theme.get().tab_height - 4.0)),
height: StyleProp::Value(Units::Pixels(theme.get().tab_height - 4.0)),
top: StyleProp::Value(Units::Stretch(0.175)),
bottom: StyleProp::Value(Units::Stretch(1.0)),
left: StyleProp::Value(Units::Stretch(1.0)),
..Default::default()
};
let exit_text_styles = Style {
left: StyleProp::Value(Units::Stretch(1.0)),
right: StyleProp::Value(Units::Stretch(1.0)),
top: StyleProp::Value(Units::Stretch(0.35)),
bottom: StyleProp::Value(Units::Stretch(1.0)),
..Default::default()
};
self.styles = Some(Style { self.styles = Some(Style {
render_command: StyleProp::Value(RenderCommand::Layout), render_command: StyleProp::Value(RenderCommand::Layout),
height: StyleProp::Value(Units::Pixels(theme.get().tab_height)), height: StyleProp::Value(Units::Pixels(theme.get().tab_height)),
...@@ -162,9 +105,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque ...@@ -162,9 +105,6 @@ pub fn Tab(context: &mut KayakContext, content: String, selected: bool, on_reque
<Background focusable={Some(true)} on_event={Some(event_handler)} styles={Some(border_styles)}> <Background focusable={Some(true)} on_event={Some(event_handler)} styles={Some(border_styles)}>
<Background styles={Some(bg_styles)}> <Background styles={Some(bg_styles)}>
<Text content={content} size={12.0} styles={Some(text_styles)} /> <Text content={content} size={12.0} styles={Some(text_styles)} />
<Background focusable={Some(true)} on_event={Some(exit_btn_event_handler)} styles={Some(exit_styles)}>
<Text content={"X".to_string()} size={8.0} styles={Some(exit_text_styles)} />
</Background>
</Background> </Background>
</Background> </Background>
} }
......
...@@ -2,18 +2,18 @@ use kayak_ui::{ ...@@ -2,18 +2,18 @@ use kayak_ui::{
core::{ core::{
styles::{LayoutType, Style, StyleProp, Units}, styles::{LayoutType, Style, StyleProp, Units},
Bound, constructor, EventType, Handler, KeyCode, OnEvent, Bound, constructor, EventType, Handler, KeyCode, OnEvent,
rsx, use_state, VecTracker, widget, rsx, VecTracker, widget,
}, },
widgets::{Background, Element, Text}, widgets::Background,
}; };
use crate::tab::Tab; use crate::tab::Tab;
use crate::TabTheme; use crate::TabTheme;
/// A widget displaying a collection of tabs in a horizontal bar
#[widget] #[widget]
pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on_select_tab: Handler<usize>, on_add_tab: Handler, on_remove_tab: Handler<usize>) { pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on_select_tab: Handler<usize>) {
let theme = context.create_consumer::<TabTheme>().unwrap_or_default(); let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
let (is_add_hovered, set_is_add_hovered, ..) = use_state!(false);
let tabs = tabs.iter().enumerate().map(|(index, tab)| { let tabs = tabs.iter().enumerate().map(|(index, tab)| {
let on_select = on_select_tab.clone(); let on_select = on_select_tab.clone();
...@@ -24,6 +24,7 @@ pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on ...@@ -24,6 +24,7 @@ pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on
} }
EventType::KeyDown(evt) => { EventType::KeyDown(evt) => {
if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space { if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space {
// We want the focused tab to also be selected by `Enter` or `Space`
on_select.call(index); on_select.call(index);
} }
} }
...@@ -31,59 +32,11 @@ pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on ...@@ -31,59 +32,11 @@ pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on
} }
}); });
let on_remove = on_remove_tab.clone();
let on_request_remove = Handler::new(move |_| {
on_remove.call(index);
});
constructor! { constructor! {
<Tab content={tab.clone()} on_event={Some(tab_event_handler.clone())} selected={selected == index} on_request_remove={on_request_remove} /> <Tab content={tab.clone()} on_event={Some(tab_event_handler.clone())} selected={selected == index} />
} }
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
let add_btn_event_handler = OnEvent::new(move |_, event| match event.event_type {
EventType::Hover => {
set_is_add_hovered(true);
}
EventType::MouseOut => {
set_is_add_hovered(false);
}
EventType::Focus => {
set_is_add_hovered(true);
}
EventType::Blur => {
set_is_add_hovered(false);
}
EventType::Click => {
on_add_tab.call(());
}
EventType::KeyDown(evt) => {
if evt.key() == KeyCode::Return || evt.key() == KeyCode::Space {
on_add_tab.call(());
}
}
_ => {}
});
let add_btn_styles = Style {
height: StyleProp::Value(Units::Pixels(theme.get().tab_height)),
width: StyleProp::Value(Units::Pixels(theme.get().tab_height)),
border_radius: StyleProp::Value((10.0, 10.0, 10.0, 10.0)),
..Default::default()
};
let add_btn_text_styles = Style {
color: if is_add_hovered {
StyleProp::Value(theme.get().text.hovered)
} else {
StyleProp::Value(theme.get().text.normal)
},
left: StyleProp::Value(Units::Stretch(1.0)),
right: StyleProp::Value(Units::Stretch(1.0)),
height: StyleProp::Value(Units::Percentage(100.0)),
width: StyleProp::Value(Units::Percentage(100.0)),
..Default::default()
};
let background_styles = Style { let background_styles = Style {
layout_type: StyleProp::Value(LayoutType::Row), layout_type: StyleProp::Value(LayoutType::Row),
background_color: StyleProp::Value(theme.get().bg), background_color: StyleProp::Value(theme.get().bg),
...@@ -95,9 +48,6 @@ pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on ...@@ -95,9 +48,6 @@ pub fn TabBar(context: &mut KayakContext, tabs: Vec<String>, selected: usize, on
rsx! { rsx! {
<Background styles={Some(background_styles)}> <Background styles={Some(background_styles)}>
<VecTracker data={tabs} /> <VecTracker data={tabs} />
<Element focusable={Some(true)} on_event={Some(add_btn_event_handler)} styles={Some(add_btn_styles)}>
<Text content={"+".to_string()} size={16.0} styles={Some(add_btn_text_styles)} />
</Element>
</Background> </Background>
} }
} }
\ No newline at end of file
...@@ -13,12 +13,17 @@ use crate::tab_content::TabContent; ...@@ -13,12 +13,17 @@ use crate::tab_content::TabContent;
#[derive(Debug, Default, Clone, PartialEq)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct TabData { pub struct TabData {
/// The name of this tab
pub name: String, pub name: String,
/// The content to display for this tab, wrapped in a [Fragment]
pub content: Fragment, pub content: Fragment,
} }
/// The actual tab container widget.
///
/// This houses both the tab bar and its content.
#[widget] #[widget]
pub fn TabBox(context: &mut KayakContext, tabs: Vec<TabData>, initial_tab: usize, on_add_tab: Handler, on_remove_tab: Handler<usize>) { pub fn TabBox(context: &mut KayakContext, tabs: Vec<TabData>, initial_tab: usize) {
let theme = context.create_consumer::<TabTheme>().unwrap_or_default(); let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
let (selected, set_selected, ..) = use_state!(initial_tab); let (selected, set_selected, ..) = use_state!(initial_tab);
...@@ -41,7 +46,7 @@ pub fn TabBox(context: &mut KayakContext, tabs: Vec<TabData>, initial_tab: usize ...@@ -41,7 +46,7 @@ pub fn TabBox(context: &mut KayakContext, tabs: Vec<TabData>, initial_tab: usize
rsx! { rsx! {
<> <>
<TabBar tabs={tab_names} selected={selected} on_select_tab={on_select_tab} on_add_tab={on_add_tab} on_remove_tab={on_remove_tab} /> <TabBar tabs={tab_names} selected={selected} on_select_tab={on_select_tab} />
<TabContent tabs={tab_content} selected={selected} /> <TabContent tabs={tab_content} selected={selected} />
</> </>
} }
......
...@@ -9,11 +9,13 @@ use kayak_ui::{ ...@@ -9,11 +9,13 @@ use kayak_ui::{
use crate::TabTheme; use crate::TabTheme;
/// A widget that displays the selected tab's content
#[widget] #[widget]
pub fn TabContent(context: &mut KayakContext, tabs: Vec<Fragment>, selected: usize) { pub fn TabContent(context: &mut KayakContext, tabs: Vec<Fragment>, selected: usize) {
let theme = context.create_consumer::<TabTheme>().unwrap_or_default(); let theme = context.create_consumer::<TabTheme>().unwrap_or_default();
if selected >= tabs.len() { if selected >= tabs.len() {
// Invalid tab -> don't do anything
return; return;
} }
......
//! This example demonstrates how one might create a tab system
//!
//! Additionally, it showcases focus navigation. Press `Tab` and `Shift + Tab` to move
//! between focusable widgets. This example also sets it up so that `Enter` or `Space`
//! can be used in place of a normal click.
use bevy::{ use bevy::{
prelude::{App as BevyApp, AssetServer, Commands, Res, ResMut}, prelude::{App as BevyApp, AssetServer, Commands, Res, ResMut},
window::WindowDescriptor, window::WindowDescriptor,
...@@ -8,7 +14,7 @@ use kayak_ui::{ ...@@ -8,7 +14,7 @@ use kayak_ui::{
bevy::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle}, bevy::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle},
core::{ core::{
styles::{Style, StyleProp, Units}, styles::{Style, StyleProp, Units},
Children, Color, constructor, Handler, Index, render, rsx, use_state, widget Children, Color, constructor, Index, render, rsx, widget
}, },
widgets::{App, Text, Window}, widgets::{App, Text, Window},
}; };
...@@ -34,11 +40,14 @@ fn TabDemo() { ...@@ -34,11 +40,14 @@ fn TabDemo() {
..Default::default() ..Default::default()
}; };
let (count, set_count, ..) = use_state!(0); // This is not the most ideal way to generate tabs. For one, the `content` has no access to its actual context
let (tabs, set_tabs, ..) = use_state!(vec![ // (i.e. where it actually exists in the hierarchy). Additionally, it would be better if tabs were created as
// children of `TabBox`. These are issues that will be addressed in the future, so for now, this will work.
let tabs = vec![
TabData { TabData {
name: "Tab 1".to_string(), name: "Tab 1".to_string(),
content: { content: {
// This is a temporary hack to prevent the `constructor` macro from throwing an error
let children = Children::None; let children = Children::None;
let text_style = text_style.clone(); let text_style = text_style.clone();
constructor! { constructor! {
...@@ -72,36 +81,10 @@ fn TabDemo() { ...@@ -72,36 +81,10 @@ fn TabDemo() {
} }
}, },
}, },
]); ];
let tab_clone = tabs.clone();
let set_added_tabs = set_tabs.clone();
let on_add_tab = Handler::new(move |_| {
let mut tab_clone = (&tab_clone).clone();
tab_clone.push(TabData {
name: format!("Tab {}", count),
content: {
let children = Children::None;
constructor! {
<>
<Text content={"Hello".to_string()} size={12.0} />
</>
}
},
}, );
set_count(count + 1);
set_added_tabs(tab_clone);
});
let tab_clone = tabs.clone();
let on_remove_tab = Handler::new(move |index: usize| {
let mut tab_clone = (&tab_clone).clone();
tab_clone.remove(index);
set_tabs(tab_clone);
});
rsx! { rsx! {
<TabBox tabs={tabs} on_add_tab={on_add_tab} on_remove_tab={on_remove_tab} /> <TabBox tabs={tabs} />
} }
} }
...@@ -123,19 +106,16 @@ fn startup( ...@@ -123,19 +106,16 @@ fn startup(
normal: Color::new(0.949, 0.956, 0.968, 1.0), normal: Color::new(0.949, 0.956, 0.968, 1.0),
hovered: Color::new(0.650, 0.574, 0.669, 1.0), hovered: Color::new(0.650, 0.574, 0.669, 1.0),
active: Color::new(0.949, 0.956, 0.968, 1.0), active: Color::new(0.949, 0.956, 0.968, 1.0),
disabled: Color::new(0.662, 0.678, 0.694, 1.0),
}, },
active_tab: ColorState { active_tab: ColorState {
normal: Color::new(0.286, 0.353, 0.392, 1.0), normal: Color::new(0.286, 0.353, 0.392, 1.0),
hovered: Color::new(0.246, 0.323, 0.352, 1.0), hovered: Color::new(0.246, 0.323, 0.352, 1.0),
active: Default::default(), active: Color::new(0.196, 0.283, 0.312, 1.0),
disabled: Color::new(0.474, 0.486, 0.505, 1.0),
}, },
inactive_tab: ColorState { inactive_tab: ColorState {
normal: Color::new(0.176, 0.227, 0.255, 1.0), normal: Color::new(0.176, 0.227, 0.255, 1.0),
hovered: Color::new(0.16, 0.21, 0.23, 1.0), hovered: Color::new(0.16, 0.21, 0.23, 1.0),
active: Default::default(), active: Color::new(0.196, 0.283, 0.312, 1.0),
disabled: Color::new(0.474, 0.486, 0.505, 1.0),
}, },
tab_height: 22.0, tab_height: 22.0,
}; };
......
...@@ -17,7 +17,6 @@ pub struct ColorState { ...@@ -17,7 +17,6 @@ pub struct ColorState {
pub normal: Color, pub normal: Color,
pub hovered: Color, pub hovered: Color,
pub active: Color, pub active: Color,
pub disabled: Color,
} }
#[widget] #[widget]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment