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                            .size(fonts.cyri.scale(25))
263                            .vertical_alignment(iced::VerticalAlignment::Center)
264                            .into(),
265                    ]),
266                )
267                .style(
268                    style::button::Style::new(imgs.selection)
269                        .hover_image(imgs.selection_hover)
270                        .press_image(imgs.selection_press)
271                        .image_color(Rgba::new(color.0, color.1, color.2, 192)),
272                )
273                .min_height(56)
274                .on_press(Message::LanguageChanged(i));
275                Row::with_children(vec![
276                    Space::new(Length::FillPortion(3), Length::Units(0)).into(),
277                    button.width(Length::FillPortion(92)).into(),
278                    Space::new(Length::FillPortion(5), Length::Units(0)).into(),
279                ])
280            });
281
282        for item in list_items {
283            list = list.push(item);
284        }
285
286        let okay_button = Container::new(neat_button(
287            &mut self.okay_button,
288            i18n.get_msg("common-okay"),
289            FILL_FRAC_TWO,
290            button_style,
291            Some(Message::OpenLanguageMenu),
292        ))
293        .center_x()
294        .max_width(200);
295
296        let content = Column::with_children(vec![title.into(), list.into(), okay_button.into()])
297            .spacing(8)
298            .width(Length::Fill)
299            .height(Length::FillPortion(38))
300            .align_items(Align::Center);
301
302        let selection_menu = BackgroundContainer::new(
303            CompoundGraphic::from_graphics(vec![
304                Graphic::image(imgs.banner_top, [138, 17], [0, 0]),
305                Graphic::rect(Rgba::new(0, 0, 0, 230), [130, 165], [4, 17]),
306                // TODO: use non image gradient
307                Graphic::gradient(Rgba::new(0, 0, 0, 230), Rgba::zero(), [130, 50], [4, 182]),
308            ])
309            .fix_aspect_ratio()
310            .height(Length::Fill),
311            content,
312        )
313        .padding(Padding::new().horizontal(5).top(15).bottom(50))
314        .max_width(350);
315
316        selection_menu.into()
317    }
318}
319
320#[derive(Default)]
321pub struct LoginBanner {
322    pub username: text_input::State,
323    pub password: text_input::State,
324    pub server: text_input::State,
325
326    multiplayer_button: button::State,
327    #[cfg(feature = "singleplayer")]
328    singleplayer_button: button::State,
329
330    unlock_server_field_button: button::State,
331}
332
333impl LoginBanner {
334    fn view(
335        &mut self,
336        fonts: &Fonts,
337        imgs: &Imgs,
338        server_field_locked: bool,
339        login_info: &LoginInfo,
340        i18n: &Localization,
341        button_style: style::button::Style,
342    ) -> Element<Message> {
343        let input_text_size = fonts.cyri.scale(INPUT_TEXT_SIZE);
344
345        let server_field: Element<Message> = if server_field_locked {
346            let unlock_style = style::button::Style::new(imgs.unlock)
347                .hover_image(imgs.unlock_hover)
348                .press_image(imgs.unlock_press);
349
350            let unlock_button = Button::new(
351                &mut self.unlock_server_field_button,
352                Space::new(Length::Fill, Length::Fill),
353            )
354            .style(unlock_style)
355            .width(Length::Fill)
356            .height(Length::Fill)
357            .on_press(Message::UnlockServerField);
358
359            let container = AspectRatioContainer::new(unlock_button);
360            let container = match unlock_style.active().0 {
361                Some((img, _)) => container.ratio_of_image(img),
362                None => container,
363            };
364
365            Row::with_children(vec![
366                Text::new(&login_info.server)
367                    .size(input_text_size)
368                    .width(Length::Fill)
369                    .height(Length::Shrink)
370                    .into(),
371                container.into(),
372            ])
373            .align_items(Align::Center)
374            .height(Length::Fill)
375            .into()
376        } else {
377            TextInput::new(
378                &mut self.server,
379                &i18n.get_msg("main-server"),
380                &login_info.server,
381                Message::Server,
382            )
383            .size(input_text_size)
384            .on_submit(Message::Multiplayer)
385            .into()
386        };
387
388        let banner_content = Column::with_children(vec![
389            Column::with_children(vec![
390                BackgroundContainer::new(
391                    Image::new(imgs.input_bg)
392                        .width(Length::Units(INPUT_WIDTH))
393                        .fix_aspect_ratio(),
394                    TextInput::new(
395                        &mut self.username,
396                        &i18n.get_msg("main-username"),
397                        &login_info.username,
398                        Message::Username,
399                    )
400                    .size(input_text_size)
401                    .on_submit(Message::FocusPassword),
402                )
403                .padding(Padding::new().horizontal(7).top(5))
404                .into(),
405                BackgroundContainer::new(
406                    Image::new(imgs.input_bg)
407                        .width(Length::Units(INPUT_WIDTH))
408                        .fix_aspect_ratio(),
409                    TextInput::new(
410                        &mut self.password,
411                        &i18n.get_msg("main-password"),
412                        &login_info.password,
413                        Message::Password,
414                    )
415                    .size(input_text_size)
416                    .password()
417                    .on_submit(Message::Multiplayer),
418                )
419                .padding(Padding::new().horizontal(7).top(5))
420                .into(),
421                BackgroundContainer::new(
422                    Image::new(imgs.input_bg)
423                        .width(Length::Units(INPUT_WIDTH))
424                        .fix_aspect_ratio(),
425                    server_field,
426                )
427                .padding(Padding::new().horizontal(7).vertical(5))
428                .into(),
429            ])
430            .spacing(5)
431            .into(),
432            Space::new(Length::Fill, Length::Units(8)).into(),
433            Column::with_children(vec![
434                neat_button(
435                    &mut self.multiplayer_button,
436                    i18n.get_msg("common-multiplayer"),
437                    FILL_FRAC_TWO,
438                    button_style,
439                    Some(Message::Multiplayer),
440                ),
441                #[cfg(feature = "singleplayer")]
442                neat_button(
443                    &mut self.singleplayer_button,
444                    i18n.get_msg("common-singleplayer"),
445                    FILL_FRAC_TWO,
446                    button_style,
447                    Some(Message::Singleplayer),
448                ),
449            ])
450            .max_width(170)
451            .height(Length::Units(200))
452            .spacing(8)
453            .into(),
454        ])
455        .width(Length::Fill)
456        .align_items(Align::Center);
457
458        Container::new(banner_content)
459            .height(Length::Fill)
460            .center_y()
461            .into()
462    }
463}