Skip to content
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
1,007 changes: 481 additions & 526 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ web = [
deployed_wasm_example = []

[dev-dependencies]
bevy = "0.16"
bevy = { version = "0.16", features = ["track_location"] }
rand = "0.9"
test-log = { version = "0.2", default-features = false, features = ["trace"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies.getrandom]
version = "0.3"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ fn ui_root() -> JonmoBuilder {
flex_direction: FlexDirection::Row,
column_gap: Val::Px(15.0),
align_items: AlignItems::Center,
padding: UiRect::all(Val::Px(25.)),
..default()
})
.insert(Counter(0))
Expand Down Expand Up @@ -117,7 +118,7 @@ fn ui_root() -> JonmoBuilder {
// `.dedupe()` ensures the rest of the chain only runs when the counter's value *actually
// changes*, preventing redundant updates every frame.
.dedupe()
// `Text` expects a `String`
// `Text` expects a `String` and `.to_string` expects a reference
.map_in_ref(ToString::to_string)
.map_in(Text)
// `component_signal` expects an `Option<Component>`. If the signal produces `None`, the
Expand Down
2 changes: 1 addition & 1 deletion examples/counter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fn ui_root() -> JonmoBuilder {
// `.dedupe()` ensures the rest of the chain only runs when the counter's value *actually
// changes*, preventing redundant updates every frame.
.dedupe()
// `Text` expects a `String`
// `Text` expects a `String` and `.to_string` expects a reference
.map_in_ref(ToString::to_string)
.map_in(Text)
// `component_signal` expects an `Option<Component>`. If the signal produces `None`, the
Expand Down
37 changes: 21 additions & 16 deletions examples/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use rand::{Rng, prelude::IndexedRandom};

fn main() {
let mut app = App::new();
let datas = MutableVec::from((0..12).map(|_| random_data()).collect::<Vec<_>>());
let rows = MutableVec::from((0..5).map(|_| ()).collect::<Vec<_>>());
let world = app.world_mut();
let datas = MutableVecBuilder::from((0..12).map(|_| random_data()).collect::<Vec<_>>()).spawn(world);
let rows = MutableVecBuilder::from((0..5).map(|_| ()).collect::<Vec<_>>()).spawn(world);
app.add_plugins(examples_plugin)
.insert_resource(Datas(datas.clone()))
.insert_resource(Rows(rows.clone()))
Expand Down Expand Up @@ -112,9 +113,10 @@ fn ui(items: MutableVec<Data>, rows: MutableVec<()>) -> JonmoBuilder {
TextLayout::new_with_justify(JustifyText::Center),
)))
.child(button("+", -2.).apply(on_click(
|_: Trigger<Pointer<Click>>, datas: Res<Datas>, mut commands: Commands| {
datas.0.write().insert(0, random_data());
commands.queue(datas.0.flush());
|_: Trigger<Pointer<Click>>,
datas: Res<Datas>,
mut mutable_vec_datas: Query<&mut MutableVecData<_>>| {
datas.0.write(&mut mutable_vec_datas).insert(0, random_data());
},
)))
.child(
Expand Down Expand Up @@ -148,9 +150,8 @@ fn ui(items: MutableVec<Data>, rows: MutableVec<()>) -> JonmoBuilder {
// BackgroundColor(Color::WHITE),
))
.child(button("+", -2.).apply(on_click(
|_: Trigger<Pointer<Click>>, rows: Res<Rows>, mut commands: Commands| {
rows.0.write().push(());
commands.queue(rows.0.flush());
|_: Trigger<Pointer<Click>>, rows: Res<Rows>, mut mutable_vec_datas: Query<&mut MutableVecData<_>>| {
rows.0.write(&mut mutable_vec_datas).push(());
},
))),
),
Expand Down Expand Up @@ -401,10 +402,12 @@ fn row(index: impl Signal<Item = Option<usize>>, items: MutableVec<Data>) -> Jon
button("-", -3.)
.component_signal(index.map_in(|index| index.map(Index)))
.apply(on_click(
|click: Trigger<Pointer<Click>>, rows: Res<Rows>, indices: Query<&Index>, mut commands: Commands| {
|click: Trigger<Pointer<Click>>,
rows: Res<Rows>,
indices: Query<&Index>,
mut mutable_vec_datas: Query<&mut MutableVecData<_>>| {
if let Ok(&Index(index)) = indices.get(click.target()) {
rows.0.write().remove(index);
commands.queue(rows.0.flush());
rows.0.write(&mut mutable_vec_datas).remove(index);
}
},
)),
Expand Down Expand Up @@ -501,19 +504,21 @@ fn item(index: impl Signal<Item = Option<usize>>, Data { number, color, shape }:
))
.component_signal(index.map_in(|index| index.map(Index)))
.apply(on_click(
|click: Trigger<Pointer<Click>>, datas: Res<Datas>, indices: Query<&Index>, mut commands: Commands| {
|click: Trigger<Pointer<Click>>,
datas: Res<Datas>,
indices: Query<&Index>,
mut mutable_vec_datas: Query<&mut MutableVecData<_>>| {
if let Ok(&Index(index)) = indices.get(click.target()) {
datas.0.write().remove(index);
commands.queue(datas.0.flush());
datas.0.write(&mut mutable_vec_datas).remove(index);
}
},
))
.child(JonmoBuilder::from((
.child((
Node::default(),
Text::new(number.to_string()),
TextColor(Color::BLACK),
TextLayout::new_with_justify(JustifyText::Center),
)))
))
}

fn camera(mut commands: Commands) {
Expand Down
21 changes: 10 additions & 11 deletions examples/letters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use jonmo::prelude::*;

fn main() {
let mut app = App::new();
let letters = MutableBTreeMap::from(
let letters = MutableBTreeMapBuilder::from(
ROWS.iter()
.flat_map(|row| row.chars().map(|letter| (letter, LetterData::default())))
.collect::<BTreeMap<_, _>>(),
);
)
.spawn(app.world_mut());
app.add_plugins(examples_plugin)
.insert_resource(Letters(letters.clone()))
.add_systems(
Expand Down Expand Up @@ -178,7 +179,11 @@ fn letter(letter: char, data: impl Signal<Item = LetterData> + Clone) -> JonmoBu
)
}

fn listen(keys: ResMut<ButtonInput<KeyCode>>, letters: Res<Letters>, mut commands: Commands) {
fn listen(
keys: ResMut<ButtonInput<KeyCode>>,
letters: Res<Letters>,
mut mutable_btree_map_datas: Query<&mut MutableBTreeMapData<char, LetterData>>,
) {
let map = HashMap::from([
(KeyCode::KeyA, 'a'),
(KeyCode::KeyB, 'b'),
Expand Down Expand Up @@ -207,33 +212,27 @@ fn listen(keys: ResMut<ButtonInput<KeyCode>>, letters: Res<Letters>, mut command
(KeyCode::KeyY, 'y'),
(KeyCode::KeyZ, 'z'),
]);
let mut flush = false;
for (key, char) in map.iter() {
if keys.just_pressed(*key) {
let mut guard = letters.0.write();
let mut guard = letters.0.write(&mut mutable_btree_map_datas);
guard.insert(
*char,
LetterData {
pressed: true,
count: guard.get(char).unwrap().count + 1,
},
);
flush = true;
} else if keys.just_released(*key) {
let mut guard = letters.0.write();
let mut guard = letters.0.write(&mut mutable_btree_map_datas);
guard.insert(
*char,
LetterData {
pressed: false,
..guard.get(char).unwrap().clone()
},
);
flush = true;
}
}
if flush {
commands.queue(letters.0.flush());
}
}

fn camera(mut commands: Commands) {
Expand Down
22 changes: 8 additions & 14 deletions examples/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ use jonmo::prelude::*;

fn main() {
let mut app = App::new();

let world = app.world_mut();
// 1. --- DATA SOURCE SETUP ---
// `MutableVec` is the core reactive data source for lists in `jonmo`.
// We initialize it with two random colors.
// It's wrapped in an `Arc<RwLock<...>>` internally, so cloning it is cheap
// and allows multiple systems to access and modify the same data.
let colors = MutableVec::from([random_color(), random_color()]);
let colors = MutableVecBuilder::from([random_color(), random_color()]).spawn(world);

app.add_plugins(examples_plugin)
// 2. --- RESOURCE MANAGEMENT ---
Expand Down Expand Up @@ -121,16 +121,14 @@ fn ui_root(colors: impl SignalVec<Item = Color>) -> JonmoBuilder {
// button entity to listen for a `Click` event.
world.entity_mut(entity).observe(
// This closure is the event handler that runs when the button is clicked.
move |_: Trigger<Pointer<Click>>, colors: Res<Colors>, mut commands: Commands| {
move |_: Trigger<Pointer<Click>>,
colors: Res<Colors>,
mut mutable_vec_datas: Query<&mut MutableVecData<_>>| {
// Try to get the `Index` component from the clicked entity.
// We found the index! Now we can mutate the central data source.
// `colors.0.write()` gets a write lock on the `MutableVec`.
let mut guard = colors.0.write();
let mut guard = colors.0.write(&mut mutable_vec_datas);
guard.insert(guard.len(), random_color());
// IMPORTANT: After mutating a `MutableVec`, you must call `flush()`
// to broadcast the changes to all listening signals.
// We queue the flush command to be run at the end of the frame.
commands.queue(colors.0.flush());
},
);
})
Expand Down Expand Up @@ -263,16 +261,12 @@ fn item(index: impl Signal<Item = Option<usize>> + Clone, color: Color) -> Jonmo
move |_: Trigger<Pointer<Click>>,
indices: Query<&Index>,
colors: Res<Colors>,
mut commands: Commands| {
mut mutable_vec_datas: Query<&mut MutableVecData<_>>| {
// Try to get the `Index` component from the clicked entity.
if let Ok(&Index(index)) = indices.get(entity) {
// We found the index! Now we can mutate the central data source.
// `colors.0.write()` gets a write lock on the `MutableVec`.
colors.0.write().remove(index);
// IMPORTANT: After mutating a `MutableVec`, you must call `flush()`
// to broadcast the changes to all listening signals.
// We queue the flush command to be run at the end of the frame.
commands.queue(colors.0.flush());
colors.0.write(&mut mutable_vec_datas).remove(index);
}
},
);
Expand Down
26 changes: 12 additions & 14 deletions examples/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ use jonmo::prelude::*;

fn main() {
let mut app = App::new();
let numbers = MutableVec::from([1, 2, 3, 4, 5]);
let numbers = MutableVecBuilder::from([1, 2, 3, 4, 5]).spawn(app.world_mut());
app.add_plugins(examples_plugin)
.insert_resource(Numbers(numbers.clone()))
.add_systems(
PostStartup,
(
move |world: &mut World| {
ui_root(numbers.clone()).spawn(world);
ui_root(numbers.clone(), world).spawn(world);
},
camera,
),
Expand All @@ -32,10 +32,10 @@ struct Numbers(MutableVec<i32>);
struct Lifetime(f32);

#[rustfmt::skip]
fn ui_root(numbers: MutableVec<i32>) -> JonmoBuilder {
let list_a = MutableVec::from([1, 2, 3, 4, 5]).signal_vec();
let list_b = MutableVec::from([3, 4, 5]).signal_vec();
let map = MutableBTreeMap::from([(1, 2), (2, 3)]);
fn ui_root(numbers: MutableVec<i32>, world: &mut World) -> JonmoBuilder {
let list_a = MutableVecBuilder::from([1, 2, 3, 4, 5]).spawn(world).signal_vec();
let list_b = MutableVecBuilder::from([3, 4, 5]).spawn(world).signal_vec();
let map = MutableBTreeMapBuilder::from([(1, 2), (2, 3)]).spawn(world);
// map.signal_map().
JonmoBuilder::from(Node {
height: Val::Percent(100.0),
Expand Down Expand Up @@ -136,18 +136,16 @@ fn live(mut lifetimes: Query<&mut Lifetime>, time: Res<Time>) {
}
}

fn hotkeys(keys: Res<ButtonInput<KeyCode>>, numbers: ResMut<Numbers>, mut commands: Commands) {
let mut flush = false;
let mut guard = numbers.0.write();
fn hotkeys(
keys: Res<ButtonInput<KeyCode>>,
numbers: ResMut<Numbers>,
mut mutable_vec_datas: Query<&mut MutableVecData<i32>>,
) {
let mut guard = numbers.0.write(&mut mutable_vec_datas);
if keys.just_pressed(KeyCode::Equal) {
guard.push((guard.len() + 1) as i32);
flush = true;
} else if keys.just_pressed(KeyCode::Minus) {
guard.pop();
flush = true;
}
if flush {
commands.queue(numbers.0.flush());
}
}

Expand Down
3 changes: 3 additions & 0 deletions repo_prompt.ncl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let examples_basic = import "examples/basic.rs" as 'Text in
let examples_lifetimes = import "examples/lifetimes.rs" as 'Text in
let examples_filters = import "examples/filters.rs" as 'Text in
let examples_counter = import "examples/counter.rs" as 'Text in
let examples_letters = import "examples/letters.rs" as 'Text in
let examples_test = import "examples/test.rs" as 'Text in
let examples_utils = import "examples/utils.rs" as 'Text in
let repo_structure = {
Expand All @@ -26,6 +27,7 @@ let repo_structure = {
"examples/lifetimes.rs" = examples_lifetimes,
"examples/filters.rs" = examples_filters,
"examples/counter.rs" = examples_counter,
"examples/letters.rs" = examples_letters,
"examples/test.rs" = examples_test,
"examples/utils.rs" = examples_utils,
} in
Expand All @@ -43,6 +45,7 @@ jonmo is a functional reactive signals implementation in rust for the bevy game
│ ├── counter.rs
│ ├── lifetimes.rs
│ ├── filters.rs
│ ├── letters.rs
│ ├── test.rs
│ └── utils.rs
└── src/
Expand Down
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
edition = "2024"

max_width = 120
format_code_in_doc_comments = true
# from bevy
use_field_init_shorthand = true
newline_style = "Unix"
Expand Down
Loading
Loading