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