diff --git a/Cargo.toml b/Cargo.toml index dcda92a1..9a38251d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "macro_ui", "rpg_tools_core", "rpg_tools_rendering", + "rpg_tools_ui", "rpg_tools_editor", ] resolver = "2" \ No newline at end of file diff --git a/macro_convert/Cargo.toml b/macro_convert/Cargo.toml index 05ddda0d..0267e325 100644 --- a/macro_convert/Cargo.toml +++ b/macro_convert/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "macro_convert" -version = "0.3.0" +version = "0.4.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/macro_core/Cargo.toml b/macro_core/Cargo.toml index 7fca3dfd..e7e27d11 100644 --- a/macro_core/Cargo.toml +++ b/macro_core/Cargo.toml @@ -1,4 +1,4 @@ [package] name = "macro_core" -version = "0.3.0" +version = "0.4.0" edition = "2021" diff --git a/macro_ui/Cargo.toml b/macro_ui/Cargo.toml index 292a582e..f2346dfc 100644 --- a/macro_ui/Cargo.toml +++ b/macro_ui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "macro_ui" -version = "0.3.0" +version = "0.4.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/resources/characters/characters.yaml b/resources/characters/characters.yaml index fe67a41f..4e2f93ff 100644 --- a/resources/characters/characters.yaml +++ b/resources/characters/characters.yaml @@ -1,5 +1,6 @@ - id: 0 name: Elf + race: 1 gender: Male appearance: type: Humanoid @@ -88,6 +89,7 @@ millimetre: 1500 - id: 1 name: Devil + race: 4 gender: Male appearance: type: Humanoid @@ -139,45 +141,8 @@ height: millimetre: 2500 - id: 2 - name: Watcher - gender: Genderless - appearance: - type: HeadOnly - head: - ears: - type: None - eyes: - type: One - eye: - type: Normal - eye_shape: Circle - pupil_shape: VerticalSlit - pupil_color: Blue - background_color: White - eyebrows: - type: Normal - color: Fuchsia - shape: Curved - style: Even - width: Average - hair: - type: None - mouth: - type: Simple - beard: - type: None - width: Medium - teeth: - type: None - teeth_color: White - shape: Round - skin: - type: Scales - color: Purple - height: - millimetre: 1500 -- id: 3 name: Vampire + race: 5 gender: Female appearance: type: Humanoid @@ -248,8 +213,9 @@ color: White height: millimetre: 1500 -- id: 4 +- id: 3 name: Orc + race: 2 gender: Male appearance: type: Humanoid @@ -320,8 +286,9 @@ color: Green height: millimetre: 1500 -- id: 5 +- id: 4 name: Dwarf + race: 3 gender: Male appearance: type: Humanoid diff --git a/resources/races.yaml b/resources/races.yaml new file mode 100644 index 00000000..cb5b19fa --- /dev/null +++ b/resources/races.yaml @@ -0,0 +1,18 @@ +- id: 0 + name: Human + gender_option: TwoGenders +- id: 1 + name: Elf + gender_option: TwoGenders +- id: 2 + name: Orc + gender_option: TwoGenders +- id: 3 + name: Dwarf + gender_option: TwoGenders +- id: 4 + name: Devil + gender_option: NoGender +- id: 5 + name: Vampire + gender_option: TwoGenders diff --git a/resources/templates/appearance_edit.html.tera b/resources/templates/appearance_edit.html.tera index 4d4f35f3..ab86319d 100644 --- a/resources/templates/appearance_edit.html.tera +++ b/resources/templates/appearance_edit.html.tera @@ -8,7 +8,7 @@ Back Preview
-
+ - +
-

Back

+

Back

{% endblock content %} diff --git a/resources/templates/character.html.tera b/resources/templates/character.html.tera deleted file mode 100644 index 0913f4f1..00000000 --- a/resources/templates/character.html.tera +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base" %} - -{% block content %} -
-

{{ name }}

-

Data

-
-

