1use client::{Client, EcsEntity};
2use common::{comp::ItemKey, rtsim};
3use conrod_core::{
4 Color, Colorable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, color,
5 widget::{self, Button, Image, Rectangle, Text},
6 widget_ids,
7};
8use i18n::Localization;
9use std::time::{Duration, Instant};
10
11use crate::ui::{TooltipManager, fonts::Fonts};
12use inline_tweak::*;
13
14use super::{
15 Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, animate_by_pulse,
16 img_ids::{Imgs, ImgsRot},
17 item_imgs::ItemImgs,
18};
19
20pub struct State {
21 ids: Ids,
22 text_timer: Option<Instant>,
23 text_position: usize,
24 last_displayed_text: Option<String>, }
26
27widget_ids! {
28 pub struct Ids {
29 quest_close,
30 bg,
31 frame,
32 icon,
33 close,
34 title_align,
35 title,
36 text_align,
37 topics_align,
38 scrollbar,
39 intro_txt,
40 desc_txt_0,
41 quest_objectives[],
42 quest_response_txt,
43 objective_text,
44 quest_responses_frames[],
45 quest_responses_btn[],
46 quest_responses_icons[],
47 quest_responses_amounts[],
48 quest_rewards_txts[],
49 accept_btn,
50 decline_btn,
51 }
52}
53
54#[derive(WidgetCommon)]
55pub struct Quest<'a> {
56 _show: &'a Show,
57 _client: &'a Client,
58 imgs: &'a Imgs,
59 fonts: &'a Fonts,
60 localized_strings: &'a Localization,
61 _rot_imgs: &'a ImgsRot,
62 _tooltip_manager: &'a mut TooltipManager,
63 item_imgs: &'a ItemImgs,
64 sender: EcsEntity,
65 dialogue: &'a rtsim::Dialogue<true>,
66 pulse: f32,
67
68 #[conrod(common_builder)]
69 common: widget::CommonBuilder,
70}
71
72impl<'a> Quest<'a> {
73 pub fn new(
74 _show: &'a Show,
75 _client: &'a Client,
76 imgs: &'a Imgs,
77 fonts: &'a Fonts,
78 localized_strings: &'a Localization,
79 _rot_imgs: &'a ImgsRot,
80 _tooltip_manager: &'a mut TooltipManager,
81 item_imgs: &'a ItemImgs,
82 sender: EcsEntity,
83 dialogue: &'a rtsim::Dialogue<true>,
84 pulse: f32,
85 ) -> Self {
86 Self {
87 _show,
88 _client,
89 imgs,
90 _rot_imgs,
91 fonts,
92 localized_strings,
93 _tooltip_manager,
94 item_imgs,
95 sender,
96 dialogue,
97 pulse,
98 common: widget::CommonBuilder::default(),
99 }
100 }
101
102 fn update_text(&self, state: &mut State, ui: &mut UiCell, msg_text: &str) {
103 let now = Instant::now();
104
105 let is_new_message = state.text_position == 0
107 || state.text_position > msg_text.chars().count()
108 || state.last_displayed_text.as_deref() != Some(msg_text);
109
110 if is_new_message {
111 state.text_timer = Some(now);
112 state.text_position = 1; state.last_displayed_text = Some(msg_text.to_string()); }
115
116 if state.text_timer.is_none() {
117 state.text_timer = Some(now);
118 }
119
120 if let Some(start_time) = state.text_timer {
121 if now.duration_since(start_time) >= Duration::from_millis(10)
122 && state.text_position < msg_text.chars().count()
123 {
124 state.text_position += 1;
125 state.text_timer = Some(now);
126 }
127 }
128
129 let display_text: String = msg_text
130 .chars()
131 .take(state.text_position.min(msg_text.chars().count()))
132 .collect();
133
134 Text::new(&display_text)
135 .top_left_with_margins_on(state.ids.text_align, 8.0, 8.0)
136 .w(500.0)
137 .font_id(self.fonts.cyri.conrod_id)
138 .font_size(self.fonts.cyri.scale(20))
139 .color(TEXT_COLOR)
140 .set(state.ids.desc_txt_0, ui);
141 }
142}
143
144pub enum Event {
145 Dialogue(EcsEntity, rtsim::Dialogue),
146 #[allow(dead_code)]
147 Close,
148}
149
150impl Widget for Quest<'_> {
151 type Event = Option<Event>;
152 type State = State;
153 type Style = ();
154
155 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
156 Self::State {
157 ids: Ids::new(id_gen),
158 text_timer: None,
159 text_position: 0,
160 last_displayed_text: None,
161 }
162 }
163
164 fn style(&self) -> Self::Style {}
165
166 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
167 let widget::UpdateArgs { state, ui, .. } = args;
168 let mut event = None;
169
170 Image::new(self.imgs.dialogue_bg)
172 .mid_bottom_with_margin_on(ui.window, 80.0)
173 .color(Some(UI_MAIN))
174 .w_h(720.0, 234.0)
175 .set(state.ids.bg, ui);
176 Image::new(self.imgs.dialogue_frame)
178 .middle_of(state.ids.bg)
179 .color(Some(UI_HIGHLIGHT_0))
180 .w_h(720.0, 234.0)
181 .set(state.ids.frame, ui);
182
183 Rectangle::fill_with([tweak!(529.0), tweak!(230.0)], color::TRANSPARENT)
198 .top_left_with_margins_on(state.ids.frame, tweak!(2.0), tweak!(2.0))
199 .scroll_kids_vertically()
200 .set(state.ids.text_align, ui);
201 Rectangle::fill_with([tweak!(186.0), tweak!(230.0)], color::TRANSPARENT)
203 .top_right_with_margins_on(state.ids.frame, tweak!(2.0), tweak!(2.0))
204 .scroll_kids_vertically()
205 .set(state.ids.topics_align, ui);
206
207 let msg_text = match &self.dialogue.kind {
209 rtsim::DialogueKind::Start | rtsim::DialogueKind::End => None,
210 rtsim::DialogueKind::Statement(msg) => Some(self.localized_strings.get_content(msg)),
211 rtsim::DialogueKind::Question { msg, .. } => {
212 Some(self.localized_strings.get_content(msg))
213 },
214 rtsim::DialogueKind::Response { response, .. } => {
215 Some(self.localized_strings.get_content(&response.msg))
216 },
217 };
218
219 if let Some(msg_text) = msg_text {
220 state.update(|s| {
221 self.update_text(s, ui, &msg_text);
222 });
223 }
224
225 if let rtsim::DialogueKind::Question { responses, tag, .. } = &self.dialogue.kind {
226 if state.ids.quest_responses_frames.len() < responses.len() {
227 state.update(|s| {
228 s.ids
229 .quest_responses_frames
230 .resize(responses.len(), &mut ui.widget_id_generator())
231 })
232 };
233 if state.ids.quest_responses_icons.len() < responses.len() {
234 state.update(|s| {
235 s.ids
236 .quest_responses_icons
237 .resize(responses.len(), &mut ui.widget_id_generator())
238 })
239 };
240 if state.ids.quest_responses_amounts.len() < responses.len() {
241 state.update(|s| {
242 s.ids
243 .quest_responses_amounts
244 .resize(responses.len(), &mut ui.widget_id_generator())
245 })
246 };
247 if state.ids.quest_rewards_txts.len() < responses.len() {
248 state.update(|s| {
249 s.ids
250 .quest_rewards_txts
251 .resize(responses.len(), &mut ui.widget_id_generator())
252 })
253 };
254 if state.ids.quest_responses_btn.len() < responses.len() {
255 state.update(|s| {
256 s.ids
257 .quest_responses_btn
258 .resize(responses.len(), &mut ui.widget_id_generator())
259 })
260 };
261
262 for (i, (response_id, response)) in responses.iter().enumerate() {
263 let frame = Button::image(self.imgs.nothing).w_h(186.0, 40.0);
264 let frame = if i == 0 {
265 frame.top_left_with_margins_on(
266 state.ids.topics_align,
267 tweak!(20.0),
268 tweak!(2.0),
269 )
270 } else {
271 frame.down_from(state.ids.quest_responses_frames[i - 1], tweak!(10.0))
272 };
273 frame.set(state.ids.quest_responses_frames[i], ui);
274
275 if Button::image(self.imgs.nothing)
277 .w_h(120.0, 40.0)
278 .hover_image(self.imgs.nothing)
279 .press_image(self.imgs.nothing)
280 .middle_of(state.ids.quest_responses_frames[i])
281 .set(state.ids.quest_responses_btn[i], ui)
282 .was_clicked()
283 {
284 event = Some(Event::Dialogue(self.sender, rtsim::Dialogue {
285 id: self.dialogue.id,
286 kind: rtsim::DialogueKind::Response {
287 tag: *tag,
288 response: response.clone(),
289 response_id: *response_id,
290 },
291 }));
292 }
293
294 if let Some((item, amount)) = &response.given_item {
296 Image::new(animate_by_pulse(
297 &self
298 .item_imgs
299 .img_ids_or_not_found_img(ItemKey::from(&**item)),
300 self.pulse,
301 ))
302 .middle_of(state.ids.quest_responses_btn[i])
303 .w_h(20.0, 20.0)
304 .graphics_for(state.ids.quest_responses_btn[i])
305 .set(state.ids.quest_responses_icons[i], ui);
306
307 if *amount > 0 {
308 Text::new(&format!("x{amount}"))
309 .mid_bottom_with_margin_on(state.ids.quest_responses_frames[i], 3.0)
310 .font_id(self.fonts.cyri.conrod_id)
311 .font_size(self.fonts.cyri.scale(12))
312 .color(TEXT_COLOR)
313 .wrap_by_word()
314 .set(state.ids.quest_responses_amounts[i], ui);
315 }
316 }
317
318 Text::new(&self.localized_strings.get_content(&response.msg))
319 .middle_of(state.ids.quest_responses_btn[i])
320 .graphics_for(state.ids.quest_responses_btn[i])
321 .font_id(self.fonts.cyri.conrod_id)
322 .color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
323 .font_size(self.fonts.cyri.scale(tweak!(14)))
324 .set(state.ids.quest_rewards_txts[i], ui);
325 }
326 }
327
328 event
329 }
330}