mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
Decorative widgets (#23804)
# Objective Part of #19236 ## Solution A bunch of new, decorative widgets - see release note. ## Testing Manual testing ## Showcase <img width="384" height="207" alt="panes" src="https://github.com/user-attachments/assets/0b0bb2ee-d520-4280-938e-e08d5c4e49d1" /> --------- Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "Feathers widgets moving to BSN"
|
||||
pull_requests: [23804]
|
||||
---
|
||||
|
||||
Going forward, BSN will be the primary means to create Feathers widgets. The old spawning
|
||||
functions have been renamed (`button` is now `button_bundle`), and will be removed in a future
|
||||
release.
|
||||
|
||||
Some of the BSN widgets are slightly different than before:
|
||||
|
||||
- `button` no longer automatically includes `flex_grow`. This was originally added due to the
|
||||
difficulty of overriding node styles when spawning, but in BSN that's no longer a problem.
|
||||
- `button`, `checkbox` and `radio` now accept a `caption` parameter which lets you specify
|
||||
the label directly instead of appending them via `Children`.
|
||||
@@ -1,9 +1,16 @@
|
||||
---
|
||||
title: "Moar widgets!"
|
||||
authors: ["@viridia"]
|
||||
pull_requests: [23645, 23707]
|
||||
pull_requests: [23645, 23707, 23788, 23787, 23804]
|
||||
---
|
||||
|
||||
Bevy Feathers, the opinionated UI widget collection, has added two new widgets: text input and
|
||||
dropdown menu buttons. Note that unlike the older widgets, these are _only_ available through
|
||||
Bevy Feathers, the opinionated UI widget collection, has added several new widgets:
|
||||
|
||||
- text input
|
||||
- dropdown menu buttons
|
||||
- icon (displays an image)
|
||||
- label (displays a text string in the standard font)
|
||||
- pane, subpane, and group (decorative frames which can be used in editors)
|
||||
|
||||
Note that unlike the older widgets, these are _only_ available through
|
||||
BSN (which will be the primary access to feathers going forward).
|
||||
|
||||
@@ -26,6 +26,7 @@ pub mod icons {
|
||||
|
||||
/// Size constants
|
||||
pub mod size {
|
||||
use bevy_text::FontSize;
|
||||
use bevy_ui::Val;
|
||||
|
||||
/// Common row size for buttons, sliders, spinners, etc.
|
||||
@@ -45,4 +46,13 @@ pub mod size {
|
||||
|
||||
/// Height of a toggle switch
|
||||
pub const TOGGLE_HEIGHT: Val = Val::Px(18.0);
|
||||
|
||||
/// Regular font size, used for most widget captions
|
||||
pub const MEDIUM_FONT: FontSize = FontSize::Px(14.0);
|
||||
|
||||
/// Slightly smaller font size, used for text inputs
|
||||
pub const COMPACT_FONT: FontSize = FontSize::Px(13.0);
|
||||
|
||||
/// Small font size
|
||||
pub const SMALL_FONT: FontSize = FontSize::Px(12.0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use bevy_scene::{bsn, Scene};
|
||||
use bevy_ui::Node;
|
||||
|
||||
/// An invisible UI node that takes up space, and which has a positive `flex_grow` setting.
|
||||
/// This is normally used within containers to provide a gap.
|
||||
pub fn flex_spacer() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use bevy_scene::{bsn, Scene};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{px, AlignItems, Display, FlexDirection, JustifyContent, Node, UiRect, Val};
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
font_styles::InheritableFont,
|
||||
rounded_corners::RoundedCorners,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
||||
tokens,
|
||||
};
|
||||
|
||||
/// Group
|
||||
pub fn group() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Stretch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Group header
|
||||
pub fn group_header() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
border: UiRect {
|
||||
left: Val::Px(1.0),
|
||||
top: Val::Px(1.0),
|
||||
right: Val::Px(1.0),
|
||||
bottom: Val::Px(0.0),
|
||||
},
|
||||
padding: UiRect::axes(Val::Px(10.0), Val::Px(0.0)),
|
||||
min_height: size::HEADER_HEIGHT,
|
||||
column_gap: Val::Px(4.0),
|
||||
border_radius: {RoundedCorners::Top.to_border_radius(4.0)}
|
||||
}
|
||||
ThemeBackgroundColor(tokens::GROUP_HEADER_BG)
|
||||
ThemeBorderColor(tokens::GROUP_HEADER_BORDER)
|
||||
ThemeFontColor(tokens::GROUP_HEADER_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Group body
|
||||
pub fn group_body() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
border: UiRect {
|
||||
left: Val::Px(1.0),
|
||||
top: Val::Px(0.0),
|
||||
right: Val::Px(1.0),
|
||||
bottom: Val::Px(1.0),
|
||||
},
|
||||
row_gap: px(4.0),
|
||||
padding: UiRect::axes(Val::Px(6.0), Val::Px(6.0)),
|
||||
border_radius: {RoundedCorners::Bottom.to_border_radius(4.0)}
|
||||
}
|
||||
ThemeBackgroundColor(tokens::GROUP_BODY_BG)
|
||||
ThemeBorderColor(tokens::GROUP_BODY_BORDER)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//! Meta-module containing all feathers containers: passive widgets that hold other widgets.
|
||||
mod flex_spacer;
|
||||
mod group;
|
||||
mod pane;
|
||||
mod subpane;
|
||||
|
||||
pub use flex_spacer::*;
|
||||
pub use group::*;
|
||||
pub use pane::*;
|
||||
pub use subpane::*;
|
||||
@@ -0,0 +1,92 @@
|
||||
use bevy_ecs::hierarchy::Children;
|
||||
use bevy_scene::{bsn, Scene};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{
|
||||
px, AlignItems, AlignSelf, Display, FlexDirection, JustifyContent, Node, PositionType, UiRect,
|
||||
Val,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
font_styles::InheritableFont,
|
||||
rounded_corners::RoundedCorners,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
||||
tokens,
|
||||
};
|
||||
|
||||
/// A standard pane
|
||||
pub fn pane() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Stretch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pane header
|
||||
pub fn pane_header() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
padding: UiRect::axes(Val::Px(6.0), Val::Px(6.0)),
|
||||
border: UiRect {
|
||||
left: Val::Px(1.0),
|
||||
top: Val::Px(1.0),
|
||||
right: Val::Px(1.0),
|
||||
bottom: Val::Px(0.0),
|
||||
},
|
||||
min_height: size::HEADER_HEIGHT,
|
||||
column_gap: Val::Px(6.0),
|
||||
border_radius: {RoundedCorners::Top.to_border_radius(4.0)},
|
||||
}
|
||||
ThemeBackgroundColor(tokens::PANE_HEADER_BG)
|
||||
ThemeBorderColor(tokens::PANE_HEADER_BORDER)
|
||||
ThemeFontColor(tokens::PANE_HEADER_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Vertical divider between groups of widgets in pane headers
|
||||
pub fn pane_header_divider() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
width: Val::Px(1.0),
|
||||
align_self: AlignSelf::Stretch,
|
||||
}
|
||||
Children [(
|
||||
// Because we want to extend the divider into the header padding area, we'll use
|
||||
// an absolutely-positioned child.
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
left: px(0),
|
||||
right: px(0),
|
||||
top: {px(-6)},
|
||||
bottom: {px(-6)},
|
||||
}
|
||||
ThemeBackgroundColor(tokens::PANE_HEADER_DIVIDER)
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Pane body
|
||||
pub fn pane_body() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
row_gap: px(4.0),
|
||||
padding: UiRect::axes(Val::Px(6.0), Val::Px(6.0)),
|
||||
border_radius: {RoundedCorners::Bottom.to_border_radius(4.0)}
|
||||
}
|
||||
ThemeBackgroundColor(tokens::PANE_BODY_BG)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use bevy_scene::{bsn, Scene};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{px, AlignItems, Display, FlexDirection, JustifyContent, Node, UiRect, Val};
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
font_styles::InheritableFont,
|
||||
rounded_corners::RoundedCorners,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
||||
tokens,
|
||||
};
|
||||
|
||||
/// Sub-pane
|
||||
pub fn subpane() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Stretch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sub-pane header
|
||||
pub fn subpane_header() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
border: UiRect {
|
||||
left: Val::Px(1.0),
|
||||
top: Val::Px(1.0),
|
||||
right: Val::Px(1.0),
|
||||
bottom: Val::Px(0.0),
|
||||
},
|
||||
padding: UiRect::axes(Val::Px(10.0), Val::Px(0.0)),
|
||||
min_height: size::HEADER_HEIGHT,
|
||||
column_gap: Val::Px(4.0),
|
||||
border_radius: {RoundedCorners::Top.to_border_radius(4.0)}
|
||||
}
|
||||
ThemeBackgroundColor(tokens::SUBPANE_HEADER_BG)
|
||||
ThemeBorderColor(tokens::SUBPANE_HEADER_BORDER)
|
||||
ThemeFontColor(tokens::SUBPANE_HEADER_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sub-pane body
|
||||
pub fn subpane_body() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
border: UiRect {
|
||||
left: Val::Px(1.0),
|
||||
top: Val::Px(0.0),
|
||||
right: Val::Px(1.0),
|
||||
bottom: Val::Px(1.0),
|
||||
},
|
||||
row_gap: px(4.0),
|
||||
padding: UiRect::axes(Val::Px(6.0), Val::Px(6.0)),
|
||||
border_radius: {RoundedCorners::Bottom.to_border_radius(4.0)}
|
||||
}
|
||||
ThemeBackgroundColor(tokens::SUBPANE_BODY_BG)
|
||||
ThemeBorderColor(tokens::SUBPANE_BODY_BORDER)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use bevy_input_focus::tab_navigation::TabIndex;
|
||||
use bevy_picking::{hover::Hovered, PickingSystems};
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use bevy_scene::{prelude::*, template_value};
|
||||
use bevy_text::{FontSize, FontWeight};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{AlignItems, InteractionDisabled, JustifyContent, Node, Pressed, UiRect, Val};
|
||||
use bevy_ui_widgets::Button;
|
||||
|
||||
@@ -40,6 +40,8 @@ pub enum ButtonVariant {
|
||||
/// A button with a more prominent color, this is used for "call to action" buttons,
|
||||
/// default buttons for dialog boxes, and so on.
|
||||
Primary,
|
||||
/// Don't display the button background unless hovering or pressed.
|
||||
Plain,
|
||||
}
|
||||
|
||||
/// Parameters for the button template, passed to [`button`] function.
|
||||
@@ -81,7 +83,6 @@ pub fn button(props: ButtonProps) -> impl Scene {
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::axes(Val::Px(8.0), Val::Px(0.)),
|
||||
flex_grow: 1.0,
|
||||
border_radius: {props.corners.to_border_radius(4.0)},
|
||||
}
|
||||
Button
|
||||
@@ -94,7 +95,7 @@ pub fn button(props: ButtonProps) -> impl Scene {
|
||||
ThemeFontColor(tokens::BUTTON_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [
|
||||
@@ -103,6 +104,27 @@ pub fn button(props: ButtonProps) -> impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tool button scene function: a smaller button for embedding in panel headers.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `props` - construction properties for the button.
|
||||
///
|
||||
/// # Emitted events
|
||||
/// * [`bevy_ui_widgets::Activate`] when any of the following happens:
|
||||
/// * the pointer is released while hovering over the button.
|
||||
/// * the ENTER or SPACE key is pressed while the button has keyboard focus.
|
||||
///
|
||||
/// These events can be disabled by adding an [`bevy_ui::InteractionDisabled`] component to the entity
|
||||
pub fn tool_button(props: ButtonProps) -> impl Scene {
|
||||
bsn! {
|
||||
:button(props)
|
||||
Node {
|
||||
padding: UiRect::axes(Val::Px(4.0), Val::Px(0.)),
|
||||
min_width: size::ROW_HEIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for the [`button_bundle`] template.
|
||||
#[derive(Default)]
|
||||
pub struct ButtonBundleProps {
|
||||
@@ -148,7 +170,7 @@ pub fn button_bundle<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundl
|
||||
ThemeBackgroundColor(tokens::BUTTON_BG),
|
||||
ThemeFontColor(tokens::BUTTON_TEXT),
|
||||
InheritableFont {
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -245,13 +267,17 @@ fn set_button_styles(
|
||||
(ButtonVariant::Primary, false, true, _) => tokens::BUTTON_PRIMARY_BG_PRESSED,
|
||||
(ButtonVariant::Primary, false, false, true) => tokens::BUTTON_PRIMARY_BG_HOVER,
|
||||
(ButtonVariant::Primary, false, false, false) => tokens::BUTTON_PRIMARY_BG,
|
||||
(ButtonVariant::Plain, true, _, _) => tokens::BUTTON_PLAIN_BG_DISABLED,
|
||||
(ButtonVariant::Plain, false, true, _) => tokens::BUTTON_PLAIN_BG_PRESSED,
|
||||
(ButtonVariant::Plain, false, false, true) => tokens::BUTTON_PLAIN_BG_HOVER,
|
||||
(ButtonVariant::Plain, false, false, false) => tokens::BUTTON_PLAIN_BG,
|
||||
};
|
||||
|
||||
let font_color_token = match (variant, disabled) {
|
||||
(ButtonVariant::Normal, true) => tokens::BUTTON_TEXT_DISABLED,
|
||||
(ButtonVariant::Normal, false) => tokens::BUTTON_TEXT,
|
||||
(ButtonVariant::Primary, true) => tokens::BUTTON_PRIMARY_TEXT_DISABLED,
|
||||
(ButtonVariant::Primary, false) => tokens::BUTTON_PRIMARY_TEXT,
|
||||
(ButtonVariant::Normal | ButtonVariant::Plain, true) => tokens::BUTTON_TEXT_DISABLED,
|
||||
(ButtonVariant::Normal | ButtonVariant::Plain, false) => tokens::BUTTON_TEXT,
|
||||
};
|
||||
|
||||
let cursor_shape = match disabled {
|
||||
|
||||
@@ -18,7 +18,7 @@ use bevy_math::Rot2;
|
||||
use bevy_picking::{hover::Hovered, PickingSystems};
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use bevy_scene::prelude::*;
|
||||
use bevy_text::{FontSize, FontWeight};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{
|
||||
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
|
||||
Node, PositionType, UiRect, UiTransform, Val,
|
||||
@@ -87,7 +87,7 @@ pub fn checkbox(props: CheckboxProps) -> impl Scene {
|
||||
ThemeFontColor(tokens::CHECKBOX_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [(
|
||||
@@ -152,7 +152,7 @@ pub fn checkbox_bundle<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bun
|
||||
TabIndex(0),
|
||||
ThemeFontColor(tokens::CHECKBOX_TEXT),
|
||||
InheritableFont {
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ use bevy_ecs::{
|
||||
use bevy_log::warn;
|
||||
use bevy_picking::{hover::Hovered, PickingSystems};
|
||||
use bevy_scene::{prelude::*, template_value};
|
||||
use bevy_text::{FontSize, FontWeight};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{
|
||||
px, AlignItems, AlignSelf, BoxShadow, Display, FlexDirection, GlobalZIndex,
|
||||
InteractionDisabled, JustifyContent, Node, OverrideClip, PositionType, Pressed, UiRect, Val,
|
||||
@@ -29,8 +29,8 @@ use crate::{
|
||||
constants::{fonts, icons, size},
|
||||
controls::{button, ButtonProps, ButtonVariant},
|
||||
cursor::EntityCursor,
|
||||
display::icon,
|
||||
font_styles::InheritableFont,
|
||||
icon,
|
||||
rounded_corners::RoundedCorners,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
||||
tokens,
|
||||
@@ -271,7 +271,7 @@ pub fn menu_item(props: MenuItemProps) -> impl Scene {
|
||||
ThemeFontColor(tokens::MENUITEM_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [
|
||||
|
||||
@@ -17,7 +17,7 @@ use bevy_input_focus::tab_navigation::TabIndex;
|
||||
use bevy_picking::{hover::Hovered, PickingSystems};
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use bevy_scene::prelude::*;
|
||||
use bevy_text::{FontSize, FontWeight};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{
|
||||
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
|
||||
Node, UiRect, Val,
|
||||
@@ -81,7 +81,7 @@ pub fn radio(props: RadioProps) -> impl Scene {
|
||||
ThemeFontColor(tokens::RADIO_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [(
|
||||
@@ -141,7 +141,7 @@ pub fn radio_bundle<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle
|
||||
TabIndex(0),
|
||||
ThemeFontColor(tokens::RADIO_TEXT),
|
||||
InheritableFont {
|
||||
font_size: FontSize::Px(14.0),
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ use bevy_input_focus::tab_navigation::TabIndex;
|
||||
use bevy_picking::PickingSystems;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use bevy_scene::prelude::*;
|
||||
use bevy_text::{FontSize, FontWeight};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{
|
||||
widget::Text, AlignItems, BackgroundGradient, ColorStop, Display, FlexDirection, Gradient,
|
||||
InteractionDisabled, InterpolationColorSpace, JustifyContent, LinearGradient, Node,
|
||||
@@ -123,7 +123,7 @@ pub fn slider(props: SliderProps) -> impl Scene {
|
||||
ThemeFontColor(tokens::SLIDER_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::MONO,
|
||||
font_size: FontSize::Px(12.0),
|
||||
font_size: size::SMALL_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [(Text("10.0") ThemedText SliderValueText)]
|
||||
@@ -189,7 +189,7 @@ pub fn slider_bundle<B: Bundle>(props: SliderProps, overrides: B) -> impl Bundle
|
||||
},
|
||||
ThemeFontColor(tokens::SLIDER_TEXT),
|
||||
InheritableFont {
|
||||
font_size: FontSize::Px(12.0),
|
||||
font_size: size::SMALL_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ use bevy_ecs::{
|
||||
use bevy_input_focus::{tab_navigation::TabIndex, InputFocus, InputFocusVisible};
|
||||
use bevy_picking::PickingSystems;
|
||||
use bevy_scene::prelude::*;
|
||||
use bevy_text::{EditableText, FontSize, FontWeight, LineBreak, TextCursorStyle, TextLayout};
|
||||
use bevy_text::{EditableText, FontWeight, LineBreak, TextCursorStyle, TextLayout};
|
||||
use bevy_ui::{
|
||||
px, AlignItems, BorderColor, BorderRadius, Display, InteractionDisabled, JustifyContent, Node,
|
||||
UiRect, Val,
|
||||
@@ -61,7 +61,7 @@ pub fn text_input_container() -> impl Scene {
|
||||
ThemeFontColor(tokens::TEXT_INPUT_TEXT)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: FontSize::Px(13.0),
|
||||
font_size: size::COMPACT_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ where
|
||||
let key_clone = key.clone();
|
||||
bsn! {
|
||||
button(ButtonProps::default())
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
on(
|
||||
move |activate: On<Activate>,
|
||||
mut commands: Commands,
|
||||
|
||||
@@ -11,6 +11,8 @@ pub fn create_dark_theme() -> ThemeProps {
|
||||
color: HashMap::from([
|
||||
(tokens::WINDOW_BG, palette::GRAY_0),
|
||||
(tokens::FOCUS_RING, palette::ACCENT.with_alpha(0.5)),
|
||||
(tokens::TEXT_MAIN, palette::LIGHT_GRAY_1),
|
||||
(tokens::TEXT_DIM, palette::LIGHT_GRAY_2),
|
||||
// Button (normal)
|
||||
(tokens::BUTTON_BG, palette::GRAY_3),
|
||||
(tokens::BUTTON_BG_HOVER, palette::GRAY_3.lighter(0.05)),
|
||||
@@ -128,6 +130,24 @@ pub fn create_dark_theme() -> ThemeProps {
|
||||
),
|
||||
(tokens::TEXT_INPUT_CURSOR, palette::ACCENT.lighter(0.2)),
|
||||
(tokens::TEXT_INPUT_SELECTION, palette::ACCENT),
|
||||
// Pane
|
||||
(tokens::PANE_HEADER_BG, palette::GRAY_0),
|
||||
(tokens::PANE_HEADER_BORDER, palette::WARM_GRAY_1),
|
||||
(tokens::PANE_HEADER_TEXT, palette::LIGHT_GRAY_1),
|
||||
(tokens::PANE_HEADER_DIVIDER, palette::WARM_GRAY_1),
|
||||
(tokens::PANE_BODY_BG, palette::GRAY_1),
|
||||
// Subpane
|
||||
(tokens::SUBPANE_HEADER_BG, palette::GRAY_2),
|
||||
(tokens::SUBPANE_HEADER_BORDER, palette::GRAY_3),
|
||||
(tokens::SUBPANE_HEADER_TEXT, palette::LIGHT_GRAY_1),
|
||||
(tokens::SUBPANE_BODY_BG, palette::GRAY_1),
|
||||
(tokens::SUBPANE_BODY_BORDER, palette::GRAY_2),
|
||||
// Group
|
||||
(tokens::GROUP_HEADER_BG, palette::GRAY_2),
|
||||
(tokens::GROUP_HEADER_BORDER, palette::GRAY_3),
|
||||
(tokens::GROUP_HEADER_TEXT, palette::LIGHT_GRAY_1),
|
||||
(tokens::GROUP_BODY_BG, palette::GRAY_2),
|
||||
(tokens::GROUP_BODY_BORDER, palette::GRAY_3),
|
||||
]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! BSN Template for loading images and displaying them as [`ImageNode`]s.
|
||||
//! BSN Scene for loading images and displaying them as [`ImageNode`]s.
|
||||
use bevy_asset::AssetServer;
|
||||
use bevy_ecs::template::template;
|
||||
use bevy_scene::{bsn, Scene};
|
||||
@@ -0,0 +1,48 @@
|
||||
//! BSN scene function for displaying a plain text string in the correct font.
|
||||
use bevy_ecs::hierarchy::Children;
|
||||
use bevy_scene::{bsn, template_value, Scene};
|
||||
use bevy_text::FontWeight;
|
||||
use bevy_ui::{widget::Text, Node};
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
font_styles::InheritableFont,
|
||||
theme::{ThemeFontColor, ThemedText},
|
||||
tokens,
|
||||
};
|
||||
|
||||
/// A text label.
|
||||
pub fn label(text: impl Into<String>) -> impl Scene {
|
||||
let text = Text::new(text.into());
|
||||
bsn! {
|
||||
Node
|
||||
ThemeFontColor(tokens::TEXT_MAIN)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [
|
||||
template_value(text)
|
||||
ThemedText
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// A text label with a dimmed color.
|
||||
pub fn label_dim(text: impl Into<String>) -> impl Scene {
|
||||
let text = Text::new(text.into());
|
||||
bsn! {
|
||||
Node
|
||||
ThemeFontColor(tokens::TEXT_DIM)
|
||||
InheritableFont {
|
||||
font: fonts::REGULAR,
|
||||
font_size: size::MEDIUM_FONT,
|
||||
weight: FontWeight::NORMAL,
|
||||
}
|
||||
Children [
|
||||
template_value(text)
|
||||
ThemedText
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Static widgets that only display data and are not interactive.
|
||||
|
||||
mod icon;
|
||||
mod label;
|
||||
|
||||
pub use icon::*;
|
||||
pub use label::*;
|
||||
@@ -47,19 +47,18 @@ use crate::{
|
||||
|
||||
mod alpha_pattern;
|
||||
pub mod constants;
|
||||
pub mod containers;
|
||||
pub mod controls;
|
||||
pub mod cursor;
|
||||
pub mod dark_theme;
|
||||
pub mod display;
|
||||
pub mod focus;
|
||||
pub mod font_styles;
|
||||
mod icon;
|
||||
pub mod palette;
|
||||
pub mod rounded_corners;
|
||||
pub mod theme;
|
||||
pub mod tokens;
|
||||
|
||||
pub use icon::icon;
|
||||
|
||||
/// Plugin which installs observers and systems for feathers themes, cursors, and all controls.
|
||||
pub struct FeathersCorePlugin;
|
||||
|
||||
|
||||
@@ -198,3 +198,43 @@ pub const TEXT_INPUT_TEXT_DISABLED: ThemeToken =
|
||||
pub const TEXT_INPUT_CURSOR: ThemeToken = ThemeToken::new_static("feathers.textinput.cursor");
|
||||
/// Selection color for text input
|
||||
pub const TEXT_INPUT_SELECTION: ThemeToken = ThemeToken::new_static("feathers.textinput.selection");
|
||||
|
||||
// Pane
|
||||
|
||||
/// Pane header background
|
||||
pub const PANE_HEADER_BG: ThemeToken = ThemeToken::new_static("feathers.pane.header.bg");
|
||||
/// Pane header border
|
||||
pub const PANE_HEADER_BORDER: ThemeToken = ThemeToken::new_static("feathers.pane.header.border");
|
||||
/// Pane header text color
|
||||
pub const PANE_HEADER_TEXT: ThemeToken = ThemeToken::new_static("feathers.pane.header.text");
|
||||
/// Pane header divider color
|
||||
pub const PANE_HEADER_DIVIDER: ThemeToken = ThemeToken::new_static("feathers.pane.header.divider");
|
||||
/// Pane body background
|
||||
pub const PANE_BODY_BG: ThemeToken = ThemeToken::new_static("feathers.pane.body.bg");
|
||||
|
||||
// Subpane
|
||||
|
||||
/// Subpane background
|
||||
pub const SUBPANE_HEADER_BG: ThemeToken = ThemeToken::new_static("feathers.subpane.header.bg");
|
||||
/// Subpane header border
|
||||
pub const SUBPANE_HEADER_BORDER: ThemeToken =
|
||||
ThemeToken::new_static("feathers.subpane.header.border");
|
||||
/// Subpane header text color
|
||||
pub const SUBPANE_HEADER_TEXT: ThemeToken = ThemeToken::new_static("feathers.subpane.header.text");
|
||||
/// Subpane body background
|
||||
pub const SUBPANE_BODY_BG: ThemeToken = ThemeToken::new_static("feathers.subpane.body.bg");
|
||||
/// Subpane body border
|
||||
pub const SUBPANE_BODY_BORDER: ThemeToken = ThemeToken::new_static("feathers.subpane.body.border");
|
||||
|
||||
// Group
|
||||
|
||||
/// Group background
|
||||
pub const GROUP_HEADER_BG: ThemeToken = ThemeToken::new_static("feathers.group.header.bg");
|
||||
/// Group header border
|
||||
pub const GROUP_HEADER_BORDER: ThemeToken = ThemeToken::new_static("feathers.group.header.border");
|
||||
/// Group header text color
|
||||
pub const GROUP_HEADER_TEXT: ThemeToken = ThemeToken::new_static("feathers.group.header.text");
|
||||
/// Group body background
|
||||
pub const GROUP_BODY_BG: ThemeToken = ThemeToken::new_static("feathers.group.body.bg");
|
||||
/// Group body border
|
||||
pub const GROUP_BODY_BORDER: ThemeToken = ThemeToken::new_static("feathers.group.body.border");
|
||||
|
||||
@@ -3,16 +3,22 @@
|
||||
use bevy::{
|
||||
color::palettes,
|
||||
feathers::{
|
||||
constants::icons,
|
||||
containers::{
|
||||
flex_spacer, group, group_body, group_header, pane, pane_body, pane_header,
|
||||
pane_header_divider, subpane, subpane_body, subpane_header,
|
||||
},
|
||||
controls::{
|
||||
button, checkbox, color_plane, color_slider, color_swatch, menu, menu_button,
|
||||
menu_divider, menu_item, menu_popup, radio, slider, text_input, text_input_container,
|
||||
toggle_switch, ButtonProps, ButtonVariant, CheckboxProps, ColorChannel, ColorPlane,
|
||||
ColorPlaneValue, ColorSlider, ColorSliderProps, ColorSwatch, ColorSwatchValue,
|
||||
MenuButtonProps, MenuItemProps, RadioProps, SliderBaseColor, SliderProps,
|
||||
TextInputProps,
|
||||
toggle_switch, tool_button, ButtonProps, ButtonVariant, CheckboxProps, ColorChannel,
|
||||
ColorPlane, ColorPlaneValue, ColorSlider, ColorSliderProps, ColorSwatch,
|
||||
ColorSwatchValue, MenuButtonProps, MenuItemProps, RadioProps, SliderBaseColor,
|
||||
SliderProps, TextInputProps,
|
||||
},
|
||||
cursor::{EntityCursor, OverrideCursor},
|
||||
dark_theme::create_dark_theme,
|
||||
display::{icon, label, label_dim},
|
||||
rounded_corners::RoundedCorners,
|
||||
theme::{ThemeBackgroundColor, ThemedText, UiTheme},
|
||||
tokens, FeathersPlugins,
|
||||
@@ -75,408 +81,521 @@ fn demo_root() -> impl Scene {
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::Start,
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
row_gap: px(10),
|
||||
flex_direction: FlexDirection::Row,
|
||||
column_gap: px(8),
|
||||
}
|
||||
TabGroup
|
||||
ThemeBackgroundColor(tokens::WINDOW_BG)
|
||||
Children[(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Stretch,
|
||||
justify_content: JustifyContent::Start,
|
||||
padding: UiRect::all(px(8)),
|
||||
row_gap: px(8),
|
||||
width: percent(30),
|
||||
min_width: px(200),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Start,
|
||||
column_gap: px(8),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Normal") ThemedText),
|
||||
)),
|
||||
..default()
|
||||
})
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Normal button clicked!");
|
||||
})
|
||||
AutoFocus
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled") ThemedText),
|
||||
)),
|
||||
..default()
|
||||
})
|
||||
InteractionDisabled
|
||||
DemoDisabledButton
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Disabled button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Primary") ThemedText),
|
||||
)),
|
||||
variant: ButtonVariant::Primary,
|
||||
..default()
|
||||
})
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Disabled button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
:menu
|
||||
Children [
|
||||
(
|
||||
:menu_button(MenuButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Menu") ThemedText),
|
||||
)),
|
||||
..default()
|
||||
})
|
||||
),
|
||||
(
|
||||
:menu_popup
|
||||
Children [
|
||||
(
|
||||
menu_item(MenuItemProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("MenuItem 1") ThemedText)))
|
||||
})
|
||||
on(|_: On<Activate>| {
|
||||
info!("Menu item 1 clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
menu_item(MenuItemProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("MenuItem 2") ThemedText)))
|
||||
})
|
||||
on(|_: On<Activate>| {
|
||||
info!("Menu item 2 clicked!");
|
||||
})
|
||||
),
|
||||
:menu_divider,
|
||||
(
|
||||
menu_item(MenuItemProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("MenuItem 3") ThemedText)))
|
||||
})
|
||||
on(|_: On<Activate>| {
|
||||
info!("Menu item 3 clicked!");
|
||||
})
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Start,
|
||||
column_gap: px(1),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Left") ThemedText),
|
||||
)),
|
||||
corners: RoundedCorners::Left,
|
||||
..default()
|
||||
})
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Left button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Center") ThemedText),
|
||||
)),
|
||||
corners: RoundedCorners::None,
|
||||
..default()
|
||||
})
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Center button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Right") ThemedText),
|
||||
)),
|
||||
variant: ButtonVariant::Primary,
|
||||
corners: RoundedCorners::Right,
|
||||
})
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Right button clicked!");
|
||||
})
|
||||
),
|
||||
]
|
||||
),
|
||||
(
|
||||
button(ButtonProps::default())
|
||||
on(|_activate: On<Activate>, mut ovr: ResMut<OverrideCursor>| {
|
||||
ovr.0 = if ovr.0.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(EntityCursor::System(SystemCursorIcon::Wait))
|
||||
};
|
||||
info!("Override cursor button clicked!");
|
||||
})
|
||||
Children [ (Text::new("Toggle override") ThemedText) ]
|
||||
),
|
||||
(
|
||||
checkbox(CheckboxProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Checkbox") ThemedText),
|
||||
)),
|
||||
})
|
||||
Checked
|
||||
on(
|
||||
|change: On<ValueChange<bool>>,
|
||||
query: Query<Entity, With<DemoDisabledButton>>,
|
||||
mut commands: Commands| {
|
||||
info!("Checkbox clicked!");
|
||||
let mut button = commands.entity(query.single().unwrap());
|
||||
if change.value {
|
||||
button.insert(InteractionDisabled);
|
||||
} else {
|
||||
button.remove::<InteractionDisabled>();
|
||||
}
|
||||
let mut checkbox = commands.entity(change.source);
|
||||
if change.value {
|
||||
checkbox.insert(Checked);
|
||||
} else {
|
||||
checkbox.remove::<Checked>();
|
||||
}
|
||||
Children[
|
||||
:demo_column_1,
|
||||
:demo_column_2,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn demo_column_1() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Stretch,
|
||||
justify_content: JustifyContent::Start,
|
||||
padding: UiRect::all(px(8)),
|
||||
row_gap: px(8),
|
||||
width: percent(30),
|
||||
min_width: px(200),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Start,
|
||||
column_gap: px(8),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Normal") ThemedText),
|
||||
)),
|
||||
..default()
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
)
|
||||
),
|
||||
(
|
||||
checkbox(CheckboxProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled") ThemedText),
|
||||
)),
|
||||
})
|
||||
InteractionDisabled
|
||||
on(|_change: On<ValueChange<bool>>| {
|
||||
warn!("Disabled checkbox clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
checkbox(CheckboxProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled+Checked") ThemedText),
|
||||
)),
|
||||
})
|
||||
InteractionDisabled
|
||||
Checked
|
||||
on(|_change: On<ValueChange<bool>>| {
|
||||
warn!("Disabled checkbox clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
row_gap: px(4),
|
||||
}
|
||||
RadioGroup
|
||||
on(
|
||||
|value_change: On<ValueChange<Entity>>,
|
||||
q_radio: Query<Entity, With<RadioButton>>,
|
||||
mut commands: Commands| {
|
||||
for radio in q_radio.iter() {
|
||||
if radio == value_change.value {
|
||||
commands.entity(radio).insert(Checked);
|
||||
} else {
|
||||
commands.entity(radio).remove::<Checked>();
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
Children [
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("One") ThemedText),
|
||||
)),
|
||||
}) Checked),
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Two") ThemedText),
|
||||
)),
|
||||
})),
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Three") ThemedText),
|
||||
)),
|
||||
})),
|
||||
(radio(RadioProps {
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Normal button clicked!");
|
||||
})
|
||||
AutoFocus
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled") ThemedText),
|
||||
)),
|
||||
}) InteractionDisabled),
|
||||
]
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Start,
|
||||
column_gap: px(8),
|
||||
}
|
||||
Children [
|
||||
(toggle_switch() on(checkbox_self_update)),
|
||||
(toggle_switch() InteractionDisabled on(checkbox_self_update)),
|
||||
(toggle_switch() InteractionDisabled Checked on(checkbox_self_update)),
|
||||
]
|
||||
),
|
||||
(
|
||||
slider(SliderProps {
|
||||
max: 100.0,
|
||||
value: 20.0,
|
||||
..default()
|
||||
})
|
||||
SliderStep(10.)
|
||||
SliderPrecision(2)
|
||||
on(slider_self_update)
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
column_gap: px(4.0),
|
||||
}
|
||||
Children [
|
||||
Text("Srgba"),
|
||||
// Spacer
|
||||
..default()
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
},
|
||||
// Text input
|
||||
(
|
||||
:text_input_container
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
Children [
|
||||
(
|
||||
text_input(TextInputProps {
|
||||
max_characters: Some(9),
|
||||
})
|
||||
HexColorInput
|
||||
on(handle_hex_color_change)
|
||||
)
|
||||
]
|
||||
)
|
||||
(color_swatch() SwatchType::Rgb),
|
||||
]
|
||||
),
|
||||
(
|
||||
color_plane(ColorPlane::RedBlue)
|
||||
on(|change: On<ValueChange<Vec2>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.red = change.value.x;
|
||||
color.rgb_color.blue = change.value.y;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Red
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.red = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Green
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.green = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Blue
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.blue = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Alpha
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.alpha = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
}
|
||||
InteractionDisabled
|
||||
DemoDisabledButton
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Disabled button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Primary") ThemedText),
|
||||
)),
|
||||
variant: ButtonVariant::Primary,
|
||||
..default()
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Disabled button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
:menu
|
||||
Children [
|
||||
(
|
||||
:menu_button(MenuButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Menu") ThemedText),
|
||||
)),
|
||||
..default()
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
),
|
||||
(
|
||||
:menu_popup
|
||||
Children [
|
||||
(
|
||||
menu_item(MenuItemProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("MenuItem 1") ThemedText)))
|
||||
})
|
||||
on(|_: On<Activate>| {
|
||||
info!("Menu item 1 clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
menu_item(MenuItemProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("MenuItem 2") ThemedText)))
|
||||
})
|
||||
on(|_: On<Activate>| {
|
||||
info!("Menu item 2 clicked!");
|
||||
})
|
||||
),
|
||||
:menu_divider,
|
||||
(
|
||||
menu_item(MenuItemProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("MenuItem 3") ThemedText)))
|
||||
})
|
||||
on(|_: On<Activate>| {
|
||||
info!("Menu item 3 clicked!");
|
||||
})
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Start,
|
||||
column_gap: px(1),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Left") ThemedText),
|
||||
)),
|
||||
corners: RoundedCorners::Left,
|
||||
..default()
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Left button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Center") ThemedText),
|
||||
)),
|
||||
corners: RoundedCorners::None,
|
||||
..default()
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Center button clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
button(ButtonProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Right") ThemedText),
|
||||
)),
|
||||
variant: ButtonVariant::Primary,
|
||||
corners: RoundedCorners::Right,
|
||||
})
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
on(|_activate: On<Activate>| {
|
||||
info!("Right button clicked!");
|
||||
})
|
||||
),
|
||||
]
|
||||
),
|
||||
(
|
||||
button(ButtonProps::default())
|
||||
on(|_activate: On<Activate>, mut ovr: ResMut<OverrideCursor>| {
|
||||
ovr.0 = if ovr.0.is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(EntityCursor::System(SystemCursorIcon::Wait))
|
||||
};
|
||||
info!("Override cursor button clicked!");
|
||||
})
|
||||
Children [ (Text::new("Toggle override") ThemedText) ]
|
||||
),
|
||||
(
|
||||
checkbox(CheckboxProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Checkbox") ThemedText),
|
||||
)),
|
||||
})
|
||||
Checked
|
||||
on(
|
||||
|change: On<ValueChange<bool>>,
|
||||
query: Query<Entity, With<DemoDisabledButton>>,
|
||||
mut commands: Commands| {
|
||||
info!("Checkbox clicked!");
|
||||
let mut button = commands.entity(query.single().unwrap());
|
||||
if change.value {
|
||||
button.insert(InteractionDisabled);
|
||||
} else {
|
||||
button.remove::<InteractionDisabled>();
|
||||
}
|
||||
let mut checkbox = commands.entity(change.source);
|
||||
if change.value {
|
||||
checkbox.insert(Checked);
|
||||
} else {
|
||||
checkbox.remove::<Checked>();
|
||||
}
|
||||
}
|
||||
Children [
|
||||
Text("Hsl"),
|
||||
(color_swatch() SwatchType::Hsl)
|
||||
]
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::HslHue
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.hsl_color.hue = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::HslSaturation
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.hsl_color.saturation = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::HslLightness
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.hsl_color.lightness = change.value;
|
||||
})
|
||||
)
|
||||
]
|
||||
)]
|
||||
),
|
||||
(
|
||||
checkbox(CheckboxProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled") ThemedText),
|
||||
)),
|
||||
})
|
||||
InteractionDisabled
|
||||
on(|_change: On<ValueChange<bool>>| {
|
||||
warn!("Disabled checkbox clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
checkbox(CheckboxProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled+Checked") ThemedText),
|
||||
)),
|
||||
})
|
||||
InteractionDisabled
|
||||
Checked
|
||||
on(|_change: On<ValueChange<bool>>| {
|
||||
warn!("Disabled checkbox clicked!");
|
||||
})
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
row_gap: px(4),
|
||||
}
|
||||
RadioGroup
|
||||
on(
|
||||
|value_change: On<ValueChange<Entity>>,
|
||||
q_radio: Query<Entity, With<RadioButton>>,
|
||||
mut commands: Commands| {
|
||||
for radio in q_radio.iter() {
|
||||
if radio == value_change.value {
|
||||
commands.entity(radio).insert(Checked);
|
||||
} else {
|
||||
commands.entity(radio).remove::<Checked>();
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
Children [
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("One") ThemedText),
|
||||
)),
|
||||
}) Checked),
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Two") ThemedText),
|
||||
)),
|
||||
})),
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Three") ThemedText),
|
||||
)),
|
||||
})),
|
||||
(radio(RadioProps {
|
||||
caption: Box::new(bsn_list!(
|
||||
(Text("Disabled") ThemedText),
|
||||
)),
|
||||
}) InteractionDisabled),
|
||||
]
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Start,
|
||||
column_gap: px(8),
|
||||
}
|
||||
Children [
|
||||
(toggle_switch() on(checkbox_self_update)),
|
||||
(toggle_switch() InteractionDisabled on(checkbox_self_update)),
|
||||
(toggle_switch() InteractionDisabled Checked on(checkbox_self_update)),
|
||||
]
|
||||
),
|
||||
(
|
||||
slider(SliderProps {
|
||||
max: 100.0,
|
||||
value: 20.0,
|
||||
..default()
|
||||
})
|
||||
SliderStep(10.)
|
||||
SliderPrecision(2)
|
||||
on(slider_self_update)
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
column_gap: px(4.0),
|
||||
}
|
||||
Children [
|
||||
:label("Srgba"),
|
||||
// Spacer
|
||||
:flex_spacer,
|
||||
// Text input
|
||||
(
|
||||
:text_input_container
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
}
|
||||
Children [
|
||||
(
|
||||
text_input(TextInputProps {
|
||||
max_characters: Some(9),
|
||||
})
|
||||
HexColorInput
|
||||
on(handle_hex_color_change)
|
||||
)
|
||||
]
|
||||
)
|
||||
(color_swatch() SwatchType::Rgb),
|
||||
]
|
||||
),
|
||||
(
|
||||
color_plane(ColorPlane::RedBlue)
|
||||
on(|change: On<ValueChange<Vec2>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.red = change.value.x;
|
||||
color.rgb_color.blue = change.value.y;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Red
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.red = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Green
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.green = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Blue
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.blue = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::Alpha
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.rgb_color.alpha = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
align_items: AlignItems::Center,
|
||||
flex_direction: FlexDirection::Row,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
}
|
||||
Children [
|
||||
:label("Hsl"),
|
||||
(color_swatch() SwatchType::Hsl)
|
||||
]
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::HslHue
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.hsl_color.hue = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::HslSaturation
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.hsl_color.saturation = change.value;
|
||||
})
|
||||
),
|
||||
(
|
||||
color_slider(ColorSliderProps {
|
||||
value: 0.5,
|
||||
channel: ColorChannel::HslLightness
|
||||
})
|
||||
on(|change: On<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
|
||||
color.hsl_color.lightness = change.value;
|
||||
})
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn demo_column_2() -> impl Scene {
|
||||
bsn! {
|
||||
Node {
|
||||
display: Display::Flex,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Stretch,
|
||||
justify_content: JustifyContent::Start,
|
||||
padding: UiRect::all(Val::Px(8.0)),
|
||||
row_gap: Val::Px(8.0),
|
||||
width: Val::Percent(30.),
|
||||
min_width: Val::Px(200.),
|
||||
}
|
||||
Children [
|
||||
(
|
||||
:pane Children [
|
||||
:pane_header Children [
|
||||
:tool_button(ButtonProps {
|
||||
variant: ButtonVariant::Primary,
|
||||
..default()
|
||||
}) Children [
|
||||
(Text("\u{0398}") ThemedText)
|
||||
],
|
||||
:pane_header_divider,
|
||||
:tool_button(ButtonProps{
|
||||
variant: ButtonVariant::Plain,
|
||||
..default()
|
||||
}) Children [
|
||||
(Text("\u{00BC}") ThemedText)
|
||||
],
|
||||
:tool_button(ButtonProps{
|
||||
variant: ButtonVariant::Plain,
|
||||
..default()
|
||||
}) Children [
|
||||
(Text("\u{00BD}") ThemedText)
|
||||
],
|
||||
:tool_button(ButtonProps{
|
||||
variant: ButtonVariant::Plain,
|
||||
..default()
|
||||
}) Children [
|
||||
(Text("\u{00BE}") ThemedText)
|
||||
],
|
||||
:pane_header_divider,
|
||||
:tool_button(ButtonProps{
|
||||
variant: ButtonVariant::Plain,
|
||||
..default()
|
||||
}) Children [
|
||||
:icon(icons::CHEVRON_DOWN)
|
||||
],
|
||||
:flex_spacer,
|
||||
:tool_button(ButtonProps{
|
||||
variant: ButtonVariant::Plain,
|
||||
..default()
|
||||
}) Children [
|
||||
:icon(icons::X)
|
||||
],
|
||||
],
|
||||
(
|
||||
:pane_body Children [
|
||||
:label_dim("A standard editor pane"),
|
||||
:subpane Children [
|
||||
:subpane_header Children [
|
||||
(Text("Left") ThemedText),
|
||||
(Text("Center") ThemedText),
|
||||
(Text("Right") ThemedText)
|
||||
],
|
||||
:subpane_body Children [
|
||||
:label_dim("A standard sub-pane"),
|
||||
:group Children [
|
||||
:group_header Children [
|
||||
(Text("Group") ThemedText),
|
||||
],
|
||||
:group_body Children [
|
||||
:label_dim("A standard group"),
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user