Id: {{ id }}

-

Gender: {{ gender }}

-

Edit Data

-
-

Appearance

-
- Front View of the Character - Back View of the Character -
-
-

Edit Appearance

-

Back

-
-
-{% endblock content %} diff --git a/resources/templates/character/all.html.tera b/resources/templates/character/all.html.tera new file mode 100644 index 00000000..d9101c44 --- /dev/null +++ b/resources/templates/character/all.html.tera @@ -0,0 +1,20 @@ +{% extends "base" %} + +{% block content %} +

Characters

+
+

Total: {{ total }}

+

Add

+
+
+ {% for c in characters %} +
+ +
{{ c.1 }}
+ Character {{ c.0 }} +
+
+ {% endfor %} +
+

Back

+{% endblock content %} diff --git a/resources/templates/character/details.html.tera b/resources/templates/character/details.html.tera new file mode 100644 index 00000000..762d2c10 --- /dev/null +++ b/resources/templates/character/details.html.tera @@ -0,0 +1,23 @@ +{% extends "base" %} + +{% block content %} +
+

Character: {{ name }}

+

Data

+
+

Id: {{ id }}

+

Race: {{ race }}

+

Gender: {{ gender }}

+

Edit Data

+
+

Appearance

+
+ Front View of the Character + Back View of the Character +
+
+

Edit Appearance

+

Back

+
+
+{% endblock content %} diff --git a/resources/templates/character/edit.html.tera b/resources/templates/character/edit.html.tera new file mode 100644 index 00000000..4d410edf --- /dev/null +++ b/resources/templates/character/edit.html.tera @@ -0,0 +1,27 @@ +{% extends "base" %} + +{% block content %} +

Edit data of {{ name }}

+
+

Id: {{ id }}

+
+ + + {% if name_error %}
{% endif %} +
+
+ + {{ macros::add_select(name="race", options=races, selected=race) }} + {% if race_error %}
{% endif %} +
+
+ + {{ macros::add_select(name="gender", options=genders, selected=gender) }} + {% if gender_error %}
{% endif %} +
+
+ +
+

Back

+
+{% endblock content %} diff --git a/resources/templates/character_edit.html.tera b/resources/templates/character_edit.html.tera deleted file mode 100644 index cfe23731..00000000 --- a/resources/templates/character_edit.html.tera +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base" %} - -{% block content %} -

Edit data of {{ name }}

-

Id: {{ id }}

-
- - -
-
- - {{ macros::add_select(name="gender", options=genders, selected=gender) }} -
-
- -
-

Back

-{% endblock content %} diff --git a/resources/templates/characters.html.tera b/resources/templates/characters.html.tera deleted file mode 100644 index c6dbd8ed..00000000 --- a/resources/templates/characters.html.tera +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base" %} - -{% block content %} -

Characters

-

Total: {{ total }}

-

Add

-
- {% for c in characters %} -
- -
{{ c.1 }}
- Character {{ c.0 }} -
-
- {% endfor %} -
-

Back

-{% endblock content %} diff --git a/resources/templates/home.html.tera b/resources/templates/home.html.tera index 823f40d2..16935c0a 100644 --- a/resources/templates/home.html.tera +++ b/resources/templates/home.html.tera @@ -3,5 +3,8 @@ {% block content %}

RPG Tools - Editor

Overview

-

Characters: {{ characters }}

+
+

Races: {{ races }}

+

Characters: {{ characters }}

+
{% endblock content %} diff --git a/resources/templates/race/all.html.tera b/resources/templates/race/all.html.tera new file mode 100644 index 00000000..739fcdbe --- /dev/null +++ b/resources/templates/race/all.html.tera @@ -0,0 +1,15 @@ +{% extends "base" %} + +{% block content %} +

Races

+
+

Total: {{ total }}

+ +

Add

+

Back

