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