Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split UI Overflow by axis #8095

Merged
merged 17 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,16 @@ description = "Illustrates how FontAtlases are populated (used to optimize text
category = "UI (User Interface)"
wasm = true

[[example]]
name = "overflow_debug"
path = "examples/ui/overflow_debug.rs"

[package.metadata.example.overflow_debug]
name = "Overflow Debug"
description = "Simple example demonstrating overflow behavior"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "relative_cursor_position"
path = "examples/ui/relative_cursor_position.rs"
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/src/flex/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ mod tests {
height: Val::Px(0.),
},
aspect_ratio: None,
overflow: crate::Overflow::Hidden,
overflow: crate::Overflow::hidden(),
gap: Size {
width: Val::Px(0.),
height: Val::Percent(0.),
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Plugin for UiPlugin {
// NOTE: used by Style::aspect_ratio
.register_type::<Option<f32>>()
.register_type::<Overflow>()
.register_type::<OverflowAxis>()
.register_type::<PositionType>()
.register_type::<Size>()
.register_type::<UiRect>()
Expand Down
71 changes: 68 additions & 3 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,18 +581,83 @@ impl Default for JustifyContent {
/// Whether to show or hide overflowing items
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum Overflow {
pub struct Overflow {
/// Whether to show or hide overflowing items on the x axis
pub x: OverflowAxis,
/// Whether to show or hide overflowing items on the y axis
pub y: OverflowAxis,
}

impl Overflow {
pub const DEFAULT: Self = Self {
x: OverflowAxis::DEFAULT,
y: OverflowAxis::DEFAULT,
};

/// Show overflowing items on both axes
pub const fn visible() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Visible,
}
}

/// Hide overflowing items on both axes
pub const fn hidden() -> Self {
Self {
x: OverflowAxis::Hidden,
y: OverflowAxis::Hidden,
}
}

/// Hide overflowing items on the x axis
pub const fn x_hidden() -> Self {
Self {
x: OverflowAxis::Hidden,
y: OverflowAxis::Visible,
}
}

/// Hide overflowing items on the y axis
pub const fn y_hidden() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Hidden,
}
}

/// Overflow is visible on both axes
pub const fn is_visible(&self) -> bool {
self.x.is_visible() && self.y.is_visible()
}
}

impl Default for Overflow {
fn default() -> Self {
Self::DEFAULT
}
}

/// Whether to show or hide overflowing items
#[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)]
#[reflect(PartialEq, Serialize, Deserialize)]
pub enum OverflowAxis {
/// Show overflowing items.
Visible,
/// Hide overflowing items.
Hidden,
}

