Skip to content

Commit

Permalink
Refine global state API + TodoMVC (#36)
Browse files Browse the repository at this point in the history
*add TodoMVC example

*add global_state macro
  • Loading branch information
JunichiSugiura authored Aug 22, 2022
1 parent cc745c9 commit 8b76a03
Show file tree
Hide file tree
Showing 28 changed files with 1,018 additions and 189 deletions.
16 changes: 15 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ bevy_dioxus_desktop = { version = "0.1", path = "./packages/desktop", optional =
bevy_dioxus_macro = { version = "0.1", path = "./packages/macro" }

[dev-dependencies]
leafwing-input-manager = { version = "0.5", default-features = false }
chrono = "0.4"
dioxus = { version = "0.2", features = ["fermi"] }
leafwing-input-manager = { version = "0.5", default-features = false }
futures-intrusive = "0.4"

[features]
default = ["desktop"]
Expand All @@ -31,6 +33,18 @@ members = [
"packages/macro",
]

[[example]]
name = "counter-channel"
path = "examples/counter/channel.rs"

[[example]]
name = "counter-global-state-ecs"
path = "examples/counter/global-state-ecs.rs"

[[example]]
name = "todomvc"
path = "examples/todomvc/main.rs"

[[example]]
name = "window-settings"
path = "examples/window/settings.rs"
Expand Down
82 changes: 5 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Dioxus is a cross-platform declarative UI framework. It provides familiar featur
### Bevy
Bevy is a cutting-edge game engine in Rust based on Entity Component System(ECS) design pattern. Think of it as a global state management tool like Redux but much more performant because all systems will run concurrently as much as possible. Thanks to its plugin system, there's already a handlfull of third-party Bevy plugins out there. Imagine implemnenting core logic as `CorePlugin` seperated from UI layer. You may start with `bevy_dioxus` to build desektop application. Then let's say you want to release a metaverse edition at some point in the future, it's as simple as swapping UI plugin to Bevy's 3d rendering plugin while still using the same CorePlugin.

## Try examples
## Examples

Make sure to install all prerequisites for Tauri.
[Prerequisites](https://tauri.studio/v1/guides/getting-started/prerequisites)
Expand All @@ -69,82 +69,10 @@ gh repo clone JunichiSugiura/bevy_dioxus
cd bevy_dioxus

cargo run --example counter
cargo run --example todomvc
```

More examples can be found in [examples/](https://github.com/JunichiSugiura/bevy_dioxus/tree/main/examples) directory.
Find more examples in [examples/](https://github.com/JunichiSugiura/bevy_dioxus/tree/main/examples) directory.

## Development

### Prerequisites
#### General
- [Tauri prerequisites](https://tauri.studio/v1/guides/getting-started/prerequisites)
- [convco](https://github.com/convco/convco#installation): Conventional commits, changelog, versioning, validation
```sh
cargo install convco
# or
brew install convco/formulae/convco
```
- [cargo-workspaces](https://github.com/pksunkara/cargo-workspaces): A tool for managing cargo workspaces and their crates, inspired by lerna
```sh
cargo install cargo-workspaces
```

#### Website
- [Zola](https://github.com/getzola/zola): A fast static site generator in a single binary with everything built-in.
```sh
brew install zola
```
- [Node.js](https://nodejs.org/en/download/): To install Tailwind CSS

#### API Reference
- [cargo-watch](https://github.com/watchexec/cargo-watch): Watches over your Cargo project's source.
```sh
cargo install cargo-watch
```

### Run
#### Examples
```sh
# Build
cargo build --examples

# or Run
cargo run --example counter
```

#### Website
```sh
# Install dependencies
npm i

# Serve locally
zola -r packages/website serve --drafts

# Watch Tailwind CSS
npm run watch

# or build
npm run build
```

#### API Reference
```sh
# Serve doc locally
cargo doc --open --no-deps

# Watch file changes and serve doc locally
cargo watch -s 'cargo doc && http target/doc'
```

### Conventions

#### Branch name example
```sh
git checkout -b docs/#20-guide-website
```

#### Conventional Commits
Make sure to use `convco commit` instead of `git commit` when it should be noted in changelog. [git-cliff](https://github.com/orhun/git-cliff) will automatically generates changelog on conventional-commit message that convco produces.
```sh
convco commit
```
## Milestone
[📌 bevy_dioxus - Project board](https://github.com/users/JunichiSugiura/projects/4/views/9)
File renamed without changes.
File renamed without changes.
47 changes: 35 additions & 12 deletions examples/counter-global.rs → examples/counter/global-state-ecs.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
use bevy::{log::LogPlugin, prelude::*};
use bevy_dioxus::{core::prelude::*, desktop::prelude::*};
use bevy_dioxus::desktop::prelude::*;
use dioxus::prelude::*;

fn main() {
App::new()
.add_plugin(LogPlugin)
.add_plugin(GlobalStatePlugin)
.add_plugin(DioxusPlugin::<GlobalState, CoreCommand, ()>::new(Root))
.add_event::<UpdateGlobalState>()
.add_startup_system(setup)
.add_system(handle_core_cmd)
.add_system(update_global_state)
.run();
}

/// Make sure to implement Default trait.
#[derive(Component, Clone, Debug, Default, GlobalState)]
#[global_state]
struct GlobalState {
count: u32,
disabled: bool,
}

#[derive(Component, Clone, Debug, Default)]
struct Count(u32);

#[derive(Component, Clone, Debug, GlobalState)]
#[derive(Component, Clone, Debug)]
struct Disabled(bool);

impl Default for Disabled {
Expand All @@ -25,28 +32,29 @@ impl Default for Disabled {
}
}

/// Warning: Execution order matters here. Make sure to place this line after deriving all GlobalState.
#[derive(GlobalStatePlugin)]
struct GlobalStatePlugin;

#[derive(Clone, Debug)]
enum CoreCommand {
Increment,
Decrement,
Reset,
}

fn setup(mut commands: Commands) {
struct UpdateGlobalState;

fn setup(mut commands: Commands, mut update_global_state: EventWriter<UpdateGlobalState>) {
info!("🧠 Spawn count");
commands
.spawn()
.insert(Count::default())
.insert(Disabled::default());

update_global_state.send(UpdateGlobalState);
}

fn handle_core_cmd(
mut events: EventReader<CoreCommand>,
mut query: Query<(&mut Count, &mut Disabled)>,
mut update_global_state: EventWriter<UpdateGlobalState>,
) {
for cmd in events.iter() {
let (mut count, mut disabled) = query.single_mut();
Expand All @@ -69,6 +77,21 @@ fn handle_core_cmd(
}
};
disabled.0 = count.0 == 0;

update_global_state.send(UpdateGlobalState);
}
}

fn update_global_state(
mut events: EventReader<UpdateGlobalState>,
query: Query<(&Count, &Disabled)>,
mut global_state: EventWriter<GlobalState>,
) {
for _ in events.iter() {
let (count, disabled) = query.single();

global_state.send(GlobalState::Count(count.0));
global_state.send(GlobalState::Disabled(disabled.0));
}
}

Expand All @@ -81,15 +104,15 @@ fn Root(cx: Scope) -> Element {

cx.render(rsx! {
h1 { "Counter Example" }
p { "count: {count.0}" }
p { "count: {count}" }
button {
onclick: move |_| window.send(CoreCommand::Decrement),
disabled: "{disabled.0}",
disabled: "{disabled}",
"-",
}
button {
onclick: move |_| window.send(CoreCommand::Reset),
disabled: "{disabled.0}",
disabled: "{disabled}",
"Reset"
}
button {
Expand Down
42 changes: 33 additions & 9 deletions examples/global-state-ecs.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
use bevy::prelude::*;
use bevy_dioxus::{core::prelude::*, desktop::prelude::*};
use bevy_dioxus::desktop::prelude::*;
use dioxus::prelude::*;

fn main() {
App::new()
.add_plugin(GlobalStatePlugin)
.add_plugin(DioxusPlugin::<GlobalState, CoreCommand, ()>::new(Root))
.add_event::<UpdateGlobalState>()
.add_startup_system(setup)
.add_system(handle_core_cmd)
.add_system(update_global_state)
.run();
}

#[derive(Component, Clone, Debug, GlobalState)]
#[global_state]
struct GlobalState {
name: String,
}

#[derive(Component, Clone, Debug)]
struct Name(String);

impl Default for Name {
Expand All @@ -20,20 +27,37 @@ impl Default for Name {
}
}

#[derive(GlobalStatePlugin)]
struct GlobalStatePlugin;

#[derive(Clone, Debug)]
struct CoreCommand(String);

fn setup(mut commands: Commands) {
struct UpdateGlobalState;

fn setup(mut commands: Commands, mut update_global_state: EventWriter<UpdateGlobalState>) {
commands.spawn().insert(Name::default());
update_global_state.send(UpdateGlobalState);
}

fn handle_core_cmd(mut events: EventReader<CoreCommand>, mut query: Query<&mut Name>) {
fn handle_core_cmd(
mut events: EventReader<CoreCommand>,
mut query: Query<&mut Name>,
mut update_global_state: EventWriter<UpdateGlobalState>,
) {
for cmd in events.iter() {
let mut name = query.single_mut();
name.0 = cmd.0.clone();

update_global_state.send(UpdateGlobalState);
}
}

fn update_global_state(
mut events: EventReader<UpdateGlobalState>,
query: Query<&Name>,
mut global_state: EventWriter<GlobalState>,
) {
for _ in events.iter() {
let name = query.single();
global_state.send(GlobalState::Name(name.0.clone()))
}
}

Expand All @@ -43,10 +67,10 @@ fn Root(cx: Scope) -> Element {
let window = use_window::<CoreCommand, ()>(&cx);

cx.render(rsx! {
h1 { "Hello, {name.0} !" }
h1 { "Hello, {name} !" }

input {
value: "{name.0}",
value: "{name}",
oninput: |e| {
window.send(CoreCommand(e.value.to_string()));
},
Expand Down
66 changes: 66 additions & 0 deletions examples/todomvc/channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use crate::{event::*, resource::*};
use bevy::ecs::prelude::*;

#[derive(Clone, Debug)]
pub enum CoreCommand {
CreateTodo(CreateTodo),
ChangeTitle(ChangeTitle),
ToggleDone(ToggleDone),
RemoveTodo(RemoveTodo),
ChangeFilter(ChangeFilter),
ClearCompleted(ClearCompleted),
ToggleAll(ToggleAll),
}

impl CoreCommand {
pub fn create_todo(title: &String) -> Self {
Self::CreateTodo(CreateTodo {
title: title.clone(),
})
}

pub fn change_title(entity: &Entity, title: &String) -> Self {
Self::ChangeTitle(ChangeTitle {
entity: entity.clone(),
title: title.clone(),
})
}

pub fn toggle_done(entity: &Entity) -> Self {
Self::ToggleDone(ToggleDone {
entity: entity.clone(),
})
}

pub fn remove_todo(entity: &Entity) -> Self {
Self::RemoveTodo(RemoveTodo {
entity: entity.clone(),
})
}

pub fn filter_all() -> Self {
Self::ChangeFilter(ChangeFilter {
filter: Filter::All,
})
}

pub fn filter_active() -> Self {
Self::ChangeFilter(ChangeFilter {
filter: Filter::Active,
})
}

pub fn filter_completed() -> Self {
Self::ChangeFilter(ChangeFilter {
filter: Filter::Completed,
})
}

pub fn toggle_all() -> Self {
Self::ToggleAll(ToggleAll)
}

pub fn clear_completed() -> Self {
Self::ClearCompleted(ClearCompleted)
}
}
Loading

1 comment on commit 8b76a03

@vercel
Copy link

@vercel vercel bot commented on 8b76a03 Aug 22, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.