1use super::{
2 BLACK, BUFF_COLOR, DEBUFF_COLOR, ERROR_COLOR, GROUP_COLOR, HP_COLOR, KILL_COLOR, LOW_HP_COLOR,
3 QUALITY_EPIC, STAMINA_COLOR, Show, TEXT_COLOR, TEXT_COLOR_GREY, UI_HIGHLIGHT_0, UI_MAIN,
4 cr_color,
5 img_ids::{Imgs, ImgsRot},
6};
7
8use crate::{
9 GlobalState,
10 game_input::GameInput,
11 hud::BuffIcon,
12 settings::Settings,
13 ui::{ImageFrame, Tooltip, TooltipManager, Tooltipable, fonts::Fonts},
14};
15use client::{self, Client};
16use common::{
17 combat,
18 comp::{Stats, group::Role, inventory::item::MaterialStatManifest, invite::InviteKind},
19 resources::Time,
20 uid::{IdMaps, Uid},
21};
22use common_net::sync::WorldSyncExt;
23use conrod_core::{
24 Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, color,
25 position::{Place, Relative},
26 widget::{self, Button, Image, Rectangle, Scrollbar, Text},
27 widget_ids,
28};
29use i18n::Localization;
30use specs::WorldExt;
31
32widget_ids! {
33 pub struct Ids {
34 group_button,
35 bg,
36 title,
37 title_bg,
38 btn_bg,
39 btn_friend,
40 btn_leader,
41 btn_link,
42 btn_kick,
43 btn_leave,
44 scroll_area,
45 scrollbar,
46 members[],
47 bubble_frame,
48 btn_accept,
49 btn_decline,
50 member_panels_bg[],
51 member_panels_frame[],
52 member_panels_txt_bg[],
53 member_panels_txt[],
54 member_health[],
55 member_health_decay[],
56 member_energy[],
57 buffs[],
58 buff_timers[],
59 dead_txt[],
60 health_txt[],
61 combat_rating_indicators[],
62 hardcore_indicators[],
63 timeout_bg,
64 timeout,
65 }
66}
67
68pub struct State {
69 ids: Ids,
70 selected_member: Option<Uid>,
72}
73
74#[derive(WidgetCommon)]
75pub struct Group<'a> {
76 show: &'a mut Show,
77 client: &'a Client,
78 settings: &'a Settings,
79 imgs: &'a Imgs,
80 rot_imgs: &'a ImgsRot,
81 fonts: &'a Fonts,
82 localized_strings: &'a Localization,
83 pulse: f32,
84 global_state: &'a GlobalState,
85 tooltip_manager: &'a mut TooltipManager,
86 msm: &'a MaterialStatManifest,
87 time: &'a Time,
88
89 #[conrod(common_builder)]
90 common: widget::CommonBuilder,
91}
92
93impl<'a> Group<'a> {
94 pub fn new(
95 show: &'a mut Show,
96 client: &'a Client,
97 settings: &'a Settings,
98 imgs: &'a Imgs,
99 rot_imgs: &'a ImgsRot,
100 fonts: &'a Fonts,
101 localized_strings: &'a Localization,
102 pulse: f32,
103 global_state: &'a GlobalState,
104 tooltip_manager: &'a mut TooltipManager,
105 msm: &'a MaterialStatManifest,
106 time: &'a Time,
107 ) -> Self {
108 Self {
109 show,
110 client,
111 settings,
112 imgs,
113 rot_imgs,
114 fonts,
115 localized_strings,
116 pulse,
117 global_state,
118 tooltip_manager,
119 msm,
120 time,
121 common: widget::CommonBuilder::default(),
122 }
123 }
124}
125
126pub enum Event {
127 Accept,
128 Decline,
129 Kick(Uid),
130 LeaveGroup,
131 AssignLeader(Uid),
132}
133
134impl Widget for Group<'_> {
135 type Event = Vec<Event>;
136 type State = State;
137 type Style = ();
138
139 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
140 Self::State {
141 ids: Ids::new(id_gen),
142 selected_member: None,
143 }
144 }
145
146 fn style(&self) -> Self::Style {}
147
148 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
152 common_base::prof_span!("Group::update");
153 let widget::UpdateArgs { state, ui, .. } = args;
154 let mut events = Vec::new();
155 let localized_strings = self.localized_strings;
156 let key_layout = &self.global_state.window.key_layout;
157 let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; let debug_on = self.global_state.settings.interface.toggle_debug;
159 let offset = if debug_on { 270.0 } else { 0.0 };
160 let buffs_tooltip = Tooltip::new({
161 let edge = &self.rot_imgs.tt_side;
164 let corner = &self.rot_imgs.tt_corner;
165 ImageFrame::new(
166 [edge.cw180, edge.none, edge.cw270, edge.cw90],
167 [corner.none, corner.cw270, corner.cw90, corner.cw180],
168 Color::Rgba(0.08, 0.07, 0.04, 1.0),
169 5.0,
170 )
171 })
172 .title_font_size(self.fonts.cyri.scale(15))
173 .parent(ui.window)
174 .desc_font_size(self.fonts.cyri.scale(12))
175 .font_id(self.fonts.cyri.conrod_id)
176 .desc_text_color(TEXT_COLOR);
177
178 let group_members = self
180 .client
181 .group_members()
182 .iter()
183 .filter_map(|(u, r)| match r {
184 Role::Member => Some(u),
185 Role::Pet => None,
186 })
187 .collect::<Vec<_>>();
188 let in_group = !group_members.is_empty();
190 if !in_group {
191 self.show.group_menu = false;
192 self.show.group = false;
193 }
194
195 let uid_to_name_text = |uid: Uid, client: &Client| match client.player_list().get(&uid) {
197 Some(player_info) => player_info
198 .character
199 .as_ref()
200 .map_or_else(|| format!("Player<{}>", uid), |c| c.name.clone()),
201 None => client
202 .state()
203 .ecs()
204 .entity_from_uid(uid)
205 .and_then(|entity| {
206 client
207 .state()
208 .ecs()
209 .read_storage::<Stats>()
210 .get(entity)
211 .map(|stats| stats.name.clone())
212 })
213 .unwrap_or_else(|| format!("Npc<{}>", uid)),
214 };
215
216 let open_invite = self.client.invite().and_then(|invite| {
217 if self
219 .client
220 .player_list()
221 .get(&invite.0)
222 .is_none_or(|player| {
223 self.global_state
224 .profile
225 .mutelist
226 .contains_key(&player.uuid)
227 })
228 {
229 None
230 } else {
231 Some(invite)
232 }
233 });
234
235 let my_uid = self.client.uid();
236
237 if self.show.group_menu || open_invite.is_some() {
241 Rectangle::fill_with([220.0, 140.0], Color::Rgba(0.0, 0.0, 0.0, 0.8))
243 .bottom_left_with_margins_on(ui.window, 108.0, 490.0)
244 .crop_kids()
245 .set(state.ids.bg, ui);
246 }
247 if let Some((_, timeout_start, timeout_dur, _)) = open_invite {
248 Button::image(self.imgs.group_icon)
250 .w_h(49.0, 26.0)
251 .bottom_left_with_margins_on(ui.window, 10.0, 490.0)
252 .set(state.ids.group_button, ui);
253 let timeout_progress =
255 1.0 - timeout_start.elapsed().as_secs_f32() / timeout_dur.as_secs_f32();
256 Image::new(self.imgs.progress_frame)
257 .w_h(100.0, 10.0)
258 .middle_of(state.ids.bg)
259 .color(Some(UI_MAIN))
260 .set(state.ids.timeout_bg, ui);
261 Image::new(self.imgs.progress)
262 .w_h(98.0 * timeout_progress as f64, 8.0)
263 .top_left_with_margins_on(state.ids.timeout_bg, 1.0, 1.0)
264 .color(Some(UI_HIGHLIGHT_0))
265 .set(state.ids.timeout, ui);
266 }
267 if let Some((group_name, leader)) = self.client.group_info().filter(|_| in_group) {
269 if Button::image(if self.show.group_menu {
271 self.imgs.group_icon_press
272 } else {
273 self.imgs.group_icon
274 })
275 .w_h(49.0, 26.0)
276 .bottom_left_with_margins_on(ui.window, 10.0, 490.0)
277 .hover_image(self.imgs.group_icon_hover)
278 .press_image(self.imgs.group_icon_press)
279 .set(state.ids.group_button, ui)
280 .was_clicked()
281 {
282 self.show.group_menu = !self.show.group_menu;
283 };
284 Text::new(&group_name)
285 .up_from(state.ids.group_button, 5.0)
286 .font_size(14)
287 .font_id(self.fonts.cyri.conrod_id)
288 .color(BLACK)
289 .set(state.ids.title_bg, ui);
290 Text::new(&group_name)
291 .bottom_right_with_margins_on(state.ids.title_bg, 1.0, 1.0)
292 .font_size(14)
293 .font_id(self.fonts.cyri.conrod_id)
294 .color(TEXT_COLOR)
295 .set(state.ids.title, ui);
296 let group_size = group_members.len();
298 if state.ids.member_panels_bg.len() < group_size {
299 state.update(|s| {
300 s.ids
301 .member_panels_bg
302 .resize(group_size, &mut ui.widget_id_generator())
303 })
304 };
305 if state.ids.member_health.len() < group_size {
306 state.update(|s| {
307 s.ids
308 .member_health
309 .resize(group_size, &mut ui.widget_id_generator());
310 })
311 };
312 if state.ids.member_health_decay.len() < group_size {
313 state.update(|s| {
314 s.ids
315 .member_health_decay
316 .resize(group_size, &mut ui.widget_id_generator());
317 })
318 };
319 if state.ids.member_energy.len() < group_size {
320 state.update(|s| {
321 s.ids
322 .member_energy
323 .resize(group_size, &mut ui.widget_id_generator())
324 })
325 };
326 if state.ids.member_panels_frame.len() < group_size {
327 state.update(|s| {
328 s.ids
329 .member_panels_frame
330 .resize(group_size, &mut ui.widget_id_generator())
331 })
332 };
333 if state.ids.member_panels_txt.len() < group_size {
334 state.update(|s| {
335 s.ids
336 .member_panels_txt
337 .resize(group_size, &mut ui.widget_id_generator())
338 })
339 };
340 if state.ids.dead_txt.len() < group_size {
341 state.update(|s| {
342 s.ids
343 .dead_txt
344 .resize(group_size, &mut ui.widget_id_generator())
345 })
346 };
347 if state.ids.health_txt.len() < group_size {
348 state.update(|s| {
349 s.ids
350 .health_txt
351 .resize(group_size, &mut ui.widget_id_generator())
352 })
353 };
354 if state.ids.member_panels_txt_bg.len() < group_size {
355 state.update(|s| {
356 s.ids
357 .member_panels_txt_bg
358 .resize(group_size, &mut ui.widget_id_generator())
359 })
360 };
361 if state.ids.combat_rating_indicators.len() < group_size {
362 state.update(|s| {
363 s.ids
364 .combat_rating_indicators
365 .resize(group_size, &mut ui.widget_id_generator())
366 })
367 };
368 if state.ids.hardcore_indicators.len() < group_size {
369 state.update(|s| {
370 s.ids
371 .hardcore_indicators
372 .resize(group_size, &mut ui.widget_id_generator())
373 })
374 };
375 let client_state = self.client.state();
376 let stats = client_state.ecs().read_storage::<Stats>();
377 let skill_sets = client_state.ecs().read_storage::<common::comp::SkillSet>();
378 let healths = client_state.ecs().read_storage::<common::comp::Health>();
379 let energy = client_state.ecs().read_storage::<common::comp::Energy>();
380 let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
381 let inventory = client_state.ecs().read_storage::<common::comp::Inventory>();
382 let id_maps = client_state.ecs().read_resource::<IdMaps>();
383 let bodies = client_state.ecs().read_storage::<common::comp::Body>();
384 let poises = client_state.ecs().read_storage::<common::comp::Poise>();
385 let stances = client_state.ecs().read_storage::<common::comp::Stance>();
386 let hardcore = client_state.ecs().read_storage::<common::comp::Hardcore>();
387
388 let mut total_buff_count = 0;
390 for (i, &uid) in group_members.iter().copied().enumerate() {
391 self.show.group = true;
392 let entity = id_maps.uid_entity(uid);
393 let stats = entity.and_then(|entity| stats.get(entity));
394 let skill_set = entity.and_then(|entity| skill_sets.get(entity));
395 let health = entity.and_then(|entity| healths.get(entity));
396 let energy = entity.and_then(|entity| energy.get(entity));
397 let buffs = entity.and_then(|entity| buffs.get(entity));
398 let inventory = entity.and_then(|entity| inventory.get(entity));
399 let is_leader = uid == leader;
400 let body = entity.and_then(|entity| bodies.get(entity));
401 let poise = entity.and_then(|entity| poises.get(entity));
402 let stance = entity.and_then(|entity| stances.get(entity));
403 let hardcore = entity.and_then(|entity| hardcore.get(entity));
404
405 if let (
406 Some(stats),
407 Some(skill_set),
408 Some(inventory),
409 Some(health),
410 Some(energy),
411 Some(body),
412 Some(poise),
413 ) = (stats, skill_set, inventory, health, energy, body, poise)
414 {
415 let combat_rating = combat::combat_rating(
416 inventory, health, energy, poise, skill_set, *body, self.msm,
417 );
418 let char_name = stats.name.to_string();
419 let health_perc = health.current() / health.base_max().max(health.maximum());
420 let x = if debug_on { i / 8 } else { i / 11 };
422 let y = if debug_on { i % 8 } else { i % 11 };
423 let back = Image::new(self.imgs.member_bg).top_left_with_margins_on(
424 ui.window,
425 50.0 + offset + y as f64 * 77.0,
426 10.0 + x as f64 * 180.0,
427 );
428 let hp_ani = (self.pulse * 4.0).cos() * 0.5 + 0.8; let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani);
430 let health_col = match (health_perc * 100.0) as u8 {
431 0..=20 => crit_hp_color,
432 21..=40 => LOW_HP_COLOR,
433 _ => HP_COLOR,
434 };
435 back.w_h(152.0, 36.0)
438 .color(if is_leader {
439 Some(ERROR_COLOR)
440 } else {
441 Some(TEXT_COLOR)
442 })
443 .set(state.ids.member_panels_bg[i], ui);
444 Image::new(self.imgs.bar_content)
446 .w_h(148.0 * f64::from(health_perc), 22.0)
447 .color(Some(health_col))
448 .top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0)
449 .set(state.ids.member_health[i], ui);
450 let decayed_health = f64::from(1.0 - health.maximum() / health.base_max());
452 if decayed_health > 0.0 {
453 let decay_bar_len = 148.0 * decayed_health;
454 Image::new(self.imgs.bar_content)
455 .w_h(decay_bar_len, 22.0)
456 .color(Some(QUALITY_EPIC))
457 .top_right_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0)
458 .set(state.ids.member_health_decay[i], ui);
459 }
460 if health.is_dead {
461 Text::new(&self.localized_strings.get_msg("hud-group-dead"))
463 .mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0)
464 .font_size(20)
465 .font_id(self.fonts.cyri.conrod_id)
466 .color(KILL_COLOR)
467 .set(state.ids.dead_txt[i], ui);
468 } else {
469 let txt = format!(
471 "{}/{}",
472 health.current().round() as u32,
473 health.maximum().round() as u32,
474 );
475 let font_size = match health.maximum() {
477 x if (0.0..100.0).contains(&x) => 14,
478 x if (100.0..=1000.0).contains(&x) => 13,
479 x if (1000.0..=10000.0).contains(&x) => 12,
480 _ => 11,
481 };
482 let txt_offset = match health.maximum() {
484 x if (0.0..=100.0).contains(&x) => 4.0,
485 x if (100.0..=1000.0).contains(&x) => 4.5,
486 x if (1000.0..=10000.0).contains(&x) => 5.0,
487 _ => 5.5,
488 };
489 Text::new(&txt)
490 .mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset)
491 .font_size(font_size)
492 .font_id(self.fonts.cyri.conrod_id)
493 .color(Color::Rgba(1.0, 1.0, 1.0, 0.5))
494 .set(state.ids.health_txt[i], ui);
495 };
496
497 Image::new(self.imgs.member_frame)
499 .w_h(152.0, 36.0)
500 .middle_of(state.ids.member_panels_bg[i])
501 .color(Some(UI_HIGHLIGHT_0))
502 .set(state.ids.member_panels_frame[i], ui);
503
504 let indicator_col = cr_color(combat_rating);
505 Image::new(self.imgs.combat_rating_ico_shadow)
506 .w_h(18.0, 18.0)
507 .top_left_with_margins_on(state.ids.member_panels_frame[i], -20.0, 2.0)
508 .color(Some(indicator_col))
509 .set(state.ids.combat_rating_indicators[i], ui);
510 if hardcore.is_some() {
511 Image::new(self.imgs.hardcore)
512 .w_h(18.0, 18.0)
513 .top_left_with_margins_on(state.ids.member_panels_frame[i], -20.0, 22.0)
514 .set(state.ids.hardcore_indicators[i], ui);
515 }
516 Text::new(&char_name)
518 .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 22.0 + hardcore.map_or(0.0, |_| 20.0))
519 .font_size(20)
520 .font_id(self.fonts.cyri.conrod_id)
521 .color(BLACK)
522 .w(300.0) .set(state.ids.member_panels_txt_bg[i], ui);
524 Text::new(&char_name)
525 .bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0)
526 .font_size(20)
527 .font_id(self.fonts.cyri.conrod_id)
528 .color(if is_leader { ERROR_COLOR } else { GROUP_COLOR })
529 .w(300.0) .set(state.ids.member_panels_txt[i], ui);
531 let stam_perc = energy.current() / energy.maximum();
532 Image::new(self.imgs.bar_content)
534 .w_h(100.0 * f64::from(stam_perc), 8.0)
535 .color(Some(STAMINA_COLOR))
536 .top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0)
537 .set(state.ids.member_energy[i], ui);
538 if let Some(buffs) = buffs {
539 let buff_icons = BuffIcon::icons_vec(buffs, stance);
540 let buff_count = buff_icons.len().min(11);
542 total_buff_count += buff_count;
543 let gen = &mut ui.widget_id_generator();
544 if state.ids.buffs.len() < total_buff_count {
545 state.update(|state| state.ids.buffs.resize(total_buff_count, gen));
546 }
547 if state.ids.buff_timers.len() < total_buff_count {
548 state.update(|state| {
549 state.ids.buff_timers.resize(total_buff_count, gen)
550 });
551 }
552 let mut prev_id = None;
554 state
555 .ids
556 .buffs
557 .iter()
558 .copied()
559 .zip(state.ids.buff_timers.iter().copied())
560 .skip(total_buff_count - buff_count)
561 .zip(buff_icons.iter())
562 .for_each(|((id, timer_id), buff)| {
563 let max_duration = buff.kind.max_duration();
564 let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
565 let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
566 let current_duration = buff.end_time.map(|end| end - self.time.0);
567 let duration_percentage = current_duration.map_or(1000.0, |cur| {
568 max_duration.map_or(1000.0, |max| cur / max.0 * 1000.0)
569 }) as u32; let buff_img = buff.kind.image(self.imgs);
571 let buff_widget = Image::new(buff_img).w_h(15.0, 15.0);
572 let buff_widget = if let Some(id) = prev_id {
573 buff_widget.right_from(id, 1.0)
574 } else {
575 buff_widget.bottom_left_with_margins_on(
576 state.ids.member_panels_frame[i],
577 -16.0,
578 1.0,
579 )
580 };
581 prev_id = Some(id);
582 buff_widget
583 .color(if current_duration.is_some_and(|cur| cur < 10.0) {
584 Some(pulsating_col)
585 } else {
586 Some(norm_col)
587 })
588 .set(id, ui);
589 let (title, desc_txt) =
591 buff.kind.title_description(localized_strings);
592 let remaining_time = buff.get_buff_time(*self.time);
593 let desc = format!("{}\n\n{}", desc_txt, remaining_time);
594 Image::new(match duration_percentage as u64 {
595 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,
604 })
605 .w_h(15.0, 15.0)
606 .middle_of(id)
607 .with_tooltip(
608 self.tooltip_manager,
609 &title,
610 &desc,
611 &buffs_tooltip,
612 if buff.is_buff {
613 BUFF_COLOR
614 } else {
615 DEBUFF_COLOR
616 },
617 )
618 .set(timer_id, ui);
619 });
620 } else {
621 Text::new(&stats.name.to_string())
623 .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0)
624 .font_size(20)
625 .font_id(self.fonts.cyri.conrod_id)
626 .color(GROUP_COLOR)
627 .set(state.ids.member_panels_txt[i], ui);
628 let back = if i == 0 {
629 Image::new(self.imgs.member_bg)
630 .top_left_with_margins_on(ui.window, offset, 20.0)
631 } else {
632 Image::new(self.imgs.member_bg)
633 .down_from(state.ids.member_panels_bg[i - 1], 40.0)
634 };
635 back.w_h(152.0, 36.0)
636 .color(Some(TEXT_COLOR))
637 .set(state.ids.member_panels_bg[i], ui);
638 Image::new(self.imgs.member_frame)
640 .w_h(152.0, 36.0)
641 .middle_of(state.ids.member_panels_bg[i])
642 .color(Some(UI_HIGHLIGHT_0))
643 .set(state.ids.member_panels_frame[i], ui);
644 Text::new(&self.localized_strings.get_msg("hud-group-out_of_range"))
646 .mid_top_with_margin_on(state.ids.member_panels_bg[i], 3.0)
647 .font_size(16)
648 .font_id(self.fonts.cyri.conrod_id)
649 .color(TEXT_COLOR)
650 .set(state.ids.dead_txt[i], ui);
651 }
652 }
653 }
654
655 if self.show.group_menu {
656 let selected = state.selected_member;
657 if Button::image(self.imgs.button) .w_h(90.0, 22.0)
659 .top_right_with_margins_on(state.ids.bg, 5.0, 5.0)
660 .hover_image(self.imgs.button)
661 .press_image(self.imgs.button)
662 .label_color(TEXT_COLOR_GREY)
663 .image_color(TEXT_COLOR_GREY)
664 .label(&self.localized_strings.get_msg("hud-group-add_friend"))
665 .label_font_id(self.fonts.cyri.conrod_id)
666 .label_font_size(self.fonts.cyri.scale(10))
667 .set(state.ids.btn_friend, ui)
668 .was_clicked()
669 {};
670 if Button::image(self.imgs.button)
671 .w_h(90.0, 22.0)
672 .bottom_right_with_margins_on(state.ids.bg, 5.0, 5.0)
673 .hover_image(self.imgs.button_hover)
674 .press_image(self.imgs.button_press)
675 .label(&self.localized_strings.get_msg("hud-group-leave"))
676 .label_color(TEXT_COLOR)
677 .label_font_id(self.fonts.cyri.conrod_id)
678 .label_font_size(self.fonts.cyri.scale(10))
679 .set(state.ids.btn_leave, ui)
680 .was_clicked()
681 {
682 self.show.group_menu = false;
683 self.show.group = !self.show.group;
684 events.push(Event::LeaveGroup);
685 };
686 if my_uid == Some(leader) {
688 if Button::image(self.imgs.button)
689 .w_h(90.0, 22.0)
690 .mid_bottom_with_margin_on(state.ids.btn_friend, -27.0)
691 .hover_image(self.imgs.button_hover)
692 .press_image(self.imgs.button_press)
693 .label(&self.localized_strings.get_msg("hud-group-assign_leader"))
694 .label_color(if state.selected_member.is_some() {
695 TEXT_COLOR
696 } else {
697 TEXT_COLOR_GREY
698 })
699 .label_font_id(self.fonts.cyri.conrod_id)
700 .label_font_size(self.fonts.cyri.scale(10))
701 .set(state.ids.btn_leader, ui)
702 .was_clicked()
703 {
704 if let Some(uid) = selected {
705 events.push(Event::AssignLeader(uid));
706 state.update(|s| {
707 s.selected_member = None;
708 });
709 }
710 };
711 if Button::image(self.imgs.button)
712 .w_h(90.0, 22.0)
713 .mid_bottom_with_margin_on(state.ids.btn_leader, -27.0)
714 .hover_image(self.imgs.button)
715 .press_image(self.imgs.button)
716 .label(&self.localized_strings.get_msg("hud-group-link_group"))
717 .hover_image(self.imgs.button)
718 .press_image(self.imgs.button)
719 .label_color(TEXT_COLOR_GREY)
720 .image_color(TEXT_COLOR_GREY)
721 .label_font_id(self.fonts.cyri.conrod_id)
722 .label_font_size(self.fonts.cyri.scale(10))
723 .set(state.ids.btn_link, ui)
724 .was_clicked()
725 {};
726 if Button::image(self.imgs.button)
727 .w_h(90.0, 22.0)
728 .mid_bottom_with_margin_on(state.ids.btn_link, -27.0)
729 .down_from(state.ids.btn_link, 5.0)
730 .hover_image(self.imgs.button_hover)
731 .press_image(self.imgs.button_press)
732 .label(&self.localized_strings.get_msg("hud-group-kick"))
733 .label_color(if state.selected_member.is_some() {
734 TEXT_COLOR
735 } else {
736 TEXT_COLOR_GREY
737 })
738 .label_font_id(self.fonts.cyri.conrod_id)
739 .label_font_size(self.fonts.cyri.scale(10))
740 .set(state.ids.btn_kick, ui)
741 .was_clicked()
742 {
743 if let Some(uid) = selected {
744 events.push(Event::Kick(uid));
745 state.update(|s| {
746 s.selected_member = None;
747 });
748 }
749 };
750 }
751 let group_size = group_members.len();
754 if state.ids.members.len() < group_size {
755 state.update(|s| {
756 s.ids
757 .members
758 .resize(group_size, &mut ui.widget_id_generator())
759 })
760 }
761 Rectangle::fill_with([110.0, 135.0], color::TRANSPARENT)
763 .top_left_with_margins_on(state.ids.bg, 5.0, 5.0)
764 .crop_kids()
765 .scroll_kids_vertically()
766 .set(state.ids.scroll_area, ui);
767 Scrollbar::y_axis(state.ids.scroll_area)
768 .thickness(5.0)
769 .rgba(0.33, 0.33, 0.33, 1.0)
770 .set(state.ids.scrollbar, ui);
771 for (i, &uid) in group_members.iter().copied().enumerate() {
773 let selected = state.selected_member == Some(uid);
774 let char_name = uid_to_name_text(uid, self.client);
775 if Button::image(if selected {
777 self.imgs.selection
778 } else {
779 self.imgs.nothing
780 })
781 .w_h(100.0, 22.0)
782 .and(|w| {
783 if i == 0 {
784 w.top_left_with_margins_on(state.ids.scroll_area, 5.0, 0.0)
785 } else {
786 w.down_from(state.ids.members[i - 1], 5.0)
787 }
788 })
789 .hover_image(self.imgs.selection_hover)
790 .press_image(self.imgs.selection_press)
791 .image_color(color::rgba(1.0, 0.82, 0.27, 1.0))
792 .crop_kids()
793 .label_x(Relative::Place(Place::Start(Some(4.0))))
794 .label(&char_name)
795 .label_color(if uid == leader {
796 ERROR_COLOR
797 } else {
798 TEXT_COLOR
799 })
800 .label_font_id(self.fonts.cyri.conrod_id)
801 .label_font_size(self.fonts.cyri.scale(12))
802 .set(state.ids.members[i], ui)
803 .was_clicked()
804 {
805 if Some(uid) != my_uid {
807 state.update(|s| {
809 s.selected_member = if selected { None } else { Some(uid) }
810 });
811 }
812 };
813 }
814 }
818 }
819 if let Some((invite_uid, _, _, kind)) = open_invite {
820 self.show.group = true; let name = uid_to_name_text(invite_uid, self.client);
825 let invite_text = match kind {
826 InviteKind::Group => self.localized_strings.get_msg_ctx(
827 "hud-group-invite_to_join",
828 &i18n::fluent_args! {
829 "name" => name,
830 },
831 ),
832 InviteKind::Trade => self.localized_strings.get_msg_ctx(
833 "hud-group-invite_to_trade",
834 &i18n::fluent_args! {
835 "name" => &name,
836 },
837 ),
838 };
839 Text::new(&invite_text)
840 .mid_top_with_margin_on(state.ids.bg, 5.0)
841 .font_size(12)
842 .font_id(self.fonts.cyri.conrod_id)
843 .color(TEXT_COLOR)
844 .w(165.0) .set(state.ids.title, ui);
846 let accept_key = self
848 .settings
849 .controls
850 .get_binding(GameInput::AcceptGroupInvite)
851 .map_or_else(|| "".into(), |key| key.display_string(key_layout));
852 if Button::image(self.imgs.button)
853 .w_h(90.0, 22.0)
854 .bottom_left_with_margins_on(state.ids.bg, 15.0, 15.0)
855 .hover_image(self.imgs.button_hover)
856 .press_image(self.imgs.button_press)
857 .label(&format!(
858 "[{}] {}",
859 &accept_key,
860 self.localized_strings.get_msg("common-accept")
861 ))
862 .label_color(TEXT_COLOR)
863 .label_font_id(self.fonts.cyri.conrod_id)
864 .label_font_size(self.fonts.cyri.scale(12))
865 .set(state.ids.btn_accept, ui)
866 .was_clicked()
867 {
868 events.push(Event::Accept);
869 self.show.group_menu = true;
870 };
871 let decline_key = self
873 .settings
874 .controls
875 .get_binding(GameInput::DeclineGroupInvite)
876 .map_or_else(|| "".into(), |key| key.display_string(key_layout));
877 if Button::image(self.imgs.button)
878 .w_h(90.0, 22.0)
879 .bottom_right_with_margins_on(state.ids.bg, 15.0, 15.0)
880 .hover_image(self.imgs.button_hover)
881 .press_image(self.imgs.button_press)
882 .label(&format!(
883 "[{}] {}",
884 &decline_key,
885 self.localized_strings.get_msg("common-decline")
886 ))
887 .label_color(TEXT_COLOR)
888 .label_font_id(self.fonts.cyri.conrod_id)
889 .label_font_size(self.fonts.cyri.scale(12))
890 .set(state.ids.btn_decline, ui)
891 .was_clicked()
892 {
893 events.push(Event::Decline);
894 };
895 }
896
897 events
898 }
899}