veloren_voxygen/menu/main/ui/
connecting.rs

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
58/// Connecting screen for the main menu
59pub 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        // TODO: add built in support for animated images
99        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}