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