veloren_voxygen/menu/main/ui/
login.rs

1use super::{FILL_FRAC_ONE, FILL_FRAC_TWO, Imgs, LoginInfo, Message, Showing};
2use crate::ui::{
3    fonts::IcedFonts as Fonts,
4    ice::{
5        Element,
6        component::neat_button,
7        style,
8        widget::{
9            AspectRatioContainer, BackgroundContainer, Image, Padding,
10            compound_graphic::{CompoundGraphic, Graphic},
11        },
12    },
13};
14
15use i18n::{LanguageMetadata, Localization};
16use iced::{
17    Align, Button, Column, Container, Length, Row, Scrollable, Space, Text, TextInput, button,
18    scrollable, text_input,
19};
20use vek::*;
21
22const INPUT_WIDTH: u16 = 230;
23const INPUT_TEXT_SIZE: u16 = 20;
24
25/// Login screen for the main menu
26#[derive(Default)]
27pub struct Screen {
28    quit_button: button::State,
29    // settings_button: button::State,
30    servers_button: button::State,
31    credits_button: button::State,
32    language_select_button: button::State,
33
34    error_okay_button: button::State,
35
36    pub banner: LoginBanner,
37    language_selection: LanguageSelectBanner,
38}
39
40impl Screen {
41    pub(super) fn view(
42        &mut self,
43        fonts: &Fonts,
44        imgs: &Imgs,
45        server_field_locked: bool,
46        login_info: &LoginInfo,
47        error: Option<&str>,
48        i18n: &Localization,
49        show: &Showing,
50        selected_language_index: Option<usize>,
51        language_metadatas: &[LanguageMetadata],
52        button_style: style::button::Style,
53    ) -> Element<'_, Message> {
54        let mut buttons = Vec::new();
55        // If the server field is locked, we don't want to show the server selection
56        // list!
57        if !server_field_locked {
58            buttons.push(neat_button(
59                &mut self.servers_button,
60                i18n.get_msg("common-servers"),
61                FILL_FRAC_ONE,
62                button_style,
63                Some(Message::ShowServers),
64            ))
65        }
66        buttons.extend([
67            // neat_button(
68            //     &mut self.settings_button,
69            //     i18n.get_msg("common-settings"),
70            //     FILL_FRAC_ONE,
71            //     button_style,
72            //     None,
73            // ),
74            neat_button(
75                &mut self.language_select_button,
76                i18n.get_msg("common-languages"),
77                FILL_FRAC_ONE,
78                button_style,
79                Some(Message::OpenLanguageMenu),
80            ),
81            neat_button(
82                &mut self.credits_button,
83                i18n.get_msg("main-credits"),
84                FILL_FRAC_ONE,
85                button_style,
86                Some(Message::ShowCredits),
87            ),
88            neat_button(
89                &mut self.quit_button,
90                i18n.get_msg("common-quit"),
91                FILL_FRAC_ONE,
92                button_style,
93                Some(Message::Quit),
94            ),
95        ]);
96
97        let buttons = Container::new(
98            Column::with_children(buttons)
99                .width(Length::Fill)
100                .max_width(100)
101                .spacing(5),
102        )
103        .width(Length::Fill)
104        .height(Length::Fill)
105        .align_y(Align::End);
106
107        let intro_text = i18n.get_msg("main-login_process");
108
109        let info_window = BackgroundContainer::new(
110            CompoundGraphic::from_graphics(vec![
111                Graphic::rect(Rgba::new(0, 0, 0, 240), [500, 300], [0, 0]),
112                // Note: a way to tell it to keep the height of this one piece constant and
113                // unstreched would be nice, I suppose we could just break this out into a
114                // column and use Length::Units
115                Graphic::image(imgs.banner_gradient_bottom, [500, 50], [0, 300])
116                    .color(Rgba::new(0, 0, 0, 240)),
117            ])
118            .height(Length::Shrink),
119            Text::new(intro_text).size(fonts.cyri.scale(18)),
120        )
121        .max_width(360)
122        .padding(Padding::new().horizontal(20).top(10).bottom(60));
123
124        let left_column = Column::with_children(vec![info_window.into(), buttons.into()])
125            .width(Length::Fill)
126            .height(Length::Fill)
127            .padding(27)
128            .into();
129
130        let central_content = if let Some(error) = error {
131            Container::new(
132                Column::with_children(vec![
133                    Container::new(Text::new(error)).height(Length::Fill).into(),
134                    Container::new(neat_button(
135                        &mut self.error_okay_button,
136                        i18n.get_msg("common-okay"),
137                        FILL_FRAC_ONE,
138                        button_style,
139                        Some(Message::CloseError),
140                    ))
141                    .width(Length::Fill)
142                    .height(Length::Units(30))
143                    .center_x()
144                    .into(),
145                ])
146                .height(Length::Fill)
147                .width(Length::Fill),
148            )
149            .style(
150                style::container::Style::color_with_double_cornerless_border(
151                    (22, 18, 16, 255).into(),
152                    (11, 11, 11, 255).into(),
153                    (54, 46, 38, 255).into(),
154                ),
155            )
156            .width(Length::Units(400))
157            .height(Length::Units(180))
158            .padding(20)
159            .into()
160        } else {
161            match show {
162                Showing::Login => self.banner.view(
163                    fonts,
164                    imgs,
165                    server_field_locked,
166                    login_info,
167                    i18n,
168                    button_style,
169                ),
170                Showing::Languages => self.language_selection.view(
171                    fonts,
172                    imgs,
173                    i18n,
174                    language_metadatas,
175                    selected_language_index,
176                    button_style,
177                ),
178            }
179        };
180
181        let central_column = Container::new(central_content)
182            .width(Length::Fill)
183            .height(Length::Fill)
184            .center_x()
185            .center_y();
186
187        let v_logo = Container::new(Image::new(imgs.v_logo).fix_aspect_ratio())
188            .padding(3)
189            .width(Length::Units(230));
190
191        let version_stage =
192            Text::new(common::util::VELOREN_VERSION_STAGE).size(fonts.cyri.scale(22));
193
194        let right_column = Container::new(
195            Column::with_children(vec![v_logo.into(), version_stage.into()])
196                .align_items(Align::Center),
197        )
198        .width(Length::Fill)
199        .height(Length::Fill)
200        .align_x(Align::End);
201
202        Row::with_children(vec![
203            left_column,
204            central_column.into(),
205            right_column.into(),
206        ])
207        .width(Length::Fill)
208        .height(Length::Fill)
209        .spacing(10)
210        .into()
211    }
212}
213
214#[derive(Default)]
215pub struct LanguageSelectBanner {
216    okay_button: button::State,
217    language_buttons: Vec<button::State>,
218
219    selection_list: scrollable::State,
220}
221
222impl LanguageSelectBanner {
223    fn view(
224        &mut self,
225        fonts: &Fonts,
226        imgs: &Imgs,
227        i18n: &Localization,
228        language_metadatas: &[LanguageMetadata],
229        selected_language_index: Option<usize>,
230        button_style: style::button::Style,
231    ) -> Element<'_, Message> {
232        // Reset button states if languages were added / removed
233        if self.language_buttons.len() != language_metadatas.len() {
234            self.language_buttons = vec![Default::default(); language_metadatas.len()];
235        }
236
237        let title = Text::new(i18n.get_msg("main-login-select_language"))
238            .size(fonts.cyri.scale(35))
239            .horizontal_alignment(iced::HorizontalAlignment::Center);
240
241        let mut list = Scrollable::new(&mut self.selection_list)
242            .spacing(8)
243            .height(Length::Fill)
244            .align_items(Align::Start);
245
246        let list_items = self
247            .language_buttons
248            .iter_mut()
249            .zip(language_metadatas)
250            .enumerate()
251            .map(|(i, (state, lang))| {
252                let color = if Some(i) == selected_language_index {
253                    (97, 255, 18)
254                } else {
255                    (97, 97, 25)
256                };
257                let button = Button::new(
258                    state,
259                    Row::with_children(vec![
260                        Space::new(Length::FillPortion(5), Length::Units(0)).into(),
261                        Text::new(lang.language_name.clone())
262                            .width(Length::FillPortion(95))
263                            .font(fonts.universal.id)
264                            .size(fonts.universal.scale(25))
265                            .vertical_alignment(iced::VerticalAlignment::Center)
266                            .into(),
267                    ]),
268                )
269                .style(
270                    style::button::Style::new(imgs.selection)
271                        .hover_image(imgs.selection_hover)
272                        .press_image(imgs.selection_press)
273                        .image_color(Rgba::new(color.0, color.1, color.2, 192)),
274                )
275                .min_height(56)
276                .on_press(Message::LanguageChanged(i));
277                Row::with_children(vec![
278                    Space::new(Length::FillPortion(3), Length::Units(0)).into(),
279                    button.width(Length::FillPortion(92)).into(),
280                    Space::new(Length::FillPortion(5), Length::Units(0)).into(),
281                ])
282            });
283
284        for item in list_items {
285            list = list.push(item);
286        }
287
288        let okay_button = Container::new(neat_button(
289            &mut self.okay_button,
290            i18n.get_msg("common-okay"),
291            FILL_FRAC_TWO,
292            button_style,
293            Some(Message::OpenLanguageMenu),
294        ))
295        .center_x()
296        .max_width(200);
297
298        let content = Column::with_children(vec![title.into(), list.into(), okay_button.into()])
299            .spacing(8)
300            .width(Length::Fill)
301            .height(Length::FillPortion(38))
302            .align_items(Align::Center);
303
304        let selection_menu = BackgroundContainer::new(
305            CompoundGraphic::from_graphics(vec![
306                Graphic::image(imgs.banner_top, [138, 17], [0, 0]),
307                Graphic::rect(Rgba::new(0, 0, 0, 230), [130, 165], [4, 17]),
308                // TODO: use non image gradient
309                Graphic::gradient(Rgba::new(0, 0, 0, 230), Rgba::zero(), [130, 50], [4, 182]),
310            ])
311            .fix_aspect_ratio()
312            .height(Length::Fill),
313            content,
314        )
315        .padding(Padding::new().horizontal(5).top(15).bottom(50))
316        .max_width(350);
317
318        selection_menu.into()
319    }
320}
321
322#[derive(Default)]
323pub struct LoginBanner {
324    pub username: text_input::State,
325    pub password: text_input::State,
326    pub server: text_input::State,
327
328    multiplayer_button: button::State,
329    #[cfg(feature = "singleplayer")]
330    singleplayer_button: button::State,
331
332    unlock_server_field_button: button::State,
333}
334
335impl LoginBanner {
336    fn view(
337        &mut self,
338        fonts: &Fonts,
339        imgs: &Imgs,
340        server_field_locked: bool,
341        login_info: &LoginInfo,
342        i18n: &Localization,
343        button_style: style::button::Style,
344    ) -> Element<'_, Message> {
345        let input_text_size = fonts.cyri.scale(INPUT_TEXT_SIZE);
346
347        let server_field: Element<Message> = if server_field_locked {
348            let unlock_style = style::button::Style::new(imgs.unlock)
349                .hover_image(imgs.unlock_hover)
350                .press_image(imgs.unlock_press);
351
352            let unlock_button = Button::new(
353                &mut self.unlock_server_field_button,
354                Space::new(Length::Fill, Length::Fill),
355            )
356            .style(unlock_style)
357            .width(Length::Fill)
358            .height(Length::Fill)
359            .on_press(Message::UnlockServerField);
360
361            let container = AspectRatioContainer::new(unlock_button);
362            let container = match unlock_style.active().0 {
363                Some((img, _)) => container.ratio_of_image(img),
364                None => container,
365            };
366
367            Row::with_children(vec![
368                Text::new(&login_info.server)
369                    .size(input_text_size)
370                    .width(Length::Fill)
371                    .height(Length::Shrink)
372                    .into(),
373                container.into(),
374            ])
375            .align_items(Align::Center)
376            .height(Length::Fill)
377            .into()
378        } else {
379            TextInput::new(
380                &mut self.server,
381                &i18n.get_msg("main-server"),
382                &login_info.server,
383                Message::Server,
384            )
385            .size(input_text_size)
386            .on_submit(Message::Multiplayer)
387            .into()
388        };
389
390        let banner_content = Column::with_children(vec![
391            Column::with_children(vec![
392                BackgroundContainer::new(
393                    Image::new(imgs.input_bg)
394                        .width(Length::Units(INPUT_WIDTH))
395                        .fix_aspect_ratio(),
396                    TextInput::new(
397                        &mut self.username,
398                        &i18n.get_msg("main-username"),
399                        &login_info.username,
400                        Message::Username,
401                    )
402                    .size(input_text_size)
403                    .on_submit(Message::FocusPassword),
404                )
405                .padding(Padding::new().horizontal(7).top(5))
406                .into(),
407                BackgroundContainer::new(
408                    Image::new(imgs.input_bg)
409                        .width(Length::Units(INPUT_WIDTH))
410                        .fix_aspect_ratio(),
411                    TextInput::new(
412                        &mut self.password,
413                        &i18n.get_msg("main-password"),
414                        &login_info.password,
415                        Message::Password,
416                    )
417                    .size(input_text_size)
418                    .password()
419                    .on_submit(Message::Multiplayer),
420                )
421                .padding(Padding::new().horizontal(7).top(5))
422                .into(),
423                BackgroundContainer::new(
424                    Image::new(imgs.input_bg)
425                        .width(Length::Units(INPUT_WIDTH))
426                        .fix_aspect_ratio(),
427                    server_field,
428                )
429                .padding(Padding::new().horizontal(7).vertical(5))
430                .into(),
431            ])
432            .spacing(5)
433            .into(),
434            Space::new(Length::Fill, Length::Units(8)).into(),
435            Column::with_children(vec![
436                neat_button(
437                    &mut self.multiplayer_button,
438                    i18n.get_msg("common-multiplayer"),
439                    FILL_FRAC_TWO,
440                    button_style,
441                    Some(Message::Multiplayer),
442                ),
443                #[cfg(feature = "singleplayer")]
444                neat_button(
445                    &mut self.singleplayer_button,
446                    i18n.get_msg("common-singleplayer"),
447                    FILL_FRAC_TWO,
448                    button_style,
449                    Some(Message::Singleplayer),
450                ),
451            ])
452            .max_width(170)
453            .height(Length::Units(200))
454            .spacing(8)
455            .into(),
456        ])
457        .width(Length::Fill)
458        .align_items(Align::Center);
459
460        Container::new(banner_content)
461            .height(Length::Fill)
462            .center_y()
463            .into()
464    }
465}