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(
132 Text::new(tip)
133 .horizontal_alignment(iced::HorizontalAlignment::Center)
134 .size(fonts.cyri.scale(25)),
135 )
136 .width(Length::Fill)
137 .height(Length::Fill)
138 .center_x()
139 .align_y(Align::End)
140 .into()
141 } else {
142 Space::new(Length::Fill, Length::Fill).into()
143 };
144
145 let stage = {
146 let stage_message = match init_stage {
147 #[cfg(feature = "singleplayer")]
148 DetailedInitializationStage::Singleplayer => {
149 i18n.get_msg("hud-init-stage-singleplayer")
150 },
151 #[cfg(feature = "singleplayer")]
152 DetailedInitializationStage::SingleplayerServer(server_stage) => {
153 match server_stage {
154 ServerInitStage::DbMigrations => {
155 i18n.get_msg("hud-init-stage-server-db-migrations")
156 },
157 ServerInitStage::DbVacuum => {
158 i18n.get_msg("hud-init-stage-server-db-vacuum")
159 },
160 ServerInitStage::WorldGen(worldgen_stage) => match worldgen_stage {
161 WorldGenerateStage::WorldSimGenerate(worldsim_stage) => {
162 match worldsim_stage {
163 WorldSimStage::Erosion { progress, estimate } => {
164 let mut msg = i18n
165 .get_msg_ctx(
166 "hud-init-stage-server-worldsim-erosion",
167 &i18n::fluent_args! { "percentage" => format!("{progress:.0}") }
168 ).into_owned();
169 if let Some(estimate) = estimate {
170 let (attr, duration) =
171 chrono::Duration::from_std(*estimate)
172 .map(|dur| {
173 let days = dur.num_days();
174 if days > 0 {
175 return ("days", days);
176 }
177 let hours = dur.num_hours();
178 if hours > 0 {
179 return ("hours", hours);
180 }
181 let minutes = dur.num_minutes();
182 if minutes > 0 {
183 return ("minutes", minutes);
184 }
185
186 ("seconds", dur.num_seconds())
187 })
188 .unwrap_or(("days", i64::MAX));
189 msg.push(' ');
190 msg.push('(');
191 msg.push_str(&i18n.get_attr_ctx(
192 "hud-init-stage-server-worldsim-erosion_time_left",
193 attr,
194 &i18n::fluent_args! { "n" => duration }
195 ));
196 msg.push(')');
197 }
198
199 std::borrow::Cow::Owned(msg)
200 },
201 }
202 },
203 WorldGenerateStage::WorldCivGenerate(worldciv_stage) => {
204 match worldciv_stage {
205 WorldCivStage::CivCreation(generated, total) => i18n
206 .get_msg_ctx(
207 "hud-init-stage-server-worldciv-civcreate",
208 &i18n::fluent_args! {
209 "generated" => generated.to_string(),
210 "total" => total.to_string(),
211 },
212 ),
213 WorldCivStage::SiteGeneration => {
214 i18n.get_msg("hud-init-stage-server-worldciv-site")
215 },
216 }
217 },
218 WorldGenerateStage::EconomySimulation => {
219 i18n.get_msg("hud-init-stage-server-economysim")
220 },
221 WorldGenerateStage::SpotGeneration => {
222 i18n.get_msg("hud-init-stage-server-spotgen")
223 },
224 },
225 ServerInitStage::StartingSystems => {
226 i18n.get_msg("hud-init-stage-server-starting")
227 },
228 }
229 },
230 DetailedInitializationStage::StartingMultiplayer => {
231 i18n.get_msg("hud-init-stage-multiplayer")
232 },
233 DetailedInitializationStage::Client(client_stage) => match client_stage {
234 ClientInitStage::ConnectionEstablish => {
235 i18n.get_msg("hud-init-stage-client-connection-establish")
236 },
237 ClientInitStage::WatingForServerVersion => {
238 i18n.get_msg("hud-init-stage-client-request-server-version")
239 },
240 ClientInitStage::Authentication => {
241 i18n.get_msg("hud-init-stage-client-authentication")
242 },
243 ClientInitStage::LoadingInitData => {
244 i18n.get_msg("hud-init-stage-client-load-init-data")
245 },
246 ClientInitStage::StartingClient => {
247 i18n.get_msg("hud-init-stage-client-starting-client")
248 },
249 },
250 DetailedInitializationStage::CreatingRenderPipeline(done, total) => i18n
251 .get_msg_ctx(
252 "hud-init-stage-render-pipeline",
253 &i18n::fluent_args! { "done" => done, "total" => total },
254 ),
255 };
256
257 Container::new(Text::new(stage_message).size(fonts.cyri.scale(20)))
258 .width(Length::Fill)
259 .height(Length::Fill)
260 .padding(10)
261 .align_x(Align::Start)
262 .into()
263 };
264
265 let cancel = Container::new(neat_button(
266 &mut self.cancel_button,
267 i18n.get_msg("common-cancel"),
268 0.7,
269 button_style,
270 Some(Message::CancelConnect),
271 ))
272 .width(Length::Fill)
273 .height(Length::Units(fonts.cyri.scale(30)))
274 .center_x()
275 .padding(3);
276
277 let tip_cancel = Column::with_children(vec![tip, cancel.into()])
278 .width(Length::FillPortion(2))
279 .align_items(Align::Center)
280 .spacing(5)
281 .padding(5);
282
283 let gear = Container::new(
284 Image::new(frame_id)
285 .width(Length::Units(64))
286 .height(Length::Units(64)),
287 )
288 .width(Length::Fill)
289 .padding(10)
290 .align_x(Align::End);
291
292 let bottom_content =
293 Row::with_children(vec![stage, tip_cancel.into(), gear.into()])
294 .align_items(Align::Center)
295 .width(Length::Fill);
296
297 let left_art = Image::new(imgs.loading_art_l)
298 .width(Length::Units(12))
299 .height(Length::Units(12));
300 let right_art = Image::new(imgs.loading_art_r)
301 .width(Length::Units(12))
302 .height(Length::Units(12));
303
304 let bottom_bar = Container::new(Row::with_children(vec![
305 left_art.into(),
306 bottom_content.into(),
307 right_art.into(),
308 ]))
309 .height(Length::Units(85))
310 .style(style::container::Style::image(imgs.loading_art));
311
312 vec![
313 Space::new(Length::Fill, Length::Fill).into(),
314 bottom_bar.into(),
315 ]
316 },
317 ConnectionState::AuthTrustPrompt { msg, .. } => {
318 let text = Text::new(msg).size(fonts.cyri.scale(25));
319
320 let cancel = neat_button(
321 &mut self.cancel_button,
322 i18n.get_msg("common-cancel"),
323 0.7,
324 button_style,
325 Some(Message::TrustPromptCancel),
326 );
327 let add = neat_button(
328 &mut self.add_button,
329 i18n.get_msg("common-add"),
330 0.7,
331 button_style,
332 Some(Message::TrustPromptAdd),
333 );
334
335 let content = Column::with_children(vec![
336 text.into(),
337 Container::new(
338 Row::with_children(vec![cancel, add])
339 .spacing(20)
340 .height(Length::Units(25)),
341 )
342 .align_x(Align::End)
343 .width(Length::Fill)
344 .into(),
345 ])
346 .spacing(4)
347 .max_width(520)
348 .width(Length::Fill)
349 .height(Length::Fill);
350
351 let prompt_window = Container::new(content)
352 .style(
353 style::container::Style::color_with_double_cornerless_border(
354 (22, 18, 16, 255).into(),
355 (11, 11, 11, 255).into(),
356 (54, 46, 38, 255).into(),
357 ),
358 )
359 .padding(20);
360
361 let container = Container::new(prompt_window)
362 .width(Length::Fill)
363 .height(Length::Fill)
364 .center_x()
365 .center_y();
366
367 vec![
368 container.into(),
369 Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
370 ]
371 },
372 };
373
374 Column::with_children(children)
375 .width(Length::Fill)
376 .height(Length::Fill)
377 .into()
378 }
379}