impl Overflow {
impl OverflowAxis {
pub const DEFAULT: Self = Self::Visible;

/// Overflow is visible on this axis
pub const fn is_visible(&self) -> bool {
matches!(self, Self::Visible)
}
}

impl Default for Overflow {
impl Default for OverflowAxis {
fn default() -> Self {
Self::DEFAULT
}
Expand Down
44 changes: 27 additions & 17 deletions crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! This module contains systems that update the UI when something changes

use crate::{CalculatedClip, Overflow, Style};
use crate::{CalculatedClip, Overflow, OverflowAxis, Style};

use super::Node;
use bevy_ecs::{
Expand Down Expand Up @@ -35,37 +35,47 @@ fn update_clipping(
children_query: &Query<&Children>,
node_query: &mut Query<(&Node, &GlobalTransform, &Style, Option<&mut CalculatedClip>)>,
entity: Entity,
clip: Option<Rect>,
maybe_inherited_clip: Option<Rect>,
) {
let (node, global_transform, style, calculated_clip) = node_query.get_mut(entity).unwrap();
let (node, global_transform, style, maybe_calculated_clip) =
node_query.get_mut(entity).unwrap();

// Update this node's CalculatedClip component
match (clip, calculated_clip) {
(None, None) => {}
match (maybe_inherited_clip, maybe_calculated_clip) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: what do you think about switching the position to (before, after) as in #8147? For me at least it's a tiny bit easier to understand:

  • (None, None) => noop
  • (Some, None) => remove
  • (None, Some) => add
  • (Some, Some) => update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to switch them because it's a good idea, I like semantic ordering etc, but then I realised that it's probably better to use nested if let statements as you avoid that which option is which ambiguity completely.

(None, Some(_)) => {
commands.entity(entity).remove::<CalculatedClip>();
}
(Some(clip), None) => {
commands.entity(entity).insert(CalculatedClip { clip });
(Some(inherited_clip), None) => {
commands.entity(entity).insert(CalculatedClip {
clip: inherited_clip,
});
}
(Some(clip), Some(mut old_clip)) => {
if old_clip.clip != clip {
*old_clip = CalculatedClip { clip };
(Some(inherited_clip), Some(mut calculated_clip)) => {
*calculated_clip = CalculatedClip {
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
clip: inherited_clip,
}
}
_ => {}
}

// Calculate new clip for its children
let children_clip = match style.overflow {
Overflow::Visible => clip,
Overflow::Hidden => {
let node_center = global_transform.translation().truncate();
let node_rect = Rect::from_center_size(node_center, node.calculated_size);
Some(clip.map_or(node_rect, |c| c.intersect(node_rect)))
let children_clip = if style.overflow.is_visible() {
maybe_inherited_clip
} else {
let mut node_rect = node.logical_rect(global_transform);
if style.overflow.x == OverflowAxis::Visible {
node_rect.min.x = -f32::INFINITY;
node_rect.max.x = f32::INFINITY;
}
if style.overflow.y == OverflowAxis::Visible {
node_rect.min.y = -f32::INFINITY;
node_rect.max.y = f32::INFINITY;
}
Some(maybe_inherited_clip.map_or(node_rect, |c| c.intersect(node_rect)))
};

if let Ok(children) = children_query.get(entity) {
for child in children.iter().cloned() {
for &child in children {
update_clipping(commands, children_query, node_query, child, children_clip);
}
}
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ Example | Description
--- | ---
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
[Overflow Debug](../examples/ui/overflow_debug.rs) | Simple example demonstrating overflow behavior
[Relative Cursor Position](../examples/ui/relative_cursor_position.rs) | Showcases the RelativeCursorPosition component
[Text](../examples/ui/text.rs) | Illustrates creating and updating text
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
Expand Down
97 changes: 97 additions & 0 deletions examples/ui/overflow_debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! Simple example demonstrating overflow behavior.

use bevy::{prelude::*, winit::WinitSettings};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
.insert_resource(WinitSettings::desktop_app())
.add_startup_system(setup)
.run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());

let text_style = TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 20.0,
color: Color::WHITE,
};

commands
.spawn(NodeBundle {
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
size: Size::width(Val::Percent(100.)),
..Default::default()
},
background_color: Color::BLACK.into(),
..Default::default()
})
.with_children(|parent| {
for overflow in [
Overflow::visible(),
Overflow::x_hidden(),
Overflow::y_hidden(),
Overflow::hidden(),
] {
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
margin: UiRect::horizontal(Val::Px(25.)),
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
let label = format!("{overflow:#?}");
parent
.spawn(NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(10.)),
margin: UiRect::bottom(Val::Px(25.)),
..Default::default()
},
background_color: Color::DARK_GRAY.into(),
..Default::default()
})
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text::from_section(label, text_style.clone()),
..Default::default()
});
});
parent
.spawn(NodeBundle {
style: Style {
size: Size::all(Val::Px(100.)),
padding: UiRect {
left: Val::Px(25.),
top: Val::Px(25.),
..Default::default()
},
overflow,
..Default::default()
},
background_color: Color::GRAY.into(),
..Default::default()
})
.with_children(|parent| {
parent.spawn(NodeBundle {
style: Style {
min_size: Size::all(Val::Px(100.)),
..Default::default()
},
background_color: Color::WHITE.into(),
..Default::default()
});
});
});
}
});
}
2 changes: 1 addition & 1 deletion examples/ui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
flex_direction: FlexDirection::Column,
align_self: AlignSelf::Stretch,
size: Size::height(Val::Percent(50.)),
overflow: Overflow::Hidden,
overflow: Overflow::y_hidden(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would maybe just use Overflow::hidden() just to keep the same behavior as before. It's not really a problem unless children use an explicit width bigger than the container… which we know doesn't happen.

Ignore me (resolve this) as you wish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking Overflow::y_hidden() is more expressive and intentional as it's a vertically scrolling list.
There is a lot to be said for only making minimal changes though and I get carried away sometimes 😅

..default()
},
background_color: Color::rgb(0.10, 0.10, 0.10).into(),
Expand Down