1use super::{FILL_FRAC_ONE, FILL_FRAC_TWO, Imgs, 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 server_address: &str,
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 server_address,
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 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 server_address: &str,
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(server_address)
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 server_address,
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 server_field,
395 )
396 .padding(Padding::new().horizontal(7).vertical(5))
397 .into(),
398 ])
399 .spacing(5)
400 .into(),
401 Space::new(Length::Fill, Length::Units(8)).into(),
402 Column::with_children(vec![
403 neat_button(
404 &mut self.multiplayer_button,
405 i18n.get_msg("common-multiplayer"),
406 FILL_FRAC_TWO,
407 button_style,
408 Some(Message::Multiplayer),
409 ),
410 #[cfg(feature = "singleplayer")]
411 neat_button(
412 &mut self.singleplayer_button,
413 i18n.get_msg("common-singleplayer"),
414 FILL_FRAC_TWO,
415 button_style,
416 Some(Message::Singleplayer),
417 ),
418 ])
419 .max_width(170)
420 .height(Length::Units(200))
421 .spacing(8)
422 .into(),
423 ])
424 .width(Length::Fill)
425 .align_items(Align::Center);
426
427 Container::new(banner_content)
428 .height(Length::Fill)
429 .center_y()
430 .into()
431 }
432}