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