Skip to content

Commit 407d31e

Browse files
committed
State Pattern: "Music Player"
1 parent 1407e5a commit 407d31e

File tree

9 files changed

+249
-1
lines changed

9 files changed

+249
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ members = [
2222
"behavioral/mediator",
2323
"behavioral/memento",
2424
"behavioral/observer",
25+
"behavioral/state",
2526
]

behavioral/command/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ path = "main.rs"
99

1010
[dependencies]
1111
cursive = {version = "0.19", default-features = false, features = ["termion-backend"]}
12-
owning_ref = "0.4"

behavioral/state/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
edition = "2021"
3+
name = "state"
4+
version = "0.1.0"
5+
6+
[[bin]]
7+
name = "state"
8+
path = "main.rs"
9+
10+
[dependencies]
11+
cursive = {version = "0.19", default-features = false, features = ["termion-backend"]}

behavioral/state/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# State
2+
3+
State Pattern reminds a finite-state machine, but each state is represented by
4+
a separate trait (with a common supertrait) and there are transitions between these
5+
state traits.
6+
7+
The State Pattern is described in detail in The Rust Book:
8+
https://doc.rust-lang.org/book/ch17-03-oo-design-patterns.html
9+
10+
## Run
11+
12+
```bash
13+
cargo run --bin state
14+
```
15+
16+
Press buttons, ESC for exit, enjoy!
17+
18+
## Screenshots
19+
20+
| | |
21+
| ------------------------------ | ------------------------------ |
22+
| ![Stopped](images/stopped.png) | ![Playing](images/playing.png) |

behavioral/state/images/playing.png

12.1 KB
Loading

behavioral/state/images/stopped.png

9.89 KB
Loading

behavioral/state/main.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
mod player;
2+
mod state;
3+
4+
use cursive::{
5+
views::{Dialog, TextView},
6+
Cursive, event::Key, view::Nameable,
7+
};
8+
use player::Player;
9+
use state::{State, StoppedState};
10+
11+
struct PlayerApplication {
12+
player: Player,
13+
state: Box<dyn State>,
14+
}
15+
16+
fn main() {
17+
let mut app = cursive::default();
18+
19+
app.set_user_data(PlayerApplication {
20+
player: Player::default(),
21+
state: Box::new(StoppedState),
22+
});
23+
24+
app.add_layer(
25+
Dialog::around(TextView::new("Press Play").with_name("Player Status"))
26+
.title("Music Player")
27+
.button("Play", |s| execute(s, "Play"))
28+
.button("Stop", |s| execute(s, "Stop"))
29+
.button("Prev", |s| execute(s, "Prev"))
30+
.button("Next", |s| execute(s, "Next")),
31+
);
32+
33+
app.add_global_callback(Key::Esc, |s| s.quit());
34+
35+
app.run();
36+
}
37+
38+
fn execute(s: &mut Cursive, button: &'static str) {
39+
let PlayerApplication {mut player, mut state} = s.take_user_data().unwrap();
40+
let mut view = s.find_name::<TextView>("Player Status").unwrap();
41+
42+
state = match button {
43+
"Play" => state.play(&mut player),
44+
"Stop" => state.stop(&mut player),
45+
"Prev" => state.prev(&mut player),
46+
"Next" => state.next(&mut player),
47+
_ => unreachable!(),
48+
};
49+
50+
state.render(&player, &mut view);
51+
52+
s.set_user_data(PlayerApplication {player, state});
53+
}

