veloren/voxygen/src/menu/main/ui/connecting.rs

260 lines
9.3 KiB
Rust

use std::str::FromStr;
use super::{ConnectionState, Imgs, Message};
use crate::{
game_input::GameInput,
settings::ControlSettings,
ui::{
fonts::IcedFonts as Fonts,
ice::{component::neat_button, style, widget::Image, Element, IcedUi as Ui, Id},
Graphic,
},
};
use common::assets::{self, AssetExt};
use i18n::Localization;
use iced::{button, Align, Column, Container, Length, Row, Space, Text};
use serde::{Deserialize, Serialize};
struct LoadingAnimation {
speed_factor: f32,
frames: Vec<Id>,
}
impl LoadingAnimation {
fn new(raw: &(f32, Vec<String>), ui: &mut Ui) -> Self {
let mut frames = vec![];
for frame_path in raw.1.iter() {
if let Ok(image) = assets::Image::load(frame_path) {
frames.push(ui.add_graphic(Graphic::Image(image.read().to_image(), None)));
} else {
frames.push(
ui.add_graphic(Graphic::Image(
assets::Image::load("voxygen.element.not_found")
.unwrap_or_else(|_| panic!("Missing asset '{}'", frame_path))
.read()
.to_image(),
None,
)),
)
}
}
Self {
speed_factor: raw.0,
frames,
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
struct LoadingAnimationManifest(Vec<(f32, Vec<String>)>);
impl assets::Asset for LoadingAnimationManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
/// Connecting screen for the main menu
pub struct Screen {
cancel_button: button::State,
add_button: button::State,
tip_number: u16,
loading_animation: LoadingAnimation,
}
impl Screen {
pub fn new(ui: &mut Ui) -> Self {
let animations =
LoadingAnimationManifest::load("voxygen.element.animation.loaders.manifest")
.expect(
"Missing loader manifest file 'voxygen/element/animation/loaders/manifest.ron'",
)
.cloned()
.0;
Self {
cancel_button: Default::default(),
add_button: Default::default(),
tip_number: rand::random(),
loading_animation: LoadingAnimation::new(
&animations[rand::random::<usize>() % animations.len()],
ui,
),
}
}
pub(super) fn view(
&mut self,
fonts: &Fonts,
imgs: &Imgs,
connection_state: &ConnectionState,
time: f64,
i18n: &Localization,
button_style: style::button::Style,
show_tip: bool,
controls: &ControlSettings,
) -> Element<Message> {
// TODO: add built in support for animated images
let frame_index = (time * self.loading_animation.speed_factor as f64)
% self.loading_animation.frames.len() as f64;
let frame_id = self.loading_animation.frames[frame_index as usize];
let children = match connection_state {
ConnectionState::InProgress => {
let tip = if show_tip {
let tip = &i18n.get_variation("loading.tips", self.tip_number);
let mut new_tip = String::with_capacity(tip.len());
let mut last_index = 0;
// This could be done with regex instead, but adding new dependencies is
// scary...
tip.match_indices("{gameinput.").for_each(|(start, s)| {
println!("start {}", start);
if let Some(end) = tip[start + s.len()..].find('}') {
let end = start + s.len() + end;
println!("end {}", end);
if let Ok(game_input) = GameInput::from_str(&tip[start + 1..end]) {
println!("input {:?}", game_input);
new_tip.push_str(&tip[last_index..start]);
new_tip.push_str(
controls.keybindings[&game_input]
.display_string(&None)
.as_str(),
);
last_index = end + 1;
}
}
});
// If there is any text left over append it
if last_index < tip.len() {
new_tip.push_str(&tip[last_index..]);
}
let tip = format!("{} {}", i18n.get("main.tip"), new_tip.as_str());
Container::new(Text::new(tip).size(fonts.cyri.scale(25)))
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.align_y(Align::End)
.into()
} else {
Space::new(Length::Fill, Length::Fill).into()
};
let cancel = Container::new(neat_button(
&mut self.cancel_button,
i18n.get("common.cancel"),
0.7,
button_style,
Some(Message::CancelConnect),
))
.width(Length::Fill)
.height(Length::Units(fonts.cyri.scale(30)))
.center_x()
.padding(3);
let tip_cancel = Column::with_children(vec![tip, cancel.into()])
.width(Length::FillPortion(3))
.align_items(Align::Center)
.spacing(5)
.padding(5);
let gear = Container::new(
Image::new(frame_id)
.width(Length::Units(64))
.height(Length::Units(64)),
)
.width(Length::Fill)
.padding(10)
.align_x(Align::End);
let bottom_content = Row::with_children(vec![
Space::new(Length::Fill, Length::Shrink).into(),
tip_cancel.into(),
gear.into(),
])
.align_items(Align::Center)
.width(Length::Fill);
let left_art = Image::new(imgs.loading_art_l)
.width(Length::Units(12))
.height(Length::Units(12));
let right_art = Image::new(imgs.loading_art_r)
.width(Length::Units(12))
.height(Length::Units(12));
let bottom_bar = Container::new(Row::with_children(vec![
left_art.into(),
bottom_content.into(),
right_art.into(),
]))
.height(Length::Units(85))
.style(style::container::Style::image(imgs.loading_art));
vec![
Space::new(Length::Fill, Length::Fill).into(),
bottom_bar.into(),
]
},
ConnectionState::AuthTrustPrompt { msg, .. } => {
let text = Text::new(msg).size(fonts.cyri.scale(25));
let cancel = neat_button(
&mut self.cancel_button,
i18n.get("common.cancel"),
0.7,
button_style,
Some(Message::TrustPromptCancel),
);
let add = neat_button(
&mut self.add_button,
i18n.get("common.add"),
0.7,
button_style,
Some(Message::TrustPromptAdd),
);
let content = Column::with_children(vec![
text.into(),
Container::new(
Row::with_children(vec![cancel, add])
.spacing(20)
.height(Length::Units(25)),
)
.align_x(Align::End)
.width(Length::Fill)
.into(),
])
.spacing(4)
.max_width(520)
.width(Length::Fill)
.height(Length::Fill);
let prompt_window = Container::new(content)
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.padding(20);
let container = Container::new(prompt_window)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y();
vec![
container.into(),
Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
]
},
};
Column::with_children(children)
.width(Length::Fill)
.height(Length::Fill)
.into()
}
}