1use super::{ConnectionState, Imgs, Message};
2
3use crate::{
4 game_input::GameInput,
5 menu::main::DetailedInitializationStage,
6 settings::ControlSettings,
7 ui::{
8 Graphic,
9 fonts::IcedFonts as Fonts,
10 ice::{Element, IcedUi as Ui, Id, component::neat_button, style, widget::Image},
11 },
12};
13use client::ClientInitStage;
14use common::assets::{self, AssetExt};
15use i18n::Localization;
16use iced::{Align, Column, Container, Length, Row, Space, Text, button};
17use keyboard_keynames::key_layout::KeyLayout;
18use serde::{Deserialize, Serialize};
19#[cfg(feature = "singleplayer")]
20use server::{ServerInitStage, WorldCivStage, WorldGenerateStage, WorldSimStage};
21
22struct LoadingAnimation {
23 speed_factor: f32,
24 frames: Vec<Id>,
25}
26impl LoadingAnimation {
27 fn new(raw: &(f32, Vec<String>), ui: &mut Ui) -> Self {
28 let mut frames = vec![];
29 for frame_path in raw.1.iter() {
30 if let Ok(image) = assets::Image::load(frame_path) {
31 frames.push(ui.add_graphic(Graphic::Image(image.read().to_image(), None)));
32 } else {
33 frames.push(
34 ui.add_graphic(Graphic::Image(
35 assets::Image::load("voxygen.element.not_found")
36 .unwrap_or_else(|_| panic!("Missing asset '{}'", frame_path))
37 .read()
38 .to_image(),
39 None,
40 )),
41 )
42 }
43 }
44 Self {
45 speed_factor: raw.0,
46 frames,
47 }
48 }
49}
50
51#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
52struct LoadingAnimationManifest(Vec<(f32, Vec<String>)>);
53impl assets::Asset for LoadingAnimationManifest {
54 type Loader = assets::RonLoader;
55
56 const EXTENSION: &'static str = "ron";
57}
58
59pub struct Screen {
61 cancel_button: button::State,
62 add_button: button::State,
63 tip_number: u16,
64 loading_animation: LoadingAnimation,
65}
66
67impl Screen {
68 pub fn new(ui: &mut Ui) -> Self {
69 let animations =
70 LoadingAnimationManifest::load("voxygen.element.animation.loaders.manifest")
71 .expect(
72 "Missing loader manifest file 'voxygen/element/animation/loaders/manifest.ron'",
73 )
74 .cloned()
75 .0;
76 Self {
77 cancel_button: Default::default(),
78 add_button: Default::default(),
79 tip_number: rand::random(),
80 loading_animation: LoadingAnimation::new(
81 &animations[rand::random::<usize>() % animations.len()],
82 ui,
83 ),
84 }
85 }
86
87 pub(super) fn view(
88 &mut self,
89 fonts: &Fonts,
90 imgs: &Imgs,
91 connection_state: &ConnectionState,
92 init_stage: &DetailedInitializationStage,
93 time: f64,
94 i18n: &Localization,
95 button_style: style::button::Style,
96 show_tip: bool,
97 controls: &ControlSettings,
98 key_layout: &Option<KeyLayout>,
99 ) -> Element<Message> {
100 let frame_index = (time * self.loading_animation.speed_factor as f64)
102 % self.loading_animation.frames.len() as f64;
103 let frame_id = self.loading_animation.frames[frame_index as usize];
104
105 let children = match connection_state {
106 ConnectionState::InProgress => {
107 let tip = if show_tip {
108 let key = |code| match controls.keybindings.get(&code) {
109 Some(Some(key_mouse)) => key_mouse.display_string(key_layout),
110 Some(None) => i18n.get_msg("main-unbound_key_tip").into_owned(),
111 None => match ControlSettings::default_binding(code) {
112 Some(key_mouse) => key_mouse.display_string(key_layout),
113 None => i18n.get_msg("main-unbound_key_tip").into_owned(),
114 },
115 };
116 let keys = i18n::fluent_args! {
117 "gameinput-togglelantern" => key(GameInput::ToggleLantern),
118 "gameinput-controls" => key(GameInput::Controls),
119 "gameinput-settings" => key(GameInput::Settings),
120 "gameinput-social" => key(GameInput::Social),
121 "gameinput-dance" => key(GameInput::Dance),
122 "gameinput-glide" => key(GameInput::Glide),
123 "gameinput-sit" => key(GameInput::Sit),
124 "gameinput-crafting" => key(GameInput::Crafting),
125 "gameinput-roll" => key(GameInput::Roll),
126 "gameinput-screenshot" => key(GameInput::Screenshot),
127 };
128 let tip = &i18n.get_variation_ctx("loading-tips", self.tip_number, &keys);
129 let tip = format!("{} {}", i18n.get_msg("main-tip"), tip);
130
131 Container::new(Text::new(tip).size(fonts.cyri.scale(25)))
132 .width(Length::Fill)
133 .height(Length::Fill)
134 .center_x()
135 .align_y(Align::End)
136 .into()
137 } else {
138 Space::new(Length::Fill, Length::Fill).into()
139 };
140
141 let stage = {
142 let stage_message = match init_stage {
143 #[cfg(feature = "singleplayer")]
144 DetailedInitializationStage::Singleplayer => {
145 i18n.get_msg("hud-init-stage-singleplayer")
146 },
147 #[cfg(feature = "singleplayer")]
148 DetailedInitializationStage::SingleplayerServer(server_stage) => {
149 match server_stage {
150 ServerInitStage::DbMigrations => {
151 i18n.get_msg("hud-init-stage-server-db-migrations")
152 },
153 ServerInitStage::DbVacuum => {
154 i18n.get_msg("hud-init-stage-server-db-vacuum")
155 },
156 ServerInitStage::WorldGen(worldgen_stage) => match worldgen_stage {
157 WorldGenerateStage::WorldSimGenerate(worldsim_stage) => {
158 match worldsim_stage {
159 WorldSimStage::Erosion(done) => i18n
160 .get_msg_ctx(
161 "hud-init-stage-server-worldsim-erosion",
162 &i18n::fluent_args! { "percentage" => format!("{done:.0}") }
163 ),
164 }
165 },
166 WorldGenerateStage::WorldCivGenerate(worldciv_stage) => {
167 match worldciv_stage {
168 WorldCivStage::CivCreation(generated, total) => i18n
169 .get_msg_ctx(
170 "hud-init-stage-server-worldciv-civcreate",
171 &i18n::fluent_args! {
172 "generated" => generated.to_string(),
173 "total" => total.to_string(),
174 }
175 ),
176 WorldCivStage::SiteGeneration => i18n.get_msg("hud-init-stage-server-worldciv-site"),
177 }
178 },
179 WorldGenerateStage::EconomySimulation => i18n.get_msg("hud-init-stage-server-economysim"),
180 WorldGenerateStage::SpotGeneration => i18n.get_msg("hud-init-stage-server-spotgen"),
181 },
182 ServerInitStage::StartingSystems => i18n.get_msg("hud-init-stage-server-starting"),
183 }
184 },
185 DetailedInitializationStage::StartingMultiplayer => {
186 i18n.get_msg("hud-init-stage-multiplayer")
187 },
188 DetailedInitializationStage::Client(client_stage) => match client_stage {
189 ClientInitStage::ConnectionEstablish => {
190 i18n.get_msg("hud-init-stage-client-connection-establish")
191 },
192 ClientInitStage::WatingForServerVersion => {
193 i18n.get_msg("hud-init-stage-client-request-server-version")
194 },
195 ClientInitStage::Authentication => {
196 i18n.get_msg("hud-init-stage-client-authentication")
197 },
198 ClientInitStage::LoadingInitData => {
199 i18n.get_msg("hud-init-stage-client-load-init-data")
200 },
201 ClientInitStage::StartingClient => {
202 i18n.get_msg("hud-init-stage-client-starting-client")
203 },
204 },
205 DetailedInitializationStage::CreatingRenderPipeline(done, total) => i18n
206 .get_msg_ctx(
207 "hud-init-stage-render-pipeline",
208 &i18n::fluent_args! { "done" => done, "total" => total },
209 ),
210 };
211
212 Container::new(Text::new(stage_message).size(fonts.cyri.scale(20)))
213 .width(Length::Fill)
214 .height(Length::Fill)
215 .padding(10)
216 .align_x(Align::Start)
217 .into()
218 };
219
220 let cancel = Container::new(neat_button(
221 &mut self.cancel_button,
222 i18n.get_msg("common-cancel"),
223 0.7,
224 button_style,
225 Some(Message::CancelConnect),
226 ))
227 .width(Length::Fill)
228 .height(Length::Units(fonts.cyri.scale(30)))
229 .center_x()
230 .padding(3);
231
232 let tip_cancel = Column::with_children(vec![tip, cancel.into()])
233 .width(Length::FillPortion(3))
234 .align_items(Align::Center)
235 .spacing(5)
236 .padding(5);
237
238 let gear = Container::new(
239 Image::new(frame_id)
240 .width(Length::Units(64))
241 .height(Length::Units(64)),
242 )
243 .width(Length::Fill)
244 .padding(10)
245 .align_x(Align::End);
246
247 let bottom_content =
248 Row::with_children(vec![stage, tip_cancel.into(), gear.into()])
249 .align_items(Align::Center)
250 .width(Length::Fill);
251
252 let left_art = Image::new(imgs.loading_art_l)
253 .width(Length::Units(12))
254 .height(Length::Units(12));
255 let right_art = Image::new(imgs.loading_art_r)
256 .width(Length::Units(12))
257 .height(Length::Units(12));
258
259 let bottom_bar = Container::new(Row::with_children(vec![
260 left_art.into(),
261 bottom_content.into(),
262 right_art.into(),
263 ]))
264 .height(Length::Units(85))
265 .style(style::container::Style::image(imgs.loading_art));
266
267 vec![
268 Space::new(Length::Fill, Length::Fill).into(),
269 bottom_bar.into(),
270 ]
271 },
272 ConnectionState::AuthTrustPrompt { msg, .. } => {
273 let text = Text::new(msg).size(fonts.cyri.scale(25));
274
275 let cancel = neat_button(
276 &mut self.cancel_button,
277 i18n.get_msg("common-cancel"),
278 0.7,
279 button_style,
280 Some(Message::TrustPromptCancel),
281 );
282 let add = neat_button(
283 &mut self.add_button,
284 i18n.get_msg("common-add"),
285 0.7,
286 button_style,
287 Some(Message::TrustPromptAdd),
288 );
289
290 let content = Column::with_children(vec![
291 text.into(),
292 Container::new(
293 Row::with_children(vec![cancel, add])
294 .spacing(20)
295 .height(Length::Units(25)),
296 )
297 .align_x(Align::End)
298 .width(Length::Fill)
299 .into(),
300 ])
301 .spacing(4)
302 .max_width(520)
303 .width(Length::Fill)
304 .height(Length::Fill);
305
306 let prompt_window = Container::new(content)
307 .style(
308 style::container::Style::color_with_double_cornerless_border(
309 (22, 18, 16, 255).into(),
310 (11, 11, 11, 255).into(),
311 (54, 46, 38, 255).into(),
312 ),
313 )
314 .padding(20);
315
316 let container = Container::new(prompt_window)
317 .width(Length::Fill)
318 .height(Length::Fill)
319 .center_x()
320 .center_y();
321
322 vec![
323 container.into(),
324 Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
325 ]
326 },
327 };
328
329 Column::with_children(children)
330 .width(Length::Fill)
331 .height(Length::Fill)
332 .into()
333 }
334}