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