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 .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 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}