1use super::Show;
2use crate::{GlobalState, ui::fonts::Fonts};
3use client::{self, Client, UserNotification};
4use conrod_core::{
5 Color, Colorable, Positionable, Widget, WidgetCommon,
6 widget::{self, Text},
7 widget_ids,
8};
9use i18n::Localization;
10use std::{collections::VecDeque, time::Instant};
11
12widget_ids! {
13 struct Ids {
14 error_bg,
15 error_text,
16 info_bg,
17 info_text,
18 message_bg,
19 message_text,
20 }
21}
22
23#[derive(WidgetCommon)]
24pub struct Popup<'a> {
25 i18n: &'a Localization,
26 client: &'a Client,
27 new_notifications: &'a VecDeque<UserNotification>,
28 fonts: &'a Fonts,
29 #[conrod(common_builder)]
30 common: widget::CommonBuilder,
31 show: &'a Show,
32 global_state: &'a GlobalState,
33}
34
35impl<'a> Popup<'a> {
38 pub fn new(
39 i18n: &'a Localization,
40 client: &'a Client,
41 new_notifications: &'a VecDeque<UserNotification>,
42 fonts: &'a Fonts,
43 show: &'a Show,
44 global_state: &'a GlobalState,
45 ) -> Self {
46 Self {
47 i18n,
48 client,
49 new_notifications,
50 fonts,
51 common: widget::CommonBuilder::default(),
52 show,
53 global_state,
54 }
55 }
56}
57
58pub struct State {
59 ids: Ids,
60 errors: VecDeque<String>,
61 infos: VecDeque<String>,
62 messages: VecDeque<String>,
63 last_error_update: Instant,
64 last_info_update: Instant,
65 last_message_update: Instant,
66 last_region_name: Option<String>,
67}
68
69impl Widget for Popup<'_> {
70 type Event = ();
71 type State = State;
72 type Style = ();
73
74 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
75 State {
76 ids: Ids::new(id_gen),
77 errors: VecDeque::new(),
78 infos: VecDeque::new(),
79 messages: VecDeque::new(),
80 last_error_update: Instant::now(),
81 last_info_update: Instant::now(),
82 last_message_update: Instant::now(),
83 last_region_name: None,
84 }
85 }
86
87 fn style(&self) -> Self::Style {}
88
89 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
90 common_base::prof_span!("Popup::update");
91 let widget::UpdateArgs { state, ui, .. } = args;
92
93 const FADE_IN: f32 = 0.5;
94 const FADE_HOLD: f32 = 1.0;
95 const FADE_OUT: f32 = 3.0;
96
97 let bg_color = |fade| Color::Rgba(0.0, 0.0, 0.0, fade);
98 let error_color = |fade| Color::Rgba(1.0, 0.0, 0.0, fade);
99 let info_color = |fade| Color::Rgba(1.0, 1.0, 0.0, fade);
100 let message_color = |fade| Color::Rgba(1.0, 1.0, 1.0, fade);
101
102 if let Some(chunk) = self.client.current_chunk()
104 && let Some(current) = chunk.meta().name()
105 {
106 if state.messages.is_empty()
108 && state
109 .last_region_name
110 .as_ref()
111 .map(|l| l != current)
112 .unwrap_or(true)
113 {
114 state.update(|s| {
116 s.last_region_name = Some(current.to_owned());
117 if self
118 .global_state
119 .settings
120 .interface
121 .toggle_biome_change_popups
122 {
123 if s.messages.is_empty() {
124 s.last_message_update = Instant::now();
125 }
126 s.messages.push_back(current.to_owned());
127 }
128 });
129 }
130 }
131
132 for notification in self.new_notifications {
134 match notification {
135 UserNotification::WaypointUpdated => {
136 state.update(|s| {
137 if s.infos.is_empty() {
138 s.last_info_update = Instant::now();
139 }
140 let text = self.i18n.get_msg("hud-waypoint_saved");
141 s.infos.push_back(text.to_string());
142 });
143 },
144 }
145 }
146
147 if !state.errors.is_empty()
149 && state.last_error_update.elapsed().as_secs_f32() > FADE_IN + FADE_HOLD + FADE_OUT
150 {
151 state.update(|s| {
152 s.errors.pop_front();
153 s.last_error_update = Instant::now();
154 });
155 }
156
157 if let Some(error) = state.errors.front() {
159 let seconds = state.last_error_update.elapsed().as_secs_f32();
160 let fade = if seconds < FADE_IN {
161 seconds / FADE_IN
162 } else if seconds < FADE_IN + FADE_HOLD {
163 1.0
164 } else {
165 (1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
166 };
167 Text::new(error)
168 .mid_top_with_margin_on(ui.window, 50.0)
169 .font_size(self.fonts.cyri.scale(20))
170 .font_id(self.fonts.cyri.conrod_id)
171 .color(bg_color(fade))
172 .set(state.ids.error_bg, ui);
173 Text::new(error)
174 .top_left_with_margins_on(state.ids.error_bg, -1.0, -1.0)
175 .font_size(self.fonts.cyri.scale(20))
176 .font_id(self.fonts.cyri.conrod_id)
177 .color(error_color(fade))
178 .set(state.ids.error_text, ui);
179 }
180
181 if !state.infos.is_empty()
183 && state.last_info_update.elapsed().as_secs_f32() > FADE_IN + FADE_HOLD + FADE_OUT
184 {
185 state.update(|s| {
186 s.infos.pop_front();
187 s.last_info_update = Instant::now();
188 });
189 }
190
191 if !self.show.intro
193 && let Some(info) = state.infos.front()
194 {
195 let seconds = state.last_info_update.elapsed().as_secs_f32();
196 let fade = if seconds < FADE_IN {
197 seconds / FADE_IN
198 } else if seconds < FADE_IN + FADE_HOLD {
199 1.0
200 } else {
201 (1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
202 };
203
204 Text::new(info)
205 .mid_top_with_margin_on(ui.window, 100.0)
206 .font_size(self.fonts.cyri.scale(20))
207 .font_id(self.fonts.cyri.conrod_id)
208 .color(bg_color(fade))
209 .set(state.ids.info_bg, ui);
210 Text::new(info)
211 .top_left_with_margins_on(state.ids.info_bg, -1.0, -1.0)
212 .font_size(self.fonts.cyri.scale(20))
213 .font_id(self.fonts.cyri.conrod_id)
214 .color(info_color(fade))
215 .set(state.ids.info_text, ui);
216 }
217
218 if !state.messages.is_empty()
220 && state.last_message_update.elapsed().as_secs_f32() > FADE_IN + FADE_HOLD + FADE_OUT
221 {
222 state.update(|s| {
223 s.messages.pop_front();
224 s.last_message_update = Instant::now();
225 });
226 }
227
228 if !self.show.intro
230 && let Some(message) = state.messages.front()
231 {
232 let seconds = state.last_message_update.elapsed().as_secs_f32();
233 let fade = if seconds < FADE_IN {
234 seconds / FADE_IN
235 } else if seconds < FADE_IN + FADE_HOLD {
236 1.0
237 } else {
238 (1.0 - (seconds - FADE_IN - FADE_HOLD) / FADE_OUT).max(0.0)
239 };
240 Text::new(message)
241 .mid_top_with_margin_on(ui.window, 200.0)
242 .font_size(self.fonts.alkhemi.scale(70))
243 .font_id(self.fonts.alkhemi.conrod_id)
244 .color(bg_color(fade))
245 .set(state.ids.message_bg, ui);
246 Text::new(message)
247 .top_left_with_margins_on(state.ids.message_bg, -2.5, -2.5)
248 .font_size(self.fonts.alkhemi.scale(70))
249 .font_id(self.fonts.alkhemi.conrod_id)
250 .color(message_color(fade))
251 .set(state.ids.message_text, ui);
252 }
253 }
254}