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(Text::new(tip).size(fonts.cyri.scale(25)))
132                        .width(Length::Fill)
133                        .height(Length::Fill)
134                        .center_x()
135                        .align_y(Align::End)
136                        .into()
137                } else {
138                    Space::new(Length::Fill, Length::Fill).into()
139                };
140
141                let stage = {
142                    let stage_message = match init_stage {
143                        #[cfg(feature = "singleplayer")]
144                        DetailedInitializationStage::Singleplayer => {
145                            i18n.get_msg("hud-init-stage-singleplayer")
146                        },
147                        #[cfg(feature = "singleplayer")]
148                        DetailedInitializationStage::SingleplayerServer(server_stage) => {
149                            match server_stage {
150                                ServerInitStage::DbMigrations => {
151                                    i18n.get_msg("hud-init-stage-server-db-migrations")
152                                },
153                                ServerInitStage::DbVacuum => {
154                                    i18n.get_msg("hud-init-stage-server-db-vacuum")
155                                },
156                                ServerInitStage::WorldGen(worldgen_stage) => match worldgen_stage {
157                                    WorldGenerateStage::WorldSimGenerate(worldsim_stage) => {
158                                        match worldsim_stage {
159                                            WorldSimStage::Erosion(done) => i18n
160                                                .get_msg_ctx(
161                                                    "hud-init-stage-server-worldsim-erosion",
162                                                    &i18n::fluent_args! { "percentage" => format!("{done:.0}") }
163                                                ),
164                                        }
165                                    },
166                                    WorldGenerateStage::WorldCivGenerate(worldciv_stage) => {
167                                        match worldciv_stage {
168                                            WorldCivStage::CivCreation(generated, total) => i18n
169                                                .get_msg_ctx(
170                                                    "hud-init-stage-server-worldciv-civcreate",
171                                                    &i18n::fluent_args! {
172                                                        "generated" => generated.to_string(),
173                                                        "total" => total.to_string(),
174                                                    }
175                                                ),
176                                            WorldCivStage::SiteGeneration => i18n.get_msg("hud-init-stage-server-worldciv-site"),
177                                        }
178                                    },
179                                    WorldGenerateStage::EconomySimulation => i18n.get_msg("hud-init-stage-server-economysim"),
180                                    WorldGenerateStage::SpotGeneration => i18n.get_msg("hud-init-stage-server-spotgen"),
181                                },
182                                ServerInitStage::StartingSystems => i18n.get_msg("hud-init-stage-server-starting"),
183                            }
184                        },
185                        DetailedInitializationStage::StartingMultiplayer => {
186                            i18n.get_msg("hud-init-stage-multiplayer")
187                        },
188                        DetailedInitializationStage::Client(client_stage) => match client_stage {
189                            ClientInitStage::ConnectionEstablish => {
190                                i18n.get_msg("hud-init-stage-client-connection-establish")
191                            },
192                            ClientInitStage::WatingForServerVersion => {
193                                i18n.get_msg("hud-init-stage-client-request-server-version")
194                            },
195                            ClientInitStage::Authentication => {
196                                i18n.get_msg("hud-init-stage-client-authentication")
197                            },
198                            ClientInitStage::LoadingInitData => {
199                                i18n.get_msg("hud-init-stage-client-load-init-data")
200                            },
201                            ClientInitStage::StartingClient => {
202                                i18n.get_msg("hud-init-stage-client-starting-client")
203                            },
204                        },
205                        DetailedInitializationStage::CreatingRenderPipeline(done, total) => i18n
206                            .get_msg_ctx(
207                                "hud-init-stage-render-pipeline",
208                                &i18n::fluent_args! { "done" => done, "total" => total },
209                            ),
210                    };
211
212                    Container::new(Text::new(stage_message).size(fonts.cyri.scale(20)))
213                        .width(Length::Fill)
214                        .height(Length::Fill)
215                        .padding(10)
216                        .align_x(Align::Start)
217                        .into()
218                };
219
220                let cancel = Container::new(neat_button(
221                    &mut self.cancel_button,
222                    i18n.get_msg("common-cancel"),
223                    0.7,
224                    button_style,
225                    Some(Message::CancelConnect),
226                ))
227                .width(Length::Fill)
228                .height(Length::Units(fonts.cyri.scale(30)))
229                .center_x()
230                .padding(3);
231
232                let tip_cancel = Column::with_children(vec![tip, cancel.into()])
233                    .width(Length::FillPortion(3))
234                    .align_items(Align::Center)
235                    .spacing(5)
236                    .padding(5);
237
238                let gear = Container::new(
239                    Image::new(frame_id)
240                        .width(Length::Units(64))
241                        .height(Length::Units(64)),
242                )
243                .width(Length::Fill)
244                .padding(10)
245                .align_x(Align::End);
246
247                let bottom_content =
248                    Row::with_children(vec![stage, tip_cancel.into(), gear.into()])
249                        .align_items(Align::Center)
250                        .width(Length::Fill);
251
252                let left_art = Image::new(imgs.loading_art_l)
253                    .width(Length::Units(12))
254                    .height(Length::Units(12));
255                let right_art = Image::new(imgs.loading_art_r)
256                    .width(Length::Units(12))
257                    .height(Length::Units(12));
258
259                let bottom_bar = Container::new(Row::with_children(vec![
260                    left_art.into(),
261                    bottom_content.into(),
262                    right_art.into(),
263                ]))
264                .height(Length::Units(85))
265                .style(style::container::Style::image(imgs.loading_art));
266
267                vec![
268                    Space::new(Length::Fill, Length::Fill).into(),
269                    bottom_bar.into(),
270                ]
271            },
272            ConnectionState::AuthTrustPrompt { msg, .. } => {
273                let text = Text::new(msg).size(fonts.cyri.scale(25));
274
275                let cancel = neat_button(
276                    &mut self.cancel_button,
277                    i18n.get_msg("common-cancel"),
278                    0.7,
279                    button_style,
280                    Some(Message::TrustPromptCancel),
281                );
282                let add = neat_button(
283                    &mut self.add_button,
284                    i18n.get_msg("common-add"),
285                    0.7,
286                    button_style,
287                    Some(Message::TrustPromptAdd),
288                );
289
290                let content = Column::with_children(vec![
291                    text.into(),
292                    Container::new(
293                        Row::with_children(vec![cancel, add])
294                            .spacing(20)
295                            .height(Length::Units(25)),
296                    )
297                    .align_x(Align::End)
298                    .width(Length::Fill)
299                    .into(),
300                ])
301                .spacing(4)
302                .max_width(520)
303                .width(Length::Fill)
304                .height(Length::Fill);
305
306                let prompt_window = Container::new(content)
307                    .style(
308                        style::container::Style::color_with_double_cornerless_border(
309                            (22, 18, 16, 255).into(),
310                            (11, 11, 11, 255).into(),
311                            (54, 46, 38, 255).into(),
312                        ),
313                    )
314                    .padding(20);
315
316                let container = Container::new(prompt_window)
317                    .width(Length::Fill)
318                    .height(Length::Fill)
319                    .center_x()
320                    .center_y();
321
322                vec![
323                    container.into(),
324                    Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
325                ]
326            },
327        };
328
329        Column::with_children(children)
330            .width(Length::Fill)
331            .height(Length::Fill)
332            .into()
333    }
334}