From 40240a4005d68969623639b90f869803754a302a Mon Sep 17 00:00:00 2001 From: Imbris Date: Fri, 24 Sep 2021 01:48:29 -0400 Subject: [PATCH] Add basic credits screen to the main menu with some example data loaded from a ron file --- assets/common/credits.ron | 23 ++++ assets/voxygen/i18n/en/main.ron | 8 ++ voxygen/src/credits.rs | 43 ++++++ voxygen/src/lib.rs | 1 + voxygen/src/menu/main/ui/credits.rs | 195 ++++++++++++++++++++++++++++ voxygen/src/menu/main/ui/login.rs | 9 ++ voxygen/src/menu/main/ui/mod.rs | 18 +++ 7 files changed, 297 insertions(+) create mode 100644 assets/common/credits.ron create mode 100644 voxygen/src/credits.rs create mode 100644 voxygen/src/menu/main/ui/credits.rs diff --git a/assets/common/credits.ron b/assets/common/credits.ron new file mode 100644 index 0000000000..0541b3a504 --- /dev/null +++ b/assets/common/credits.ron @@ -0,0 +1,23 @@ +( + music: [( + name: "Desert jams", + authors: ["AuthorOne", "author two"], + )], + fonts: [( + name: "Wizard", + license: "cc-by-sa 3", + )], + other_art: [( + name: "Voxel shrooms", + authors: ["AuthorOne", "author two"], + )], + contributors: [ + ( + name: "Example", + contributions: "An example note", + ), + ( + name: "Example Two", + ), + ], +) diff --git a/assets/voxygen/i18n/en/main.ron b/assets/voxygen/i18n/en/main.ron index 3f48895725..650721de8d 100644 --- a/assets/voxygen/i18n/en/main.ron +++ b/assets/voxygen/i18n/en/main.ron @@ -61,6 +61,14 @@ https://veloren.net/account/."#, "main.login.client_version": "Client Version", "main.login.server_version": "Server Version", "main.servers.select_server": "Select a server", + + // Credits screen + "main.credits": "Credits", + "main.credits.music": "Music", + "main.credits.fonts": "Fonts", + "main.credits.other_art": "Other Art", + "main.credits.contributors": "Contributors", + /// End Main screen section }, diff --git a/voxygen/src/credits.rs b/voxygen/src/credits.rs new file mode 100644 index 0000000000..45bcdc215d --- /dev/null +++ b/voxygen/src/credits.rs @@ -0,0 +1,43 @@ +use common::assets; +use serde::Deserialize; + +// NOTE: we are free to split the manifest asset format and the format processed +// for display into separate structs but they happen to be identical for now + +// TODO: add serde attribs to certain fields + +#[derive(Clone, Deserialize)] +pub struct Art { + pub name: String, + // Include asset path as a field? + #[serde(default)] + pub authors: Vec, + #[serde(default)] + pub license: String, + // Include optional license file path and/or web link? +} + +#[derive(Clone, Deserialize)] +pub struct Contributor { + pub name: String, + /// Short note or description of the contributions + /// Optional, can be left empty/ommitted + #[serde(default)] + pub contributions: String, +} + +/// Credits manifest processed into format for display in the UI +#[derive(Clone, Deserialize)] +pub struct Credits { + pub music: Vec, + pub fonts: Vec, + pub other_art: Vec, + pub contributors: Vec, + // TODO: include credits for dependencies where the license requires attribution? +} + +impl assets::Asset for Credits { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index da3bde5232..e7ff518c3c 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -16,6 +16,7 @@ pub mod ui; pub mod audio; pub mod controller; +mod credits; mod ecs; pub mod error; pub mod game_input; diff --git a/voxygen/src/menu/main/ui/credits.rs b/voxygen/src/menu/main/ui/credits.rs new file mode 100644 index 0000000000..b68b0635de --- /dev/null +++ b/voxygen/src/menu/main/ui/credits.rs @@ -0,0 +1,195 @@ +use super::Message; +use crate::{ + credits::Credits, + ui::{ + fonts::IcedFonts as Fonts, + ice::{component::neat_button, style, Element}, + }, +}; +use i18n::Localization; +use iced::{button, scrollable, Column, Container, Length, Scrollable, Space}; + +/// Connecting screen for the main menu +pub struct Screen { + back_button: button::State, + scroll: scrollable::State, +} + +impl Screen { + pub fn new() -> Self { + Self { + back_button: Default::default(), + scroll: Default::default(), + } + } + + pub(super) fn view( + &mut self, + fonts: &Fonts, + i18n: &Localization, + credits: &Credits, + button_style: style::button::Style, + ) -> Element { + use core::fmt::Write; + // TODO: i18n and better formating + let format_art_credit = |credit: &crate::credits::Art| -> Result { + let mut text = String::new(); + text.push_str(&credit.name); + + let mut authors = credit.authors.iter(); + if let Some(author) = authors.next() { + write!(&mut text, " created by {}", author)?; + } + authors.try_for_each(|author| write!(&mut text, ", {}", author))?; + + if !credit.license.is_empty() { + write!(&mut text, " ({})", &credit.license)?; + } + + Ok::<_, core::fmt::Error>(text) + }; + let format_contributor_credit = + |credit: &crate::credits::Contributor| -> Result { + let mut text = String::new(); + text.push_str(&credit.name); + + if !credit.contributions.is_empty() { + write!(&mut text, ": {}", &credit.contributions)?; + } + + Ok(text) + }; + + let music_header_color = iced::Color::from_rgb8(0xfc, 0x71, 0x76); + let fonts_header_color = iced::Color::from_rgb8(0xf7, 0xd1, 0x81); + let other_art_header_color = iced::Color::from_rgb8(0xc5, 0xe9, 0x80); + let contributors_header_color = iced::Color::from_rgb8(0x4a, 0xa6, 0x7b); + + Container::new( + Container::new( + Column::with_children(vec![ + iced::Text::new(i18n.get("main.credits")) + .font(fonts.alkhemi.id) + .size(fonts.alkhemi.scale(35)) + .into(), + Space::new(Length::Fill, Length::Units(25)).into(), + Scrollable::new(&mut self.scroll) + .push(Column::with_children( + core::iter::once( + iced::Text::new(i18n.get("main.credits.music")) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(30)) + .color(music_header_color) + .into(), + ) + .chain(credits.music.iter().map(|credit| { + let text = format_art_credit(credit).expect("Formatting failed!!!"); + iced::Text::new(text) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(23)) + .into() + })) + .chain(core::iter::once( + Space::new(Length::Fill, Length::Units(15)).into(), + )) + .collect(), + )) + .push(Column::with_children( + core::iter::once( + iced::Text::new(i18n.get("main.credits.fonts")) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(30)) + .color(fonts_header_color) + .into(), + ) + .chain(credits.fonts.iter().map(|credit| { + let text = format_art_credit(credit).expect("Formatting failed!!!"); + iced::Text::new(text) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(23)) + .into() + })) + .chain(core::iter::once( + Space::new(Length::Fill, Length::Units(15)).into(), + )) + .collect(), + )) + .push(Column::with_children( + core::iter::once( + iced::Text::new(i18n.get("main.credits.other_art")) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(30)) + .color(other_art_header_color) + .into(), + ) + .chain(credits.other_art.iter().map(|credit| { + let text = format_art_credit(credit).expect("Formatting failed!!!"); + iced::Text::new(text) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(23)) + .into() + })) + .chain(core::iter::once( + Space::new(Length::Fill, Length::Units(15)).into(), + )) + .collect(), + )) + .push(Column::with_children( + core::iter::once( + iced::Text::new(i18n.get("main.credits.contributors")) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(30)) + .color(contributors_header_color) + .into(), + ) + .chain(credits.contributors.iter().map(|credit| { + let text = format_contributor_credit(credit) + .expect("Formatting failed!!!"); + iced::Text::new(text) + .font(fonts.cyri.id) + .size(fonts.cyri.scale(23)) + .into() + })) + .chain(core::iter::once( + Space::new(Length::Fill, Length::Units(15)).into(), + )) + .collect(), + )) + .height(Length::FillPortion(1)) + .into(), + Container::new( + Container::new(neat_button( + &mut self.back_button, + i18n.get("common.back"), + 0.7, + button_style, + Some(Message::Back), + )) + .height(Length::Units(fonts.cyri.scale(50))), + ) + .center_x() + .height(Length::Shrink) + .width(Length::Fill) + .into(), + ]) + .spacing(5) + .padding(20) + .width(Length::Fill) + .height(Length::Fill), + ) + .style( + style::container::Style::color_with_double_cornerless_border( + (22, 19, 17, 255).into(), + (11, 11, 11, 255).into(), + (54, 46, 38, 255).into(), + ), + ), + ) + .center_x() + .center_y() + .padding(70) + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} diff --git a/voxygen/src/menu/main/ui/login.rs b/voxygen/src/menu/main/ui/login.rs index 9234e713d6..0ac08c54c0 100644 --- a/voxygen/src/menu/main/ui/login.rs +++ b/voxygen/src/menu/main/ui/login.rs @@ -26,6 +26,7 @@ pub struct Screen { quit_button: button::State, settings_button: button::State, servers_button: button::State, + credits_button: button::State, language_select_button: button::State, error_okay_button: button::State, @@ -38,6 +39,7 @@ impl Screen { pub fn new() -> Self { Self { servers_button: Default::default(), + credits_button: Default::default(), settings_button: Default::default(), quit_button: Default::default(), language_select_button: Default::default(), @@ -85,6 +87,13 @@ impl Screen { button_style, Some(Message::OpenLanguageMenu), ), + neat_button( + &mut self.credits_button, + i18n.get("main.credits"), + FILL_FRAC_ONE, + button_style, + Some(Message::ShowCredits), + ), neat_button( &mut self.quit_button, i18n.get("common.quit"), diff --git a/voxygen/src/menu/main/ui/mod.rs b/voxygen/src/menu/main/ui/mod.rs index a013750891..ee23c541c1 100644 --- a/voxygen/src/menu/main/ui/mod.rs +++ b/voxygen/src/menu/main/ui/mod.rs @@ -1,10 +1,12 @@ mod connecting; // Note: Keeping in case we re-add the disclaimer //mod disclaimer; +mod credits; mod login; mod servers; use crate::{ + credits::Credits, render::UiDrawer, ui::{ self, @@ -101,6 +103,9 @@ enum Screen { /*Disclaimer { screen: disclaimer::Screen, },*/ + Credits { + screen: credits::Screen, + }, Login { screen: login::Screen, // Error to display in a box @@ -124,6 +129,7 @@ struct Controls { version: String, // Alpha disclaimer alpha: String, + credits: Credits, selected_server_index: Option, login_info: LoginInfo, @@ -141,6 +147,7 @@ enum Message { Quit, Back, ShowServers, + ShowCredits, #[cfg(feature = "singleplayer")] Singleplayer, Multiplayer, @@ -170,6 +177,8 @@ impl Controls { let version = common::util::DISPLAY_VERSION_LONG.clone(); let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str()); + let credits = Credits::load_expect_cloned("common.credits"); + // Note: Keeping in case we re-add the disclaimer let screen = /* if settings.show_disclaimer { Screen::Disclaimer { @@ -205,6 +214,7 @@ impl Controls { i18n, version, alpha, + credits, selected_server_index, login_info, @@ -263,6 +273,9 @@ impl Controls { let content = match &mut self.screen { // Note: Keeping in case we re-add the disclaimer //Screen::Disclaimer { screen } => screen.view(&self.fonts, &self.i18n, button_style), + Screen::Credits { screen } => { + screen.view(&self.fonts, &self.i18n.read(), &self.credits, button_style) + }, Screen::Login { screen, error } => screen.view( &self.fonts, &self.imgs, @@ -334,6 +347,11 @@ impl Controls { }; } }, + Message::ShowCredits => { + self.screen = Screen::Credits { + screen: credits::Screen::new(), + }; + }, #[cfg(feature = "singleplayer")] Message::Singleplayer => { self.screen = Screen::Connecting {