Skip to content

Commit

Permalink
feat: create ScrollView composable and new ui module
Browse files Browse the repository at this point in the history
  • Loading branch information
matthunz committed Dec 7, 2024
1 parent 825e007 commit b3903b6
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 16 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ repository = "https://github.com/actuate-rs/actuate"
animation = ["ecs", "dep:bevy_math", "dep:bevy_time", "dep:tokio"]
ecs = ["std", "dep:bevy_app", "dep:bevy_ecs", "dep:bevy_hierarchy", "dep:bevy_utils", "dep:bevy_winit"]
executor = ["std", "dep:tokio"]
material = ["ecs", "picking", "dep:bevy_color", "dep:bevy_text", "dep:bevy_ui"]
material = ["ui", "ecs", "picking", "dep:bevy_color", "dep:bevy_input", "dep:bevy_text"]
picking = ["dep:bevy_picking"]
rt = ["executor", "tokio/rt-multi-thread"]
std = []
tracing = ["dep:tracing"]
ui = ["dep:bevy_ui"]
full = ["animation", "ecs", "material", "rt", "tracing"]
default = ["std"]

Expand All @@ -31,6 +32,7 @@ bevy_app = { version = "0.15.0", optional = true }
bevy_color = { version = "0.15.0", optional = true }
bevy_ecs = { version = "0.15.0", optional = true }
bevy_hierarchy = { version = "0.15.0", optional = true }
bevy_input = { version = "0.15.0", optional = true }
bevy_math = { version = "0.15.0", optional = true }
bevy_picking = { version = "0.15.0", optional = true }
bevy_text = { version = "0.15.0", optional = true }
Expand Down
20 changes: 11 additions & 9 deletions examples/http.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// HTTP UI example

use actuate::{executor::ExecutorContext, material::container, prelude::*};
use actuate::{executor::ExecutorContext, prelude::*};
use bevy::{prelude::*, winit::WinitSettings};
use serde::Deserialize;
use std::collections::HashMap;
Expand Down Expand Up @@ -49,16 +49,11 @@ impl Compose for BreedList {
});

// Render the currently loaded breeds.
spawn(Node {
flex_direction: FlexDirection::Column,
row_gap: Val::Px(30.),
overflow: Overflow::scroll_y(),
..default()
})
.content(compose::from_iter(breeds, |breed| Breed {
scroll_view(compose::from_iter(breeds, |breed| Breed {
name: breed.0,
families: breed.1,
}))
.flex_gap(Val::Px(30.))
}
}

Expand Down Expand Up @@ -86,5 +81,12 @@ fn setup(mut commands: Commands) {
commands.spawn(Camera2d::default());

// Spawn a composition with a `BreedList`, adding it to the Actuate runtime.
commands.spawn((Node::default(), Composition::new(Example)));
commands.spawn((
Node {
width: Val::Percent(100.),
height: Val::Percent(100.),
..default()
},
Composition::new(Example),
));
}
24 changes: 24 additions & 0 deletions src/ecs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ use std::{
task::{Context, Wake, Waker},
};

#[cfg(feature = "ui")]
use bevy_ui::{FlexDirection, Node, Val};

#[cfg(feature = "picking")]
use bevy_picking::prelude::*;

Expand Down Expand Up @@ -613,6 +616,27 @@ pub trait Modify<'a> {
})
}

#[cfg(feature = "ui")]
#[cfg_attr(docsrs, doc(cfg(feature = "ui")))]
/// Set the flex gap of this composable's spawned [`Node`].
///
/// This will set the `column_gap` for a `FlexDirection::Row` or `FlexDirection::RowReverse`
/// and the `row_gap` for a `FlexDirection::Column` or `FlexDirection::ColumnReverse`.
fn flex_gap(self, gap: Val) -> Self
where
Self: Sized,
{
self.modify(move |spawn| {
spawn.on_insert(move |mut entity| {
let mut node = entity.get_mut::<Node>().unwrap();
match node.flex_direction {
FlexDirection::Row | FlexDirection::RowReverse => node.column_gap = gap,
FlexDirection::Column | FlexDirection::ColumnReverse => node.row_gap = gap,
}
})
})
}

