1use super::{
2 DEFAULT_NPC, ENEMY_HP_COLOR, FACTION_COLOR, GROUP_COLOR, GROUP_MEMBER, HP_COLOR, LOW_HP_COLOR,
3 MARKED_NPC, QUALITY_EPIC, REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG,
4 TEXT_COLOR, cr_color, img_ids::Imgs,
5};
6use crate::{
7 GlobalState,
8 game_input::GameInput,
9 hud::{BuffIcon, controller_icons as icon_utils},
10 ui::{RichText, fonts::Fonts},
11 window::LastInput,
12};
13use common::{
14 comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stance},
15 resources::Time,
16};
17use conrod_core::{
18 Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, color,
19 position::Align,
20 widget::{self, Image, Rectangle, RoundedRectangle, Text},
21 widget_ids,
22};
23use i18n::Localization;
24
25const MAX_BUBBLE_WIDTH: f64 = 250.0;
26widget_ids! {
27 struct Ids {
28 speech_bubble_text,
30 speech_bubble_shadow,
31 speech_bubble_top_left,
32 speech_bubble_top,
33 speech_bubble_top_right,
34 speech_bubble_left,
35 speech_bubble_mid,
36 speech_bubble_right,
37 speech_bubble_bottom_left,
38 speech_bubble_bottom,
39 speech_bubble_bottom_right,
40 speech_bubble_tail,
41 speech_bubble_icon,
42
43 name_bg,
45 name,
46
47 level,
49 level_skull,
50 hardcore,
51 health_bar,
52 decay_bar,
53 health_bar_bg,
54 health_txt,
55 mana_bar,
56 health_bar_fg,
57
58 buffs_align,
60 buffs[],
61 buff_timers[],
62
63 interaction_hints_action,
65 interaction_hints_input,
66 interaction_hints_bg,
67 }
68}
69
70pub struct Info<'a> {
71 pub name: Option<String>,
72 pub health: Option<&'a Health>,
73 pub buffs: Option<&'a Buffs>,
74 pub energy: Option<&'a Energy>,
75 pub combat_rating: Option<f32>,
76 pub hardcore: bool,
77 pub stance: Option<&'a Stance>,
78 pub marked: bool,
79}
80
81pub fn should_show_healthbar(health: &Health) -> bool {
83 (health.current() - health.maximum()).abs() > Health::HEALTH_EPSILON
84 || health.current() < health.base_max()
85}
86pub fn decayed_health_displayed(health: &Health) -> bool {
88 (1.0 - health.maximum() / health.base_max()) > 0.0
89}
90#[derive(WidgetCommon)]
93pub struct Overhead<'a> {
94 info: Option<Info<'a>>,
95 bubble: Option<&'a SpeechBubble>,
96 in_group: bool,
97 pulse: f32,
98 interaction_options: Vec<(GameInput, String)>,
99
100 i18n: &'a Localization,
101 imgs: &'a Imgs,
102 fonts: &'a Fonts,
103 time: &'a Time,
104 global_state: &'a GlobalState,
105
106 #[conrod(common_builder)]
107 common: widget::CommonBuilder,
108}
109
110impl<'a> Overhead<'a> {
111 pub fn new(
112 info: Option<Info<'a>>,
113 bubble: Option<&'a SpeechBubble>,
114 in_group: bool,
115 pulse: f32,
116 interaction_options: Vec<(GameInput, String)>,
117 i18n: &'a Localization,
118 imgs: &'a Imgs,
119 fonts: &'a Fonts,
120 time: &'a Time,
121 global_state: &'a GlobalState,
122 ) -> Self {
123 Self {
124 info,
125 bubble,
126 in_group,
127 pulse,
128 interaction_options,
129 i18n,
130 imgs,
131 fonts,
132 time,
133 global_state,
134 common: widget::CommonBuilder::default(),
135 }
136 }
137}
138
139pub struct State {
140 ids: Ids,
141}
142
143impl Widget for Overhead<'_> {
144 type Event = ();
145 type State = State;
146 type Style = ();
147
148 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
149 State {
150 ids: Ids::new(id_gen),
151 }
152 }
153
154 fn style(&self) -> Self::Style {}
155
156 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
157 let widget::UpdateArgs { id, state, ui, .. } = args;
158 const BARSIZE: f64 = 2.0; const MANA_BAR_HEIGHT: f64 = BARSIZE * 1.5;
160 const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0;
161 if let Some(Info {
162 ref name,
163 health,
164 buffs,
165 energy,
166 combat_rating,
167 hardcore,
168 stance,
169 marked,
170 }) = self.info
171 {
172 let hp_percentage = health.map_or(100.0, |h| {
174 f64::from(h.current() / h.base_max().max(h.maximum()) * 100.0)
175 });
176 let health_current = health.map_or(1.0, |h| f64::from(h.current()));
178 let health_max = health.map_or(1.0, |h| f64::from(h.maximum()));
179 let name_y = if (health_current - health_max).abs() < 1e-6 {
180 MANA_BAR_Y + 20.0
181 } else {
182 MANA_BAR_Y + 32.0
183 };
184 let font_size = if hp_percentage.abs() > 99.9 { 24 } else { 20 };
185 let health_cur_txt = if self.global_state.settings.interface.use_health_prefixes {
188 match health_current as u32 {
189 0..=999 => format!("{:.0}", health_current.max(1.0)),
190 1000..=999999 => format!("{:.0}K", (health_current / 1000.0).max(1.0)),
191 _ => format!("{:.0}M", (health_current / 1.0e6).max(1.0)),
192 }
193 } else {
194 format!("{:.0}", health_current.max(1.0))
195 };
196 let health_max_txt = if self.global_state.settings.interface.use_health_prefixes {
197 match health_max as u32 {
198 0..=999 => format!("{:.0}", health_max.max(1.0)),
199 1000..=999999 => format!("{:.0}K", (health_max / 1000.0).max(1.0)),
200 _ => format!("{:.0}M", (health_max / 1.0e6).max(1.0)),
201 }
202 } else {
203 format!("{:.0}", health_max.max(1.0))
204 };
205 let buff_icons = buffs
208 .as_ref()
209 .map(|buffs| BuffIcon::icons_vec(buffs, stance))
210 .unwrap_or_default();
211 let buff_count = buff_icons.len().min(11);
212 Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
213 .x_y(-1.0, name_y + 60.0)
214 .parent(id)
215 .set(state.ids.buffs_align, ui);
216
217 let generator = &mut ui.widget_id_generator();
218 if state.ids.buffs.len() < buff_count {
219 state.update(|state| state.ids.buffs.resize(buff_count, generator));
220 };
221 if state.ids.buff_timers.len() < buff_count {
222 state.update(|state| state.ids.buff_timers.resize(buff_count, generator));
223 };
224
225 let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
227 let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
228 if self.bubble.is_none() {
230 state
231 .ids
232 .buffs
233 .iter()
234 .copied()
235 .zip(state.ids.buff_timers.iter().copied())
236 .zip(buff_icons.iter())
237 .enumerate()
238 .for_each(|(i, ((id, timer_id), buff))| {
239 let max_duration = buff.kind.max_duration();
241 let current_duration = buff.end_time.map(|end| end - self.time.0);
242 let duration_percentage = current_duration.map_or(1000.0, |cur| {
243 max_duration.map_or(1000.0, |max| cur / max.0 * 1000.0)
244 }) as u32; let buff_img = buff.kind.image(self.imgs);
246 let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
247 let x = i % 5;
249 let y = i / 5;
250 let buff_widget = buff_widget.bottom_left_with_margins_on(
251 state.ids.buffs_align,
252 0.0 + y as f64 * (21.0),
253 0.0 + x as f64 * (21.0),
254 );
255 buff_widget
256 .color(if current_duration.is_some_and(|cur| cur < 10.0) {
257 Some(pulsating_col)
258 } else {
259 Some(norm_col)
260 })
261 .set(id, ui);
262
263 Image::new(match duration_percentage as u64 {
264 875..=1000 => self.imgs.nothing, 750..=874 => self.imgs.buff_0, 625..=749 => self.imgs.buff_1, 500..=624 => self.imgs.buff_2, 375..=499 => self.imgs.buff_3, 250..=374 => self.imgs.buff_4, 125..=249 => self.imgs.buff_5, 0..=124 => self.imgs.buff_6, _ => self.imgs.nothing,
273 })
274 .w_h(20.0, 20.0)
275 .middle_of(id)
276 .set(timer_id, ui);
277 });
278 }
279 Text::new(name.as_deref().unwrap_or(""))
281 .font_id(self.fonts.cyri.conrod_id)
283 .font_size(font_size)
284 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
285 .x_y(-1.0, name_y)
286 .parent(id)
287 .set(state.ids.name_bg, ui);
288 Text::new(name.as_deref().unwrap_or(""))
289 .font_id(self.fonts.cyri.conrod_id)
291 .font_size(font_size)
292 .color(if self.in_group {
293 GROUP_MEMBER
294 } else if marked {
297 MARKED_NPC
298 } else {
299 DEFAULT_NPC
300 })
301 .x_y(0.0, name_y + 1.0)
302 .parent(id)
303 .set(state.ids.name, ui);
304
305 match health {
306 Some(health)
307 if should_show_healthbar(health) || decayed_health_displayed(health) =>
308 {
309 let hp_ani = (self.pulse * 4.0).cos() * 0.5 + 1.0; let crit_hp_color: Color = Color::Rgba(0.93, 0.59, 0.03, hp_ani);
312 let decayed_health = f64::from(1.0 - health.maximum() / health.base_max());
313 Image::new(if self.in_group {self.imgs.health_bar_group_bg} else {self.imgs.enemy_health_bg})
315 .w_h(84.0 * BARSIZE, 10.0 * BARSIZE)
316 .x_y(0.0, MANA_BAR_Y + 6.5) .color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8)))
318 .parent(id)
319 .set(state.ids.health_bar_bg, ui);
320
321 let size_factor = (hp_percentage / 100.0) * BARSIZE;
323 let w = if self.in_group {
324 82.0 * size_factor
325 } else {
326 73.0 * size_factor
327 };
328 let h = 6.0 * BARSIZE;
329 let x = if self.in_group {
330 (0.0 + (hp_percentage / 100.0 * 41.0 - 41.0)) * BARSIZE
331 } else {
332 (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE
333 };
334 Image::new(self.imgs.enemy_bar)
335 .w_h(w, h)
336 .x_y(x, MANA_BAR_Y + 8.0)
337 .color(if self.in_group {
338 Some(match hp_percentage {
340 x if (0.0..25.0).contains(&x) => crit_hp_color,
341 x if (25.0..50.0).contains(&x) => LOW_HP_COLOR,
342 _ => HP_COLOR,
343 })
344 } else {
345 Some(ENEMY_HP_COLOR)
346 })
347 .parent(id)
348 .set(state.ids.health_bar, ui);
349
350 if decayed_health > 0.0 {
351 let x_decayed = if self.in_group {
352 (0.0 - (decayed_health * 41.0 - 41.0)) * BARSIZE
353 } else {
354 (4.5 - (decayed_health * 36.45 - 36.45)) * BARSIZE
355 };
356
357 let decay_bar_len = decayed_health
358 * if self.in_group {
359 82.0 * BARSIZE
360 } else {
361 73.0 * BARSIZE
362 };
363 Image::new(self.imgs.enemy_bar)
364 .w_h(decay_bar_len, h)
365 .x_y(x_decayed, MANA_BAR_Y + 8.0)
366 .color(Some(QUALITY_EPIC))
367 .parent(id)
368 .set(state.ids.decay_bar, ui);
369 }
370 let mut txt = format!("{}/{}", health_cur_txt, health_max_txt);
371 if health.is_dead {
372 txt = self.i18n.get_msg("hud-group-dead").to_string()
373 };
374 Text::new(&txt)
375 .mid_top_with_margin_on(state.ids.health_bar_bg, 2.0)
376 .font_size(10)
377 .font_id(self.fonts.cyri.conrod_id)
378 .color(TEXT_COLOR)
379 .parent(id)
380 .set(state.ids.health_txt, ui);
381
382 if let Some(energy) = energy {
384 let energy_factor = f64::from(energy.current() / energy.maximum());
385 let size_factor = energy_factor * BARSIZE;
386 let w = if self.in_group {
387 80.0 * size_factor
388 } else {
389 72.0 * size_factor
390 };
391 let x = if self.in_group {
392 ((0.0 + (energy_factor * 40.0)) - 40.0) * BARSIZE
393 } else {
394 ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE
395 };
396 Rectangle::fill_with([w, MANA_BAR_HEIGHT], STAMINA_COLOR)
397 .x_y(
398 x, MANA_BAR_Y, )
400 .parent(id)
401 .set(state.ids.mana_bar, ui);
402 }
403
404 Image::new(if self.in_group {self.imgs.health_bar_group} else {self.imgs.enemy_health})
406 .w_h(84.0 * BARSIZE, 10.0 * BARSIZE)
407 .x_y(0.0, MANA_BAR_Y + 6.5) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99)))
409 .parent(id)
410 .set(state.ids.health_bar_fg, ui);
411
412 if let Some(combat_rating) = combat_rating {
413 let indicator_col = cr_color(combat_rating);
414 let artifact_diffculty = 122.0;
415
416 if combat_rating > artifact_diffculty && !self.in_group {
417 let skull_ani =
418 ((self.pulse * 0.7).cos() * 0.5 + 0.5) * 10.0; Image::new(if skull_ani as i32 == 1 && rand::random::<f32>() < 0.9 {
420 self.imgs.skull_2
421 } else {
422 self.imgs.skull
423 })
424 .w_h(18.0 * BARSIZE, 18.0 * BARSIZE)
425 .x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0)
426 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
427 .parent(id)
428 .set(state.ids.level_skull, ui);
429 } else {
430 Image::new(if self.in_group {
431 self.imgs.nothing
432 } else {
433 self.imgs.combat_rating_ico
434 })
435 .w_h(7.0 * BARSIZE, 7.0 * BARSIZE)
436 .x_y(-37.0 * BARSIZE, MANA_BAR_Y + 6.0)
437 .color(Some(indicator_col))
438 .parent(id)
439 .set(state.ids.level, ui);
440 }
441 }
442
443 if hardcore {
444 Image::new(self.imgs.hardcore)
445 .w_h(18.0 * BARSIZE, 18.0 * BARSIZE)
446 .x_y(39.0 * BARSIZE, MANA_BAR_Y + 13.0)
447 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
448 .parent(id)
449 .set(state.ids.hardcore, ui);
450 }
451 },
452 _ => {},
453 }
454
455 if !self.interaction_options.is_empty() {
457 let scale = 30.0;
458 let btn_rect_size = scale * 0.8;
459 let btn_font_size = scale * 0.6;
460 let btn_radius = btn_rect_size / 5.0;
461 let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
462 let mut max_w = btn_rect_size;
463 let spacing = 8.0;
464
465 let interactions: Vec<(String, String)> = match self
467 .global_state
468 .window
469 .last_input()
470 {
471 LastInput::Keyboard | LastInput::Mouse => self
472 .interaction_options
473 .iter()
474 .map(|(input, action)| {
475 match self.global_state.settings.controls.get_binding(*input) {
476 Some(binding) => (binding.display_string(), action.to_string()),
477 None => (icon_utils::UNBOUND_KEY.to_string(), action.to_string()),
478 }
479 })
480 .collect(),
481
482 LastInput::Controller => self
483 .interaction_options
484 .iter()
485 .map(|(input, action)| {
486 let input_str = icon_utils::get_controller_input_string(
487 *input,
488 &self.global_state.settings,
489 self.global_state.window.controller_type(),
490 );
491
492 match input_str {
493 Some(binding) => (binding, action.to_string()),
494 None => (icon_utils::UNBOUND_KEY.to_string(), action.to_string()),
495 }
496 })
497 .collect(),
498 };
499
500 let mut temp_list: Vec<String> = Vec::new();
505 for i in &interactions {
506 let s = i.0.clone();
508 temp_list.push(s);
509 }
510 let icons_input = temp_list.join("\n");
511
512 let mut temp_list: Vec<String> = Vec::new();
514 for i in &interactions {
515 let s = i.1.clone();
516 temp_list.push(s);
517 }
518 let action_input = temp_list.join("\n");
519
520 let anchor_id = self.info.map_or(state.ids.name, |info| {
521 if info.health.is_some_and(should_show_healthbar) {
522 if info.energy.is_some() {
523 state.ids.mana_bar
524 } else {
525 state.ids.health_bar
526 }
527 } else {
528 state.ids.name
529 }
530 });
531
532 let actions_hint = RichText::new(&action_input, self.imgs)
534 .font_id(self.fonts.cyri.conrod_id)
535 .font_size(btn_font_size as u32)
536 .color(TEXT_COLOR)
537 .parent(id)
538 .justify(conrod_core::text::Justify::Left);
539
540 let [actions_w, actions_h] = actions_hint.get_wh(ui).unwrap_or([btn_rect_size; 2]);
541 max_w += actions_w;
542 let max_h = actions_h;
543
544 let inputs_hint = RichText::new(&icons_input, self.imgs)
546 .font_id(self.fonts.cyri.conrod_id)
547 .font_size(btn_font_size as u32)
548 .color(TEXT_COLOR)
549 .parent(id)
550 .justify(conrod_core::text::Justify::Right);
551
552 let [inputs_w, _inputs_h] = inputs_hint.get_wh(ui).unwrap_or([btn_rect_size; 2]);
553 max_w += inputs_w;
554 let box_offset = -(inputs_w + spacing) / 2.0;
555
556 let centering_offset = (inputs_w + spacing) / 2.0;
559
560 actions_hint
561 .down_from(anchor_id, 12.0)
562 .x_relative_to(anchor_id, centering_offset)
563 .depth(1.0)
564 .set(state.ids.interaction_hints_action, ui);
565
566 inputs_hint
567 .left_from(state.ids.interaction_hints_action, spacing)
568 .depth(1.0)
569 .set(state.ids.interaction_hints_input, ui);
570
571 RoundedRectangle::fill_with(
572 [max_w + btn_radius * 2.0, max_h + btn_radius * 2.0],
573 btn_radius,
574 btn_color,
575 )
576 .depth(2.0)
577 .x_relative_to(state.ids.interaction_hints_action, box_offset)
578 .align_middle_y_of(state.ids.interaction_hints_action)
579 .parent(id)
580 .set(state.ids.interaction_hints_bg, ui);
581 }
582 }
583 if let Some(bubble) = self.bubble {
585 let dark_mode = self.global_state.settings.interface.speech_bubble_dark_mode;
586 let bubble_contents: String = self.i18n.get_content(bubble.content());
587 let (text_color, shadow_color) = bubble_color(bubble, dark_mode);
588 let mut text = Text::new(&bubble_contents)
589 .color(text_color)
590 .font_id(self.fonts.cyri.conrod_id)
591 .font_size(18)
592 .up_from(state.ids.name, 26.0)
593 .x_align_to(state.ids.name, Align::Middle)
594 .parent(id);
595
596 if let Some(w) = text.get_w(ui)
597 && w > MAX_BUBBLE_WIDTH
598 {
599 text = text.w(MAX_BUBBLE_WIDTH);
600 }
601 Image::new(if dark_mode {
602 self.imgs.dark_bubble_top_left
603 } else {
604 self.imgs.speech_bubble_top_left
605 })
606 .w_h(16.0, 16.0)
607 .top_left_with_margin_on(state.ids.speech_bubble_text, -20.0)
608 .parent(id)
609 .set(state.ids.speech_bubble_top_left, ui);
610 Image::new(if dark_mode {
611 self.imgs.dark_bubble_top
612 } else {
613 self.imgs.speech_bubble_top
614 })
615 .h(16.0)
616 .padded_w_of(state.ids.speech_bubble_text, -4.0)
617 .mid_top_with_margin_on(state.ids.speech_bubble_text, -20.0)
618 .parent(id)
619 .set(state.ids.speech_bubble_top, ui);
620 Image::new(if dark_mode {
621 self.imgs.dark_bubble_top_right
622 } else {
623 self.imgs.speech_bubble_top_right
624 })
625 .w_h(16.0, 16.0)
626 .top_right_with_margin_on(state.ids.speech_bubble_text, -20.0)
627 .parent(id)
628 .set(state.ids.speech_bubble_top_right, ui);
629 Image::new(if dark_mode {
630 self.imgs.dark_bubble_left
631 } else {
632 self.imgs.speech_bubble_left
633 })
634 .w(16.0)
635 .padded_h_of(state.ids.speech_bubble_text, -4.0)
636 .mid_left_with_margin_on(state.ids.speech_bubble_text, -20.0)
637 .parent(id)
638 .set(state.ids.speech_bubble_left, ui);
639 Image::new(if dark_mode {
640 self.imgs.dark_bubble_mid
641 } else {
642 self.imgs.speech_bubble_mid
643 })
644 .padded_wh_of(state.ids.speech_bubble_text, -4.0)
645 .top_left_with_margin_on(state.ids.speech_bubble_text, -4.0)
646 .parent(id)
647 .set(state.ids.speech_bubble_mid, ui);
648 Image::new(if dark_mode {
649 self.imgs.dark_bubble_right
650 } else {
651 self.imgs.speech_bubble_right
652 })
653 .w(16.0)
654 .padded_h_of(state.ids.speech_bubble_text, -4.0)
655 .mid_right_with_margin_on(state.ids.speech_bubble_text, -20.0)
656 .parent(id)
657 .set(state.ids.speech_bubble_right, ui);
658 Image::new(if dark_mode {
659 self.imgs.dark_bubble_bottom_left
660 } else {
661 self.imgs.speech_bubble_bottom_left
662 })
663 .w_h(16.0, 16.0)
664 .bottom_left_with_margin_on(state.ids.speech_bubble_text, -20.0)
665 .parent(id)
666 .set(state.ids.speech_bubble_bottom_left, ui);
667 Image::new(if dark_mode {
668 self.imgs.dark_bubble_bottom
669 } else {
670 self.imgs.speech_bubble_bottom
671 })
672 .h(16.0)
673 .padded_w_of(state.ids.speech_bubble_text, -4.0)
674 .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -20.0)
675 .parent(id)
676 .set(state.ids.speech_bubble_bottom, ui);
677 Image::new(if dark_mode {
678 self.imgs.dark_bubble_bottom_right
679 } else {
680 self.imgs.speech_bubble_bottom_right
681 })
682 .w_h(16.0, 16.0)
683 .bottom_right_with_margin_on(state.ids.speech_bubble_text, -20.0)
684 .parent(id)
685 .set(state.ids.speech_bubble_bottom_right, ui);
686 let tail = Image::new(if dark_mode {
687 self.imgs.dark_bubble_tail
688 } else {
689 self.imgs.speech_bubble_tail
690 })
691 .parent(id)
692 .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -32.0);
693
694 if dark_mode {
695 tail.w_h(22.0, 13.0)
696 } else {
697 tail.w_h(22.0, 28.0)
698 }
699 .set(state.ids.speech_bubble_tail, ui);
700
701 let mut text_shadow = Text::new(&bubble_contents)
702 .color(shadow_color)
703 .font_id(self.fonts.cyri.conrod_id)
704 .font_size(18)
705 .x_relative_to(state.ids.speech_bubble_text, 1.0)
706 .y_relative_to(state.ids.speech_bubble_text, -1.0)
707 .parent(id);
708 text.depth(text_shadow.get_depth() - 1.0)
710 .set(state.ids.speech_bubble_text, ui);
711 if let Some(w) = text_shadow.get_w(ui)
712 && w > MAX_BUBBLE_WIDTH
713 {
714 text_shadow = text_shadow.w(MAX_BUBBLE_WIDTH);
715 }
716 text_shadow.set(state.ids.speech_bubble_shadow, ui);
717 let icon = if self.global_state.settings.interface.speech_bubble_icon {
718 bubble_icon(bubble, self.imgs)
719 } else {
720 self.imgs.nothing
721 };
722 Image::new(icon)
723 .w_h(16.0, 16.0)
724 .top_left_with_margin_on(state.ids.speech_bubble_text, -16.0)
725 .set(state.ids.speech_bubble_icon, ui);
728 }
729 }
730}
731
732fn bubble_color(bubble: &SpeechBubble, dark_mode: bool) -> (Color, Color) {
733 let light_color = match bubble.icon {
734 SpeechBubbleType::Tell => TELL_COLOR,
735 SpeechBubbleType::Say => SAY_COLOR,
736 SpeechBubbleType::Region => REGION_COLOR,
737 SpeechBubbleType::Group => GROUP_COLOR,
738 SpeechBubbleType::Faction => FACTION_COLOR,
739 SpeechBubbleType::World
740 | SpeechBubbleType::Quest
741 | SpeechBubbleType::Trade
742 | SpeechBubbleType::None => TEXT_COLOR,
743 };
744 if dark_mode {
745 (light_color, TEXT_BG)
746 } else {
747 (TEXT_BG, light_color)
748 }
749}
750
751fn bubble_icon(sb: &SpeechBubble, imgs: &Imgs) -> conrod_core::image::Id {
752 match sb.icon {
753 SpeechBubbleType::Tell => imgs.chat_tell_small,
755 SpeechBubbleType::Say => imgs.chat_say_small,
756 SpeechBubbleType::Region => imgs.chat_region_small,
757 SpeechBubbleType::Group => imgs.chat_group_small,
758 SpeechBubbleType::Faction => imgs.chat_faction_small,
759 SpeechBubbleType::World => imgs.chat_world_small,
760 SpeechBubbleType::Quest => imgs.nothing, SpeechBubbleType::Trade => imgs.nothing, SpeechBubbleType::None => imgs.nothing, }
764}