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 buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; let debug_on = self.global_state.settings.interface.toggle_debug;
158 let offset = if debug_on { 270.0 } else { 0.0 };
159 let buffs_tooltip = Tooltip::new({
160 let edge = &self.rot_imgs.tt_side;
163 let corner = &self.rot_imgs.tt_corner;
164 ImageFrame::new(
165 [edge.cw180, edge.none, edge.cw270, edge.cw90],
166 [corner.none, corner.cw270, corner.cw90, corner.cw180],
167 Color::Rgba(0.08, 0.07, 0.04, 1.0),
168 5.0,
169 )
170 })
171 .title_font_size(self.fonts.cyri.scale(15))
172 .parent(ui.window)
173 .desc_font_size(self.fonts.cyri.scale(12))
174 .font_id(self.fonts.cyri.conrod_id)
175 .desc_text_color(TEXT_COLOR);
176
177 let group_members = self
179 .client
180 .group_members()
181 .iter()
182 .filter_map(|(u, r)| match r {
183 Role::Member => Some(u),
184 Role::Pet => None,
185 })
186 .collect::<Vec<_>>();
187 let in_group = !group_members.is_empty();
189 if !in_group {
190 self.show.group_menu = false;
191 self.show.group = false;
192 }
193
194 let uid_to_name_text = |uid: Uid, client: &Client| match client.player_list().get(&uid) {
196 Some(player_info) => player_info.character.as_ref().map_or_else(
197 || format!("Player<{}>", uid),
198 |c| self.localized_strings.get_content(&c.name),
199 ),
200 None => client
201 .state()
202 .ecs()
203 .entity_from_uid(uid)
204 .and_then(|entity| {
205 client
206 .state()
207 .ecs()
208 .read_storage::<Stats>()
209 .get(entity)
210 .map(|stats| self.localized_strings.get_content(&stats.name))
211 })
212 .unwrap_or_else(|| format!("Npc<{}>", uid)),
213 };
214
215 let open_invite = self.client.invite().and_then(|invite| {
216 if self
218 .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 None
229 } else {
230 Some(invite)
231 }
232 });
233
234 let my_uid = self.client.uid();
235
236 if self.show.group_menu || open_invite.is_some() {
240 Rectangle::fill_with([220.0, 140.0], Color::Rgba(0.0, 0.0, 0.0, 0.8))
242 .bottom_left_with_margins_on(ui.window, 108.0, 490.0)
243 .crop_kids()
244 .set(state.ids.bg, ui);
245 }
246 if let Some((_, timeout_start, timeout_dur, _)) = open_invite {
247 Button::image(self.imgs.group_icon)
249 .w_h(49.0, 26.0)
250 .bottom_left_with_margins_on(ui.window, 10.0, 490.0)
251 .set(state.ids.group_button, ui);
252 let timeout_progress =
254 1.0 - timeout_start.elapsed().as_secs_f32() / timeout_dur.as_secs_f32();
255 Image::new(self.imgs.progress_frame)
256 .w_h(100.0, 10.0)
257 .middle_of(state.ids.bg)
258 .color(Some(UI_MAIN))
259 .set(state.ids.timeout_bg, ui);
260 Image::new(self.imgs.progress)
261 .w_h(98.0 * timeout_progress as f64, 8.0)
262 .top_left_with_margins_on(state.ids.timeout_bg, 1.0, 1.0)
263 .color(Some(UI_HIGHLIGHT_0))
264 .set(state.ids.timeout, ui);
265 }
266 if let Some((group_name, leader)) = self.client.group_info().filter(|_| in_group) {
268 if Button::image(if self.show.group_menu {
270 self.imgs.group_icon_press
271 } else {
272 self.imgs.group_icon
273 })
274 .w_h(49.0, 26.0)
275 .bottom_left_with_margins_on(ui.window, 10.0, 490.0)
276 .hover_image(self.imgs.group_icon_hover)
277 .press_image(self.imgs.group_icon_press)
278 .set(state.ids.group_button, ui)
279 .was_clicked()
280 {
281 self.show.group_menu = !self.show.group_menu;
282 };
283 Text::new(&group_name)
284 .up_from(state.ids.group_button, 5.0)
285 .font_size(14)
286 .font_id(self.fonts.cyri.conrod_id)
287 .color(BLACK)
288 .set(state.ids.title_bg, ui);
289 Text::new(&group_name)
290 .bottom_right_with_margins_on(state.ids.title_bg, 1.0, 1.0)
291 .font_size(14)
292 .font_id(self.fonts.cyri.conrod_id)
293 .color(TEXT_COLOR)
294 .set(state.ids.title, ui);
295 let group_size = group_members.len();
297 if state.ids.member_panels_bg.len() < group_size {
298 state.update(|s| {
299 s.ids
300 .member_panels_bg
301 .resize(group_size, &mut ui.widget_id_generator())
302 })
303 };
304 if state.ids.member_health.len() < group_size {
305 state.update(|s| {
306 s.ids
307 .member_health
308 .resize(group_size, &mut ui.widget_id_generator());
309 })
310 };
311 if state.ids.member_health_decay.len() < group_size {
312 state.update(|s| {
313 s.ids
314 .member_health_decay
315 .resize(group_size, &mut ui.widget_id_generator());
316 })
317 };
318 if state.ids.member_energy.len() < group_size {
319 state.update(|s| {
320 s.ids
321 .member_energy
322 .resize(group_size, &mut ui.widget_id_generator())
323 })
324 };
325 if state.ids.member_panels_frame.len() < group_size {
326 state.update(|s| {
327 s.ids
328 .member_panels_frame
329 .resize(group_size, &mut ui.widget_id_generator())
330 })
331 };
332 if state.ids.member_panels_txt.len() < group_size {
333 state.update(|s| {
334 s.ids
335 .member_panels_txt
336 .resize(group_size, &mut ui.widget_id_generator())
337 })
338 };
339 if state.ids.dead_txt.len() < group_size {
340 state.update(|s| {
341 s.ids
342 .dead_txt
343 .resize(group_size, &mut ui.widget_id_generator())
344 })
345 };
346 if state.ids.health_txt.len() < group_size {
347 state.update(|s| {
348 s.ids
349 .health_txt
350 .resize(group_size, &mut ui.widget_id_generator())
351 })
352 };
353 if state.ids.member_panels_txt_bg.len() < group_size {
354 state.update(|s| {
355 s.ids
356 .member_panels_txt_bg
357 .resize(group_size, &mut ui.widget_id_generator())
358 })
359 };
360 if state.ids.combat_rating_indicators.len() < group_size {
361 state.update(|s| {
362 s.ids
363 .combat_rating_indicators
364 .resize(group_size, &mut ui.widget_id_generator())
365 })
366 };
367 if state.ids.hardcore_indicators.len() < group_size {
368 state.update(|s| {
369 s.ids
370 .hardcore_indicators
371 .resize(group_size, &mut ui.widget_id_generator())
372 })
373 };
374 let client_state = self.client.state();
375 let stats = client_state.ecs().read_storage::<Stats>();
376 let skill_sets = client_state.ecs().read_storage::<common::comp::SkillSet>();
377 let healths = client_state.ecs().read_storage::<common::comp::Health>();
378 let energy = client_state.ecs().read_storage::<common::comp::Energy>();
379 let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
380 let inventory = client_state.ecs().read_storage::<common::comp::Inventory>();
381 let id_maps = client_state.ecs().read_resource::<IdMaps>();
382 let bodies = client_state.ecs().read_storage::<common::comp::Body>();
383 let poises = client_state.ecs().read_storage::<common::comp::Poise>();
384 let stances = client_state.ecs().read_storage::<common::comp::Stance>();
385 let hardcore = client_state.ecs().read_storage::<common::comp::Hardcore>();
386
387 let mut total_buff_count = 0;
389 for (i, &uid) in group_members.iter().copied().enumerate() {
390 self.show.group = true;
391 let entity = id_maps.uid_entity(uid);
392 let stats = entity.and_then(|entity| stats.get(entity));
393 let skill_set = entity.and_then(|entity| skill_sets.get(entity));
394 let health = entity.and_then(|entity| healths.get(entity));
395 let energy = entity.and_then(|entity| energy.get(entity));
396 let buffs = entity.and_then(|entity| buffs.get(entity));
397 let inventory = entity.and_then(|entity| inventory.get(entity));
398 let is_leader = uid == leader;
399 let body = entity.and_then(|entity| bodies.get(entity));
400 let poise = entity.and_then(|entity| poises.get(entity));
401 let stance = entity.and_then(|entity| stances.get(entity));
402 let hardcore = entity.and_then(|entity| hardcore.get(entity));
403
404 if let (
405 Some(stats),
406 Some(skill_set),
407 Some(inventory),
408 Some(health),
409 Some(energy),
410 Some(body),
411 Some(poise),
412 ) = (stats, skill_set, inventory, health, energy, body, poise)
413 {
414 let combat_rating = combat::combat_rating(
415 inventory, health, energy, poise, skill_set, *body, self.msm,
416 );
417 let char_name = self.localized_strings.get_content(&stats.name);
418 let health_perc = health.current() / health.base_max().max(health.maximum());
419 let x = if debug_on { i / 8 } else { i / 11 };
421 let y = if debug_on { i % 8 } else { i % 11 };
422 let back = Image::new(self.imgs.member_bg).top_left_with_margins_on(
423 ui.window,
424 50.0 + offset + y as f64 * 77.0,
425 10.0 + x as f64 * 180.0,
426 );
427 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);
429 let health_col = match (health_perc * 100.0) as u8 {
430 0..=20 => crit_hp_color,
431 21..=40 => LOW_HP_COLOR,
432 _ => HP_COLOR,
433 };
434 back.w_h(152.0, 36.0)
437 .color(if is_leader {
438 Some(ERROR_COLOR)
439 } else {
440 Some(TEXT_COLOR)
441 })
442 .set(state.ids.member_panels_bg[i], ui);
443 Image::new(self.imgs.bar_content)
445 .w_h(148.0 * f64::from(health_perc), 22.0)
446 .color(Some(health_col))
447 .top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0)
448 .set(state.ids.member_health[i], ui);
449 let decayed_health = f64::from(1.0 - health.maximum() / health.base_max());
451 if decayed_health > 0.0 {
452 let decay_bar_len = 148.0 * decayed_health;
453 Image::new(self.imgs.bar_content)
454 .w_h(decay_bar_len, 22.0)
455 .color(Some(QUALITY_EPIC))
456 .top_right_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0)
457 .set(state.ids.member_health_decay[i], ui);
458 }
459 if health.is_dead {
460 Text::new(&self.localized_strings.get_msg("hud-group-dead"))
462 .mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0)
463 .font_size(20)
464 .font_id(self.fonts.cyri.conrod_id)
465 .color(KILL_COLOR)
466 .set(state.ids.dead_txt[i], ui);
467 } else {
468 let txt = format!(
470 "{}/{}",
471 health.current().round() as u32,
472 health.maximum().round() as u32,
473 );
474 let font_size = match health.maximum() {
476 x if (0.0..100.0).contains(&x) => 14,
477 x if (100.0..=1000.0).contains(&x) => 13,
478 x if (1000.0..=10000.0).contains(&x) => 12,
479 _ => 11,
480 };
481 let txt_offset = match health.maximum() {
483 x if (0.0..=100.0).contains(&x) => 4.0,
484 x if (100.0..=1000.0).contains(&x) => 4.5,
485 x if (1000.0..=10000.0).contains(&x) => 5.0,
486 _ => 5.5,
487 };
488 Text::new(&txt)
489 .mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset)
490 .font_size(font_size)
491 .font_id(self.fonts.cyri.conrod_id)
492 .color(Color::Rgba(1.0, 1.0, 1.0, 0.5))
493 .set(state.ids.health_txt[i], ui);
494 };
495
496 Image::new(self.imgs.member_frame)
498 .w_h(152.0, 36.0)
499 .middle_of(state.ids.member_panels_bg[i])
500 .color(Some(UI_HIGHLIGHT_0))
501 .set(state.ids.member_panels_frame[i], ui);
502
503 let indicator_col = cr_color(combat_rating);
504 Image::new(self.imgs.combat_rating_ico_shadow)
505 .w_h(18.0, 18.0)
506 .top_left_with_margins_on(state.ids.member_panels_frame[i], -20.0, 2.0)
507 .color(Some(indicator_col))
508 .set(state.ids.combat_rating_indicators[i], ui);
509 if hardcore.is_some() {
510 Image::new(self.imgs.hardcore)
511 .w_h(18.0, 18.0)
512 .top_left_with_margins_on(state.ids.member_panels_frame[i], -20.0, 22.0)
513 .set(state.ids.hardcore_indicators[i], ui);
514 }
515 Text::new(&char_name)
517 .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 22.0 + hardcore.map_or(0.0, |_| 20.0))
518 .font_size(20)
519 .font_id(self.fonts.cyri.conrod_id)
520 .color(BLACK)
521 .w(300.0) .set(state.ids.member_panels_txt_bg[i], ui);
523 Text::new(&char_name)
524 .bottom_left_with_margins_on(state.ids.member_panels_txt_bg[i], 2.0, 2.0)
525 .font_size(20)
526 .font_id(self.fonts.cyri.conrod_id)
527 .color(if is_leader { ERROR_COLOR } else { GROUP_COLOR })
528 .w(300.0) .set(state.ids.member_panels_txt[i], ui);
530 let stam_perc = energy.current() / energy.maximum();
531 Image::new(self.imgs.bar_content)
533 .w_h(100.0 * f64::from(stam_perc), 8.0)
534 .color(Some(STAMINA_COLOR))
535 .top_left_with_margins_on(state.ids.member_panels_bg[i], 26.0, 2.0)
536 .set(state.ids.member_energy[i], ui);
537 if let Some(buffs) = buffs {
538 let buff_icons = BuffIcon::icons_vec(buffs, stance);
539 let buff_count = buff_icons.len().min(11);
541 total_buff_count += buff_count;
542 let gen = &mut ui.widget_id_generator();
543 if state.ids.buffs.len() < total_buff_count {
544 state.update(|state| state.ids.buffs.resize(total_buff_count, gen));
545 }
546 if state.ids.buff_timers.len() < total_buff_count {
547 state.update(|state| {
548 state.ids.buff_timers.resize(total_buff_count, gen)
549 });
550 }
551 let mut prev_id = None;
553 state
554 .ids
555 .buffs
556 .iter()
557 .copied()
558 .zip(state.ids.buff_timers.iter().copied())
559 .skip(total_buff_count - buff_count)
560 .zip(buff_icons.iter())
561 .for_each(|((id, timer_id), buff)| {
562 let max_duration = buff.kind.max_duration();
563 let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
564 let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
565 let current_duration = buff.end_time.map(|end| end - self.time.0);
566 let duration_percentage = current_duration.map_or(1000.0, |cur| {
567 max_duration.map_or(1000.0, |max| cur / max.0 * 1000.0)
568 }) as u32; let buff_img = buff.kind.image(self.imgs);
570 let buff_widget = Image::new(buff_img).w_h(15.0, 15.0);
571 let buff_widget = if let Some(id) = prev_id {
572 buff_widget.right_from(id, 1.0)
573 } else {
574 buff_widget.bottom_left_with_margins_on(
575 state.ids.member_panels_frame[i],
576 -16.0,
577 1.0,
578 )
579 };
580 prev_id = Some(id);
581 buff_widget
582 .color(if current_duration.is_some_and(|cur| cur < 10.0) {
583 Some(pulsating_col)
584 } else {
585 Some(norm_col)
586 })
587 .set(id, ui);
588 let (title, desc_txt) =
590 buff.kind.title_description(localized_strings);
591 let remaining_time = buff.get_buff_time(*self.time);
592 let desc = format!("{}\n\n{}", desc_txt, remaining_time);
593 Image::new(match duration_percentage as u64 {
594 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,
603 })
604 .w_h(15.0, 15.0)
605 .middle_of(id)
606 .with_tooltip(
607 self.tooltip_manager,
608 &title,
609 &desc,
610 &buffs_tooltip,
611 if buff.is_buff {
612 BUFF_COLOR
613 } else {
614 DEBUFF_COLOR
615 },
616 )
617 .set(timer_id, ui);
618 });
619 } else {
620 Text::new(&self.localized_strings.get_content(&stats.name))
622 .top_left_with_margins_on(state.ids.member_panels_frame[i], -22.0, 0.0)
623 .font_size(20)
624 .font_id(self.fonts.cyri.conrod_id)
625 .color(GROUP_COLOR)
626 .set(state.ids.member_panels_txt[i], ui);
627 let back = if i == 0 {
628 Image::new(self.imgs.member_bg)
629 .top_left_with_margins_on(ui.window, offset, 20.0)
630 } else {
631 Image::new(self.imgs.member_bg)
632 .down_from(state.ids.member_panels_bg[i - 1], 40.0)
633 };
634 back.w_h(152.0, 36.0)
635 .color(Some(TEXT_COLOR))
636 .set(state.ids.member_panels_bg[i], ui);
637 Image::new(self.imgs.member_frame)
639 .w_h(152.0, 36.0)
640 .middle_of(state.ids.member_panels_bg[i])
641 .color(Some(UI_HIGHLIGHT_0))
642 .set(state.ids.member_panels_frame[i], ui);
643 Text::new(&self.localized_strings.get_msg("hud-group-out_of_range"))
645 .mid_top_with_margin_on(state.ids.member_panels_bg[i], 3.0)
646 .font_size(16)
647 .font_id(self.fonts.cyri.conrod_id)
648 .color(TEXT_COLOR)
649 .set(state.ids.dead_txt[i], ui);
650 }
651 }
652 }
653
654 if self.show.group_menu {
655 let selected = state.selected_member;
656 if Button::image(self.imgs.button) .w_h(90.0, 22.0)
658 .top_right_with_margins_on(state.ids.bg, 5.0, 5.0)
659 .hover_image(self.imgs.button)
660 .press_image(self.imgs.button)
661 .label_color(TEXT_COLOR_GREY)
662 .image_color(TEXT_COLOR_GREY)
663 .label(&self.localized_strings.get_msg("hud-group-add_friend"))
664 .label_font_id(self.fonts.cyri.conrod_id)
665 .label_font_size(self.fonts.cyri.scale(10))
666 .set(state.ids.btn_friend, ui)
667 .was_clicked()
668 {};
669 if Button::image(self.imgs.button)
670 .w_h(90.0, 22.0)
671 .bottom_right_with_margins_on(state.ids.bg, 5.0, 5.0)
672 .hover_image(self.imgs.button_hover)
673 .press_image(self.imgs.button_press)
674 .label(&self.localized_strings.get_msg("hud-group-leave"))
675 .label_color(TEXT_COLOR)
676 .label_font_id(self.fonts.cyri.conrod_id)
677 .label_font_size(self.fonts.cyri.scale(10))
678 .set(state.ids.btn_leave, ui)
679 .was_clicked()
680 {
681 self.show.group_menu = false;
682 self.show.group = !self.show.group;
683 events.push(Event::LeaveGroup);
684 };
685 if my_uid == Some(leader) {
687 if Button::image(self.imgs.button)
688 .w_h(90.0, 22.0)
689 .mid_bottom_with_margin_on(state.ids.btn_friend, -27.0)
690 .hover_image(self.imgs.button_hover)
691 .press_image(self.imgs.button_press)
692 .label(&self.localized_strings.get_msg("hud-group-assign_leader"))
693 .label_color(if state.selected_member.is_some() {
694 TEXT_COLOR
695 } else {
696 TEXT_COLOR_GREY
697 })
698 .label_font_id(self.fonts.cyri.conrod_id)
699 .label_font_size(self.fonts.cyri.scale(10))
700 .set(state.ids.btn_leader, ui)
701 .was_clicked()
702 {
703 if let Some(uid) = selected {
704 events.push(Event::AssignLeader(uid));
705 state.update(|s| {
706 s.selected_member = None;
707 });
708 }
709 };
710 if Button::image(self.imgs.button)
711 .w_h(90.0, 22.0)
712 .mid_bottom_with_margin_on(state.ids.btn_leader, -27.0)
713 .hover_image(self.imgs.button)
714 .press_image(self.imgs.button)
715 .label(&self.localized_strings.get_msg("hud-group-link_group"))
716 .hover_image(self.imgs.button)
717 .press_image(self.imgs.button)
718 .label_color(TEXT_COLOR_GREY)
719 .image_color(TEXT_COLOR_GREY)
720 .label_font_id(self.fonts.cyri.conrod_id)
721 .label_font_size(self.fonts.cyri.scale(10))
722 .set(state.ids.btn_link, ui)
723 .was_clicked()
724 {};
725 if Button::image(self.imgs.button)
726 .w_h(90.0, 22.0)
727 .mid_bottom_with_margin_on(state.ids.btn_link, -27.0)
728 .down_from(state.ids.btn_link, 5.0)
729 .hover_image(self.imgs.button_hover)
730 .press_image(self.imgs.button_press)
731 .label(&self.localized_strings.get_msg("hud-group-kick"))
732 .label_color(if state.selected_member.is_some() {
733 TEXT_COLOR
734 } else {
735 TEXT_COLOR_GREY
736 })
737 .label_font_id(self.fonts.cyri.conrod_id)
738 .label_font_size(self.fonts.cyri.scale(10))
739 .set(state.ids.btn_kick, ui)
740 .was_clicked()
741 {
742 if let Some(uid) = selected {
743 events.push(Event::Kick(uid));
744 state.update(|s| {
745 s.selected_member = None;
746 });
747 }
748 };
749 }
750 let group_size = group_members.len();
753 if state.ids.members.len() < group_size {
754 state.update(|s| {
755 s.ids
756 .members
757 .resize(group_size, &mut ui.widget_id_generator())
758 })
759 }
760 Rectangle::fill_with([110.0, 135.0], color::TRANSPARENT)
762 .top_left_with_margins_on(state.ids.bg, 5.0, 5.0)
763 .crop_kids()
764 .scroll_kids_vertically()
765 .set(state.ids.scroll_area, ui);
766 Scrollbar::y_axis(state.ids.scroll_area)
767 .thickness(5.0)
768 .rgba(0.33, 0.33, 0.33, 1.0)
769 .set(state.ids.scrollbar, ui);
770 for (i, &uid) in group_members.iter().copied().enumerate() {
772 let selected = state.selected_member == Some(uid);
773 let char_name = uid_to_name_text(uid, self.client);
774 if Button::image(if selected {
776 self.imgs.selection
777 } else {
778 self.imgs.nothing
779 })
780 .w_h(100.0, 22.0)
781 .and(|w| {
782 if i == 0 {
783 w.top_left_with_margins_on(state.ids.scroll_area, 5.0, 0.0)
784 } else {
785 w.down_from(state.ids.members[i - 1], 5.0)
786 }
787 })
788 .hover_image(self.imgs.selection_hover)
789 .press_image(self.imgs.selection_press)
790 .image_color(color::rgba(1.0, 0.82, 0.27, 1.0))
791 .crop_kids()
792 .label_x(Relative::Place(Place::Start(Some(4.0))))
793 .label(&char_name)
794 .label_color(if uid == leader {
795 ERROR_COLOR
796 } else {
797 TEXT_COLOR
798 })
799 .label_font_id(self.fonts.cyri.conrod_id)
800 .label_font_size(self.fonts.cyri.scale(12))
801 .set(state.ids.members[i], ui)
802 .was_clicked()
803 {
804 if Some(uid) != my_uid {
806 state.update(|s| {
808 s.selected_member = if selected { None } else { Some(uid) }
809 });
810 }
811 };
812 }
813 }
817 }
818 if let Some((invite_uid, _, _, kind)) = open_invite {
819 self.show.group = true; let name = uid_to_name_text(invite_uid, self.client);
824 let invite_text = match kind {
825 InviteKind::Group => self.localized_strings.get_msg_ctx(
826 "hud-group-invite_to_join",
827 &i18n::fluent_args! {
828 "name" => name,
829 },
830 ),
831 InviteKind::Trade => self.localized_strings.get_msg_ctx(
832 "hud-group-invite_to_trade",
833 &i18n::fluent_args! {
834 "name" => &name,
835 },
836 ),
837 };
838 Text::new(&invite_text)
839 .mid_top_with_margin_on(state.ids.bg, 5.0)
840 .font_size(12)
841 .font_id(self.fonts.cyri.conrod_id)
842 .color(TEXT_COLOR)
843 .w(165.0) .set(state.ids.title, ui);
845 let accept_key = self
847 .settings
848 .controls
849 .get_binding(GameInput::AcceptGroupInvite)
850 .map_or_else(|| "".into(), |key| key.display_string());
851 if Button::image(self.imgs.button)
852 .w_h(90.0, 22.0)
853 .bottom_left_with_margins_on(state.ids.bg, 15.0, 15.0)
854 .hover_image(self.imgs.button_hover)
855 .press_image(self.imgs.button_press)
856 .label(&format!(
857 "[{}] {}",
858 &accept_key,
859 self.localized_strings.get_msg("common-accept")
860 ))
861 .label_color(TEXT_COLOR)
862 .label_font_id(self.fonts.cyri.conrod_id)
863 .label_font_size(self.fonts.cyri.scale(12))
864 .set(state.ids.btn_accept, ui)
865 .was_clicked()
866 {
867 events.push(Event::Accept);
868 self.show.group_menu = true;
869 };
870 let decline_key = self
872 .settings
873 .controls
874 .get_binding(GameInput::DeclineGroupInvite)
875 .map_or_else(|| "".into(), |key| key.display_string());
876 if Button::image(self.imgs.button)
877 .w_h(90.0, 22.0)
878 .bottom_right_with_margins_on(state.ids.bg, 15.0, 15.0)
879 .hover_image(self.imgs.button_hover)
880 .press_image(self.imgs.button_press)
881 .label(&format!(
882 "[{}] {}",
883 &decline_key,
884 self.localized_strings.get_msg("common-decline")
885 ))
886 .label_color(TEXT_COLOR)
887 .label_font_id(self.fonts.cyri.conrod_id)
888 .label_font_size(self.fonts.cyri.scale(12))
889 .set(state.ids.btn_decline, ui)
890 .was_clicked()
891 {
892 events.push(Event::Decline);
893 };
894 }
895
896 events
897 }
898}