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#[derive(Default)]
27pub struct Screen {
28 quit_button: button::State,
29 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 !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(
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 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 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 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}