Skip to main content

veloren_voxygen/menu/main/ui/
connecting.rs

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