/// Add an observer to this composable's bundle.
fn observe<F, E, B, Marker>(self, observer: F) -> Self
where
Expand Down
16 changes: 10 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,14 @@ pub mod prelude {
#[cfg_attr(docsrs, doc(cfg(feature = "executor")))]
pub use crate::use_task;

#[cfg(feature = "ui")]
#[cfg_attr(docsrs, doc(cfg(feature = "ui")))]
pub use crate::ui::{scroll_view, ScrollView};

#[cfg(feature = "material")]
#[cfg_attr(docsrs, doc(cfg(feature = "material")))]
pub use crate::material::{
button, radio_button, text, Button, MaterialTheme, RadioButton, TypographyKind,
pub use crate::ui::material::{
button, container, radio_button, text, Button, MaterialTheme, RadioButton, TypographyKind,
TypographyStyleKind,
};
}
Expand Down Expand Up @@ -199,10 +203,10 @@ pub mod ecs;
/// Task execution context.
pub mod executor;

#[cfg(feature = "material")]
#[cfg_attr(docsrs, doc(cfg(feature = "material")))]
/// Material UI.
pub mod material;
#[cfg(feature = "ui")]
#[cfg_attr(docsrs, doc(cfg(feature = "ui")))]
/// User interface components.
pub mod ui;

/// Clone-on-write value.
///
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
109 changes: 109 additions & 0 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::{
ecs::{spawn, use_world, Modifier, Modify},
prelude::Compose,
use_mut, Scope, Signal, SignalMut,
};
use actuate_macros::Data;
use bevy_ecs::prelude::*;
use bevy_input::{
mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
};
use bevy_picking::prelude::*;
use bevy_ui::prelude::*;
use std::mem;

#[cfg(feature = "material")]
#[cfg_attr(docsrs, doc(cfg(feature = "material")))]
/// Material UI.
pub mod material;

/// Create a scroll view.
pub fn scroll_view<'a, C: Compose>(content: C) -> ScrollView<'a, C> {
ScrollView {
content,
line_size: 30.,
modifier: Modifier::default(),
}
}

#[derive(Data)]
#[actuate(path = "crate")]
/// Scroll view composable.
pub struct ScrollView<'a, C> {
content: C,
line_size: f32,
modifier: Modifier<'a>,
}

impl<C> ScrollView<'_, C> {
/// Set the line size to scroll (default: 30).
pub fn line_size(mut self, size: f32) -> Self {
self.line_size = size;
self
}
}

impl<C: Compose> Compose for ScrollView<'_, C> {
fn compose(cx: Scope<Self>) -> impl Compose {
let is_hovered = use_mut(&cx, || false);

let entity_cell = use_mut(&cx, || None);

use_world(
&cx,
move |mut mouse_wheel_events: EventReader<MouseWheel>,
mut scrolled_node_query: Query<&mut ScrollPosition>,
keyboard_input: Res<ButtonInput<KeyCode>>| {
for mouse_wheel_event in mouse_wheel_events.read() {
dbg!(mouse_wheel_event);
let (mut dx, mut dy) = match mouse_wheel_event.unit {
MouseScrollUnit::Line => (
mouse_wheel_event.x * cx.me().line_size,
mouse_wheel_event.y * cx.me().line_size,
),
MouseScrollUnit::Pixel => (mouse_wheel_event.x, mouse_wheel_event.y),
};

if keyboard_input.pressed(KeyCode::ControlLeft)
|| keyboard_input.pressed(KeyCode::ControlRight)
{
std::mem::swap(&mut dx, &mut dy)
}

if *is_hovered {
if let Some(entity) = *entity_cell {
if let Ok(mut scroll_position) = scrolled_node_query.get_mut(entity) {
scroll_position.offset_x -= dx;
scroll_position.offset_y -= dy;
}
}
}
}
},
);

let modifier = &cx.me().modifier;
let modifier: &Modifier = unsafe { mem::transmute(modifier) };

modifier
.apply(
spawn(Node {
height: Val::Percent(100.),
flex_direction: FlexDirection::Column,
overflow: Overflow::scroll_y(),
..Default::default()
})
.on_spawn(move |entity| SignalMut::set(entity_cell, Some(entity.id())))
.observe(move |_: Trigger<Pointer<Over>>| SignalMut::set(is_hovered, true))
.observe(move |_: Trigger<Pointer<Out>>| SignalMut::set(is_hovered, false)),
)
.content(unsafe { Signal::map_unchecked(cx.me(), |me| &me.content) })
}
}

impl<'a, C: Compose> Modify<'a> for ScrollView<'a, C> {
fn modifier(&mut self) -> &mut Modifier<'a> {
&mut self.modifier
}
}

0 comments on commit b3903b6

Please sign in to comment.