1use crate::{
2 game_input::GameInput,
3 settings::ControlSettings,
4 ui::{Ingameable, fonts::Fonts},
5};
6use conrod_core::{
7 Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, color,
8 widget::{self, RoundedRectangle, Text},
9 widget_ids,
10};
11use i18n::Localization;
12use std::borrow::Cow;
13
14use crate::hud::{CollectFailedData, HudCollectFailedReason, HudLootOwner};
15
16pub const TEXT_COLOR: Color = Color::Rgba(0.61, 0.61, 0.89, 1.0);
17pub const NEGATIVE_TEXT_COLOR: Color = Color::Rgba(0.91, 0.15, 0.17, 1.0);
18pub const PICKUP_FAILED_FADE_OUT_TIME: f32 = 1.5;
19
20widget_ids! {
21 struct Ids {
22 name_bg,
24 name,
25 btn_bg,
27 btns[],
28 inv_full_bg,
30 inv_full,
31 }
32}
33
34#[derive(WidgetCommon)]
37pub struct Overitem<'a> {
38 name: Cow<'a, str>,
39 quality: Color,
40 distance_from_player_sqr: f32,
41 fonts: &'a Fonts,
42 localized_strings: &'a Localization,
43 controls: &'a ControlSettings,
44 #[conrod(common_builder)]
45 common: widget::CommonBuilder,
46 properties: OveritemProperties,
47 pulse: f32,
48 interaction_options: Vec<(Option<GameInput>, String, Color)>,
50}
51
52impl<'a> Overitem<'a> {
53 pub fn new(
54 name: Cow<'a, str>,
55 quality: Color,
56 distance_from_player_sqr: f32,
57 fonts: &'a Fonts,
58 localized_strings: &'a Localization,
59 controls: &'a ControlSettings,
60 properties: OveritemProperties,
61 pulse: f32,
62 interaction_options: Vec<(Option<GameInput>, String, Color)>,
63 ) -> Self {
64 Self {
65 name,
66 quality,
67 distance_from_player_sqr,
68 fonts,
69 localized_strings,
70 controls,
71 common: widget::CommonBuilder::default(),
72 properties,
73 pulse,
74 interaction_options,
75 }
76 }
77}
78
79pub struct OveritemProperties {
80 pub active: bool,
81 pub pickup_failed_pulse: Option<CollectFailedData>,
82}
83
84pub struct State {
85 ids: Ids,
86}
87
88impl Ingameable for Overitem<'_> {
89 fn prim_count(&self) -> usize {
90 2 + match self
95 .controls
96 .get_binding(GameInput::Interact)
97 .filter(|_| self.properties.active)
98 {
99 Some(_) => 2,
100 None => 0,
101 } + if self.properties.pickup_failed_pulse.is_some() {
102 2
103 } else {
104 0
105 }
106 }
107}
108
109impl Widget for Overitem<'_> {
110 type Event = ();
111 type State = State;
112 type Style = ();
113
114 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
115 State {
116 ids: Ids::new(id_gen),
117 }
118 }
119
120 fn style(&self) -> Self::Style {}
121
122 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
123 let widget::UpdateArgs { id, state, ui, .. } = args;
124
125 let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
126
127 let scale = 30.0;
141
142 let text_font_size = scale * 1.0;
143 let text_pos_y = scale * 1.2;
144
145 let btn_rect_size = scale * 0.8;
146 let btn_font_size = scale * 0.6;
147 let btn_rect_pos_y = 0.0;
148 let btn_text_pos_y = btn_rect_pos_y + ((btn_rect_size - btn_font_size) * 0.5);
149 let btn_radius = btn_rect_size / 5.0;
150
151 let inv_full_font_size = scale * 1.0;
152 let inv_full_pos_y = scale * 2.4;
153
154 Text::new(&self.name)
156 .font_id(self.fonts.cyri.conrod_id)
157 .font_size(text_font_size as u32)
158 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
159 .x_y(-1.0, text_pos_y - 2.0)
160 .parent(id)
161 .depth(self.distance_from_player_sqr + 4.0)
162 .set(state.ids.name_bg, ui);
163 Text::new(&self.name)
164 .font_id(self.fonts.cyri.conrod_id)
165 .font_size(text_font_size as u32)
166 .color(self.quality)
167 .x_y(0.0, text_pos_y)
168 .depth(self.distance_from_player_sqr + 3.0)
169 .parent(id)
170 .set(state.ids.name, ui);
171
172 if !self.interaction_options.is_empty() && self.properties.active {
174 let texts = self
175 .interaction_options
176 .iter()
177 .filter_map(|(input, action, color)| {
178 let binding = if let Some(input) = input {
179 Some(self.controls.get_binding(*input)?)
180 } else {
181 None
182 };
183 Some((binding, action, color))
184 })
185 .map(|(input, action, color)| {
186 if let Some(input) = input {
187 let input = input.display_string();
188 (format!("{} {action}", input.as_str()), color)
189 } else {
190 (action.to_string(), color)
191 }
192 })
193 .collect::<Vec<_>>();
194 if state.ids.btns.len() < texts.len() {
195 state.update(|state| {
196 state
197 .ids
198 .btns
199 .resize(texts.len(), &mut ui.widget_id_generator());
200 })
201 }
202
203 let mut max_w = btn_rect_size;
204 let mut max_h = 0.0;
205
206 for (idx, (text, color)) in texts.iter().enumerate() {
207 let hints_text = Text::new(text)
208 .font_id(self.fonts.cyri.conrod_id)
209 .font_size(btn_font_size as u32)
210 .color(**color)
211 .x_y(0.0, btn_text_pos_y + max_h)
212 .depth(self.distance_from_player_sqr + 1.0)
213 .parent(id);
214 let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
215 max_w = max_w.max(w);
216 max_h += h;
217 hints_text.set(state.ids.btns[idx], ui);
218 }
219
220 max_h = max_h.max(btn_rect_size);
221
222 RoundedRectangle::fill_with(
223 [max_w + btn_radius * 2.0, max_h + btn_radius * 2.0],
224 btn_radius,
225 btn_color,
226 )
227 .x_y(0.0, btn_rect_pos_y)
228 .depth(self.distance_from_player_sqr + 2.0)
229 .parent(id)
230 .set(state.ids.btn_bg, ui);
231 }
232 if let Some(collect_failed_data) = self.properties.pickup_failed_pulse {
233 let age = ((self.pulse - collect_failed_data.pulse) / PICKUP_FAILED_FADE_OUT_TIME)
235 .clamp(0.0, 1.0);
236
237 let alpha = 1.0 - age.powi(4);
238 let brightness = 1.0 / (age / 0.07 - 1.0).abs().clamp(0.01, 1.0);
239 let shade_color = |color: Color| {
240 let color::Hsla(hue, sat, lum, alp) = color.to_hsl();
241 color::hsla(hue, sat / brightness, lum * brightness.sqrt(), alp * alpha)
242 };
243
244 let text = match collect_failed_data.reason {
245 HudCollectFailedReason::InventoryFull => {
246 self.localized_strings.get_msg("hud-inventory_full")
247 },
248 HudCollectFailedReason::LootOwned { owner, expiry_secs } => {
249 let owner_name = match owner {
250 HudLootOwner::Name(name) => {
251 Cow::Owned(self.localized_strings.get_content(&name))
252 },
253 HudLootOwner::Group => self.localized_strings.get_msg("hud-another_group"),
254 HudLootOwner::Unknown => self.localized_strings.get_msg("hud-someone_else"),
255 };
256 self.localized_strings.get_msg_ctx(
257 "hud-owned_by_for_secs",
258 &i18n::fluent_args! {
259 "name" => owner_name,
260 "secs" => expiry_secs,
261 },
262 )
263 },
264 };
265
266 Text::new(&text)
267 .font_id(self.fonts.cyri.conrod_id)
268 .font_size(inv_full_font_size as u32)
269 .color(shade_color(Color::Rgba(0.0, 0.0, 0.0, 1.0)))
270 .x_y(-1.0, inv_full_pos_y - 2.0)
271 .parent(id)
272 .depth(self.distance_from_player_sqr + 6.0)
273 .set(state.ids.inv_full_bg, ui);
274
275 Text::new(&text)
276 .font_id(self.fonts.cyri.conrod_id)
277 .font_size(inv_full_font_size as u32)
278 .color(shade_color(Color::Rgba(1.0, 0.0, 0.0, 1.0)))
279 .x_y(0.0, inv_full_pos_y)
280 .parent(id)
281 .depth(self.distance_from_player_sqr + 5.0)
282 .set(state.ids.inv_full, ui);
283 }
284 }
285}