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