+
+{% endblock content %} diff --git a/resources/templates/race/details.html.tera b/resources/templates/race/details.html.tera new file mode 100644 index 00000000..2bcedf2e --- /dev/null +++ b/resources/templates/race/details.html.tera @@ -0,0 +1,22 @@ +{% extends "base" %} + +{% block content %} +
+

Race: {{ name }}

+

Data

+
+

Id: {{ id }}

+

Gender Option: {{ gender_option }}

+

Edit Data

+
+

Characters

+
+ +

Back

+
+
+{% endblock content %} diff --git a/resources/templates/race/edit.html.tera b/resources/templates/race/edit.html.tera new file mode 100644 index 00000000..4469a04c --- /dev/null +++ b/resources/templates/race/edit.html.tera @@ -0,0 +1,22 @@ +{% extends "base" %} + +{% block content %} +

Edit Race: {{ name }}

+
+

Id: {{ id }}

+
+ + + {% if name_error %}
{% endif %} +
+
+ + {{ macros::add_select(name="gender_option", options=gender_options, selected=gender_option) }} + {% if gender_error %}
{% endif %} +
+
+ +
+

Back

+
+{% endblock content %} diff --git a/rpg_tools_core/Cargo.toml b/rpg_tools_core/Cargo.toml index 57c52130..e2fe46d7 100644 --- a/rpg_tools_core/Cargo.toml +++ b/rpg_tools_core/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "rpg_tools_core" -version = "0.3.0" +version = "0.4.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0" macro_core = { path = "../macro_core" } macro_convert = { path = "../macro_convert" } macro_ui = { path = "../macro_ui" } -serde = { version = "1.0", features = ["derive"] } -titlecase = "2.2" \ No newline at end of file +serde = { version = "1.0", features = ["derive"] } \ No newline at end of file diff --git a/rpg_tools_core/src/lib.rs b/rpg_tools_core/src/lib.rs index 51ff0b59..074ba809 100644 --- a/rpg_tools_core/src/lib.rs +++ b/rpg_tools_core/src/lib.rs @@ -1,5 +1,5 @@ pub mod model; -pub mod ui; +pub mod usecase; extern crate macro_core; extern crate macro_ui; diff --git a/rpg_tools_core/src/model/character/mod.rs b/rpg_tools_core/src/model/character/mod.rs index 2b196e5a..e51bc76d 100644 --- a/rpg_tools_core/src/model/character/mod.rs +++ b/rpg_tools_core/src/model/character/mod.rs @@ -1,5 +1,6 @@ use crate::model::character::appearance::Appearance; use crate::model::character::gender::Gender; +use crate::model::race::RaceId; use serde::{Deserialize, Serialize}; pub mod appearance; @@ -25,6 +26,7 @@ impl CharacterId { pub struct Character { id: CharacterId, name: String, + race: RaceId, gender: Gender, appearance: Appearance, } @@ -34,6 +36,7 @@ impl Character { Character { id, name: format!("Character {}", id.0), + race: RaceId::new(0), gender: Gender::default(), appearance: Appearance::default(), } @@ -51,6 +54,14 @@ impl Character { self.name = name; } + pub fn race(&self) -> RaceId { + self.race + } + + pub fn set_race(&mut self, race: RaceId) { + self.race = race; + } + pub fn gender(&self) -> Gender { self.gender } diff --git a/rpg_tools_core/src/model/mod.rs b/rpg_tools_core/src/model/mod.rs index 53fc634a..7b278110 100644 --- a/rpg_tools_core/src/model/mod.rs +++ b/rpg_tools_core/src/model/mod.rs @@ -1,8 +1,18 @@ +use crate::model::character::manager::CharacterMgr; +use crate::model::race::manager::RaceMgr; + pub mod character; pub mod color; pub mod equipment; pub mod length; +pub mod race; pub mod side; pub mod size; pub mod transparency; pub mod width; + +#[derive(Default, Debug)] +pub struct RpgData { + pub character_manager: CharacterMgr, + pub race_manager: RaceMgr, +} diff --git a/rpg_tools_core/src/model/race/gender.rs b/rpg_tools_core/src/model/race/gender.rs new file mode 100644 index 00000000..83d89e97 --- /dev/null +++ b/rpg_tools_core/src/model/race/gender.rs @@ -0,0 +1,33 @@ +use crate::model::character::gender::Gender; +use macro_convert::Convert; +use serde::{Deserialize, Serialize}; + +/// Which [`genders`](Gender) are available for members of this [`race`](crate::model::race::Race)? +#[derive(Convert, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +pub enum GenderOption { + NoGender, + TwoGenders, +} + +impl GenderOption { + /// Is the [`gender`](Gender) valid for this option? + /// + /// ``` + ///# use rpg_tools_core::model::race::gender::GenderOption::*; + ///# use rpg_tools_core::model::character::gender::Gender::*; + /// + /// assert!(!NoGender.is_valid(Female)); + /// assert!(!NoGender.is_valid(Male)); + /// assert!(NoGender.is_valid(Genderless)); + /// + /// assert!(TwoGenders.is_valid(Female)); + /// assert!(TwoGenders.is_valid(Male)); + /// assert!(!TwoGenders.is_valid(Genderless)); + /// ``` + pub fn is_valid(&self, gender: Gender) -> bool { + match self { + GenderOption::NoGender => gender == Gender::Genderless, + GenderOption::TwoGenders => gender == Gender::Female || gender == Gender::Male, + } + } +} diff --git a/rpg_tools_core/src/model/race/manager.rs b/rpg_tools_core/src/model/race/manager.rs new file mode 100644 index 00000000..89e0893e --- /dev/null +++ b/rpg_tools_core/src/model/race/manager.rs @@ -0,0 +1,31 @@ +use crate::model::race::{Race, RaceId}; + +/// Manages & stores the [`race`](Race). +#[derive(Default, Debug)] +pub struct RaceMgr { + races: Vec, +} + +impl RaceMgr { + pub fn new(races: Vec) -> Self { + Self { races } + } + + pub fn create(&mut self) -> RaceId { + let id = RaceId::new(self.races.len()); + self.races.push(Race::new(id)); + id + } + + pub fn get_all(&self) -> &Vec { + &self.races + } + + pub fn get(&self, id: RaceId) -> Option<&Race> { + self.races.get(id.0) + } + + pub fn get_mut(&mut self, id: RaceId) -> Option<&mut Race> { + self.races.get_mut(id.0) + } +} diff --git a/rpg_tools_core/src/model/race/mod.rs b/rpg_tools_core/src/model/race/mod.rs new file mode 100644 index 00000000..aa047cad --- /dev/null +++ b/rpg_tools_core/src/model/race/mod.rs @@ -0,0 +1,57 @@ +use crate::model::race::gender::GenderOption; +use serde::{Deserialize, Serialize}; + +pub mod gender; +pub mod manager; + +/// The unique identifier of a [`race`](Race). +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct RaceId(usize); + +impl RaceId { + pub fn new(id: usize) -> Self { + Self(id) + } + + pub fn id(&self) -> usize { + self.0 + } +} + +/// A race like human, elf or dragon. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Race { + id: RaceId, + name: String, + gender_option: GenderOption, +} + +impl Race { + pub fn new(id: RaceId) -> Self { + Race { + id, + name: format!("Race {}", id.0), + gender_option: GenderOption::TwoGenders, + } + } + + pub fn id(&self) -> &RaceId { + &self.id + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + pub fn gender_option(&self) -> GenderOption { + self.gender_option + } + + pub fn set_gender_option(&mut self, gender_option: GenderOption) { + self.gender_option = gender_option; + } +} diff --git a/rpg_tools_core/src/usecase/edit/character.rs b/rpg_tools_core/src/usecase/edit/character.rs new file mode 100644 index 00000000..4ea5f245 --- /dev/null +++ b/rpg_tools_core/src/usecase/edit/character.rs @@ -0,0 +1,245 @@ +use crate::model::character::gender::Gender; +use crate::model::character::CharacterId; +use crate::model::RpgData; +use anyhow::{bail, Context, Result}; + +/// Tries to update the name of a [`character`](crate::model::character::Character). +pub fn update_character_name(data: &mut RpgData, id: CharacterId, name: &str) -> Result<()> { + let trimmed = name.trim().to_string(); + + if trimmed.is_empty() { + bail!("Name is empty!") + } else if data + .character_manager + .get_all() + .iter() + .filter(|r| r.id().ne(&id)) + .any(|r| r.name().eq(&trimmed)) + { + bail!("Name '{}' already exists!", trimmed) + } + + data.character_manager + .get_mut(id) + .map(|r| r.set_name(trimmed)) + .context("Character doesn't exist!")?; + + Ok(()) +} + +/// Tries to update the [`gender`](Gender) of a [`character`](crate::model::character::Character). +pub fn update_character_gender(data: &mut RpgData, id: CharacterId, gender: Gender) -> Result<()> { + let race_id = data + .character_manager + .get(id) + .map(|c| c.race()) + .context("Character doesn't exist!")?; + let option = data + .race_manager + .get(race_id) + .map(|r| r.gender_option()) + .context("Character's race doesn't exist!")?; + + if !option.is_valid(gender) { + bail!("Gender is not valid for the race's gender option!"); + } + + if let Some(c) = data.character_manager.get_mut(id) { + c.set_gender(gender) + } + + Ok(()) +} + +/// Tries to update the [`race`](crate::model::race::Race) of a [`character`](crate::model::character::Character). +pub fn update_character_race(data: &mut RpgData, id: CharacterId, race_name: &str) -> Result<()> { + let race = data + .race_manager + .get_all() + .iter() + .find(|race| race.name().eq(race_name)) + .context("Race doesn't exist!")?; + let gender = data + .character_manager + .get(id) + .map(|c| c.gender()) + .context("Character doesn't exist!")?; + + if !race.gender_option().is_valid(gender) { + bail!("Race's gender option conflicts with the gender!") + } + + let race_id = *race.id(); + + if let Some(r) = data.character_manager.get_mut(id) { + r.set_race(race_id) + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::character::gender::Gender::Female; + use Gender::{Genderless, Male}; + + // update_character_name() + + #[test] + fn test_empty_name() { + let mut data = RpgData::default(); + + assert!(update_character_name(&mut data, CharacterId::new(0), "").is_err()); + } + + #[test] + fn test_name_contains_only_whitespaces() { + let mut data = RpgData::default(); + + assert!(update_character_name(&mut data, CharacterId::new(0), " ").is_err()); + } + + #[test] + fn test_update_name_of_non_existing_race() { + let mut data = RpgData::default(); + + assert!(update_character_name(&mut data, CharacterId::new(0), "Test").is_err()); + } + + #[test] + fn test_update_valid_name() { + test_update_name("Test", "Test"); + } + + #[test] + fn test_update_trimmed_name() { + test_update_name(" Name ", "Name"); + } + + fn test_update_name(input: &str, result: &str) { + let mut data = RpgData::default(); + let id = data.character_manager.create(); + + assert!(update_character_name(&mut data, id, input).is_ok()); + + assert_eq!( + result, + data.character_manager.get(id).map(|r| r.name()).unwrap() + ); + } + + #[test] + fn test_update_duplicate_name() { + let mut data = RpgData::default(); + let id0 = data.character_manager.create(); + let id1 = data.character_manager.create(); + + assert!(update_character_name(&mut data, id0, "Test").is_ok()); + assert!(update_character_name(&mut data, id1, "Test").is_err()); + } + + // update_character_gender() + + #[test] + fn test_update_gender_of_non_existing_character() { + let mut data = RpgData::default(); + + assert!(update_character_gender(&mut data, CharacterId::new(0), Male).is_err()); + } + + #[test] + fn test_update_gender_with_non_existing_race() { + let mut data = RpgData::default(); + let character_id = data.character_manager.create(); + + assert!(update_character_gender(&mut data, character_id, Male).is_err()); + } + + #[test] + fn test_update_genders() { + test_update_gender(Male); + test_update_gender(Female); + } + + fn test_update_gender(gender: Gender) { + let mut data = RpgData::default(); + data.race_manager.create(); + let character_id = data.character_manager.create(); + + assert!(update_character_gender(&mut data, character_id, gender).is_ok()); + + assert_eq!( + gender, + data.character_manager + .get(character_id) + .map(|r| r.gender()) + .unwrap() + ); + } + + #[test] + fn test_update_invalid_genders() { + let mut data = RpgData::default(); + data.race_manager.create(); + let character_id = data.character_manager.create(); + + assert!(update_character_gender(&mut data, character_id, Genderless).is_err()); + } + + // update_character_gender() + + #[test] + fn test_update_race_with_non_existing_race() { + let mut data = RpgData::default(); + let character_id = data.character_manager.create(); + + assert!(update_character_race(&mut data, character_id, "Test").is_err()); + } + + #[test] + fn test_update_race_of_non_existing_character() { + let mut data = RpgData::default(); + let race_id = data.race_manager.create(); + data.race_manager + .get_mut(race_id) + .map(|r| r.set_name("Test".to_string())); + + assert!(update_character_race(&mut data, CharacterId::new(0), "Test").is_err()); + } + + #[test] + fn test_update_race() { + let mut data = RpgData::default(); + let character_id = data.character_manager.create(); + let race_id = data.race_manager.create(); + data.race_manager + .get_mut(race_id) + .map(|r| r.set_name("Test".to_string())); + + assert!(update_character_race(&mut data, character_id, "Test").is_ok()); + + assert_eq!( + race_id, + data.character_manager + .get(character_id) + .map(|r| r.race()) + .unwrap() + ); + } + + #[test] + fn test_update_race_with_invalid_gender() { + let mut data = RpgData::default(); + let character_id = data.character_manager.create(); + data.character_manager + .get_mut(character_id) + .map(|c| c.set_gender(Genderless)); + let race_id = data.race_manager.create(); + data.race_manager + .get_mut(race_id) + .map(|r| r.set_name("Test".to_string())); + + assert!(update_character_race(&mut data, character_id, "Test").is_err()); + } +} diff --git a/rpg_tools_core/src/usecase/edit/mod.rs b/rpg_tools_core/src/usecase/edit/mod.rs new file mode 100644 index 00000000..00ed6aa1 --- /dev/null +++ b/rpg_tools_core/src/usecase/edit/mod.rs @@ -0,0 +1,2 @@ +pub mod character; +pub mod race; diff --git a/rpg_tools_core/src/usecase/edit/race.rs b/rpg_tools_core/src/usecase/edit/race.rs new file mode 100644 index 00000000..35bd4e3b --- /dev/null +++ b/rpg_tools_core/src/usecase/edit/race.rs @@ -0,0 +1,152 @@ +use crate::model::race::gender::GenderOption; +use crate::model::race::RaceId; +use crate::model::RpgData; +use anyhow::{bail, Context, Result}; + +/// Tries to update the name of a [`race`](crate::model::race::Race). +pub fn update_race_name(data: &mut RpgData, id: RaceId, name: &str) -> Result<()> { + let trimmed = name.trim().to_string(); + + if trimmed.is_empty() { + bail!("Name is empty!") + } else if data + .race_manager + .get_all() + .iter() + .filter(|r| r.id().ne(&id)) + .any(|r| r.name().eq(&trimmed)) + { + bail!("Name '{}' already exists!", trimmed) + } + + data.race_manager + .get_mut(id) + .map(|r| r.set_name(trimmed)) + .context("Race doesn't exist")?; + + Ok(()) +} + +/// Tries to update the gender option of a [`race`](crate::model::race::Race). +pub fn update_gender_option(data: &mut RpgData, id: RaceId, option: GenderOption) -> Result<()> { + if data + .race_manager + .get(id) + .map(|r| r.gender_option() == option) + .context("Race doesn't exist")? + { + return Ok(()); + } + + if !data.character_manager.get_all().is_empty() { + bail!("Cannot change, because the race is used by characters!") + } + + if let Some(r) = data.race_manager.get_mut(id) { + r.set_gender_option(option) + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use GenderOption::{NoGender, TwoGenders}; + + #[test] + fn test_empty_name() { + let mut data = RpgData::default(); + + assert!(update_race_name(&mut data, RaceId::new(0), "").is_err()); + } + + #[test] + fn test_name_contains_only_whitespaces() { + let mut data = RpgData::default(); + + assert!(update_race_name(&mut data, RaceId::new(0), " ").is_err()); + } + + #[test] + fn test_update_name_of_non_existing_race() { + let mut data = RpgData::default(); + + assert!(update_race_name(&mut data, RaceId::new(0), "Test").is_err()); + } + + #[test] + fn test_update_valid_name() { + test_update_name("Test", "Test"); + } + + #[test] + fn test_update_trimmed_name() { + test_update_name(" Name ", "Name"); + } + + fn test_update_name(input: &str, result: &str) { + let mut data = RpgData::default(); + let id = data.race_manager.create(); + + assert!(update_race_name(&mut data, id, input).is_ok()); + + assert_eq!(result, data.race_manager.get(id).map(|r| r.name()).unwrap()); + } + + #[test] + fn test_update_duplicate_name() { + let mut data = RpgData::default(); + let id0 = data.race_manager.create(); + let id1 = data.race_manager.create(); + + assert!(update_race_name(&mut data, id0, "Test").is_ok()); + assert!(update_race_name(&mut data, id1, "Test").is_err()); + } + + #[test] + fn test_update_gender_of_non_existing_race() { + let mut data = RpgData::default(); + + assert!(update_gender_option(&mut data, RaceId::new(0), TwoGenders).is_err()); + } + + #[test] + fn test_update_gender_options() { + test_update_gender_option(NoGender); + test_update_gender_option(TwoGenders); + } + + fn test_update_gender_option(option: GenderOption) { + let mut data = RpgData::default(); + let id = data.race_manager.create(); + + assert!(update_gender_option(&mut data, id, option).is_ok()); + + assert_eq!( + option, + data.race_manager + .get(id) + .map(|r| r.gender_option()) + .unwrap() + ); + } + + #[test] + fn test_update_gender_options_while_used_by_characters() { + let mut data = RpgData::default(); + let id = data.race_manager.create(); + data.character_manager.create(); + + assert!(update_gender_option(&mut data, id, NoGender).is_err()); + } + + #[test] + fn test_update_gender_options_ignore_characters_if_unchanged() { + let mut data = RpgData::default(); + let id = data.race_manager.create(); + data.character_manager.create(); + + assert!(update_gender_option(&mut data, id, TwoGenders).is_ok()); + } +} diff --git a/rpg_tools_core/src/usecase/mod.rs b/rpg_tools_core/src/usecase/mod.rs new file mode 100644 index 00000000..99946a87 --- /dev/null +++ b/rpg_tools_core/src/usecase/mod.rs @@ -0,0 +1 @@ +pub mod edit; diff --git a/rpg_tools_editor/Cargo.toml b/rpg_tools_editor/Cargo.toml index a9daf3ed..6c6ff945 100644 --- a/rpg_tools_editor/Cargo.toml +++ b/rpg_tools_editor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rpg_tools_editor" -version = "0.3.0" +version = "0.4.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rpg_tools_editor/src/appearance.rs b/rpg_tools_editor/src/appearance.rs deleted file mode 100644 index 34446e92..00000000 --- a/rpg_tools_editor/src/appearance.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::parser::UrlParser; -use rpg_tools_core::model::character::appearance::Appearance; -use rpg_tools_rendering::math::aabb2d::AABB; -use rpg_tools_rendering::renderer::svg::SvgBuilder; -use rpg_tools_rendering::renderer::Renderer; -use rpg_tools_rendering::rendering::character::{ - calculate_character_size, render_character_from_back, render_character_from_front, -}; -use rpg_tools_rendering::rendering::config::example::create_border_options; -use rpg_tools_rendering::rendering::config::RenderConfig; -use url_encoded_data::UrlEncodedData; - -pub fn apply_update_to_appearance(update: &str) -> Appearance { - let data = UrlEncodedData::parse_str(update); - let parser = UrlParser::new(data); - - Appearance::parse(&parser, "appearance", "") -} - -#[derive(Responder)] -#[response(status = 200, content_type = "image/svg+xml")] -pub struct RawSvg(String); - -pub fn render_to_svg(config: &RenderConfig, appearance: &Appearance, front: bool) -> RawSvg { - let size = calculate_character_size(config, appearance); - let aabb = AABB::with_size(size); - let options = create_border_options(); - let mut svg_builder = SvgBuilder::new(size); - - svg_builder.render_rectangle(&aabb, &options); - - if front { - render_character_from_front(&mut svg_builder, config, &aabb, appearance); - } else { - render_character_from_back(&mut svg_builder, config, &aabb, appearance); - } - - let svg = svg_builder.finish(); - RawSvg(svg.export()) -} diff --git a/rpg_tools_editor/src/main.rs b/rpg_tools_editor/src/main.rs index 0ff25207..a923b1bf 100644 --- a/rpg_tools_editor/src/main.rs +++ b/rpg_tools_editor/src/main.rs @@ -2,30 +2,40 @@ extern crate macro_core; #[macro_use] extern crate rocket; -use crate::appearance::{apply_update_to_appearance, render_to_svg, RawSvg}; -use crate::io::{read, write}; +use crate::io::read; +use crate::route::appearance::{ + edit_appearance, get_appearance_back, get_appearance_front, get_preview_back, + get_preview_front, update_appearance, update_appearance_preview, +}; +use crate::route::character::{ + add_character, edit_character, get_all_characters, get_character_details, update_character, + CHARACTER_FILE, +}; +use crate::route::race::{ + add_race, edit_race, get_all_races, get_race_details, update_race, RACES_FILE, +}; use anyhow::Result; -use rocket::form::Form; use rocket::fs::FileServer; use rocket::State; use rocket_dyn_templates::{context, Template}; use rpg_tools_core::model::character::appearance::Appearance; use rpg_tools_core::model::character::manager::CharacterMgr; -use rpg_tools_core::model::character::{Character, CharacterId}; +use rpg_tools_core::model::character::Character; +use rpg_tools_core::model::race::manager::RaceMgr; +use rpg_tools_core::model::race::Race; +use rpg_tools_core::model::RpgData; use rpg_tools_rendering::rendering::config::example::create_config; use rpg_tools_rendering::rendering::config::RenderConfig; use std::path::Path; use std::sync::Mutex; -pub mod appearance; pub mod io; pub mod parser; +pub mod route; -const FILE: &str = "resources/characters/characters.yaml"; - -struct EditorData { +pub struct EditorData { config: RenderConfig, - data: Mutex, + data: Mutex, preview: Mutex, } @@ -35,189 +45,8 @@ fn home(data: &State) -> Template { Template::render( "home", context! { - characters: data.get_all().len(), - }, - ) -} - -#[get("/character")] -fn get_characters(data: &State) -> Template { - let data = data.data.lock().expect("lock shared data"); - let characters: Vec<(usize, &str)> = data - .get_all() - .iter() - .map(|c| (c.id().id(), c.name())) - .collect(); - let total = characters.len(); - - Template::render( - "characters", - context! { - characters: characters, - total: total, - }, - ) -} - -#[get("/character/new")] -fn add_character(data: &State) -> Option