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 ) -> Element<'_, Message> {
54 let mut buttons = Vec::new();
55 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(
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 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 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 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}