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