behavioral/state/player.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
pub struct Track {
2+
pub title: String,
3+
pub duration: u32,
4+
cursor: u32,
5+
}
6+
7+
impl Track {
8+
pub fn new(title: &'static str, duration: u32) -> Self {
9+
Self {
10+
title: title.into(),
11+
duration,
12+
cursor: 0,
13+
}
14+
}
15+
}
16+
17+
pub struct Player {
18+
playlist: Vec<Track>,
19+
current_track: usize,
20+
_volume: u8,
21+
}
22+
23+
impl Default for Player {
24+
fn default() -> Self {
25+
Self {
26+
playlist: vec![
27+
Track::new("Track 1", 180),
28+
Track::new("Track 2", 165),
29+
Track::new("Track 3", 197),
30+
Track::new("Track 4", 205),
31+
],
32+
current_track: 0,
33+
_volume: 25,
34+
}
35+
}
36+
}
37+
38+
impl Player {
39+
pub fn next_track(&mut self) {
40+
self.current_track = (self.current_track + 1) % self.playlist.len();
41+
}
42+
43+
pub fn prev_track(&mut self) {
44+
self.current_track = (self.playlist.len() + self.current_track - 1) % self.playlist.len();
45+
}
46+
47+
pub fn play(&mut self) {
48+
self.track_mut().cursor = 10; // Playback imitation.
49+
}
50+
51+
pub fn pause(&mut self) {
52+
self.track_mut().cursor = 43; // Paused at some moment.
53+
}
54+
55+
pub fn rewind(&mut self) {
56+
self.track_mut().cursor = 0;
57+
}
58+
59+
pub fn track(&self) -> &Track {
60+
&self.playlist[self.current_track]
61+
}
62+
63+
fn track_mut(&mut self) -> &mut Track {
64+
&mut self.playlist[self.current_track]
65+
}
66+
}

behavioral/state/state.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use cursive::views::TextView;
2+
3+
use crate::player::Player;
4+
5+
pub struct StoppedState;
6+
pub struct PausedState;
7+
pub struct PlayingState;
8+
9+
pub trait State {
10+
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State>;
11+
fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State>;
12+
fn render(&self, player: &Player, view: &mut TextView);
13+
}
14+
15+
impl State for StoppedState {
16+
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
17+
player.play();
18+
19+
// Stopped -> Playing.
20+
Box::new(PlayingState)
21+
}
22+
23+
fn stop(self: Box<Self>, _: &mut Player) -> Box<dyn State> {
24+
// Change no state.
25+
self
26+
}
27+
28+
fn render(&self, _: &Player, view: &mut TextView) {
29+
view.set_content("[Stopped] Press 'Play'")
30+
}
31+
}
32+
33+
impl State for PausedState {
34+
fn play(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
35+
player.pause();
36+
37+
// Paused -> Playing.
38+
Box::new(PlayingState)
39+
}
40+
41+
fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
42+
player.pause();
43+
player.rewind();
44+
45+
// Paused -> Stopped.
46+
Box::new(StoppedState)
47+
}
48+
49+
fn render(&self, player: &Player, view: &mut TextView) {
50+
view.set_content(format!(
51+
"[Paused] {} - {} sec",
52+
player.track().title,
53+
player.track().duration
54+
))
55+
}
56+
}
57+
58+
impl State for PlayingState {
59+
fn play(self: Box<Self>, _: &mut Player) -> Box<dyn State> {
60+
// Playing -> Paused.
61+
Box::new(PausedState)
62+
}
63+
64+
fn stop(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
65+
player.pause();
66+
player.rewind();
67+
68+
// Playing -> Stopped.
69+
Box::new(StoppedState)
70+
}
71+
72+
fn render(&self, player: &Player, view: &mut TextView) {
73+
view.set_content(format!(
74+
"[Playing] {} - {} sec",
75+
player.track().title,
76+
player.track().duration
77+
))
78+
}
79+
}
80+
81+
// Default "next" and "prev" implementations for the trait.
82+
impl dyn State {
83+
pub fn next(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
84+
player.next_track();
85+
86+
// Change no state.
87+
self
88+
}
89+
90+
pub fn prev(self: Box<Self>, player: &mut Player) -> Box<dyn State> {
91+
player.prev_track();
92+
93+
// Change no state.
94+
self
95+
}
96+
}

0 commit comments

Comments
 (0)