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, 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    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        // TODO: add built in support for animated images
88        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}