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