9 protected const string HINT_BASE =
"<action name='%1' hideEmpty='1'/>";
10 protected const float CHANGE_CONTENT_OPACITY = 0.4;
11 protected const float ENTRY_ANIMATION_SCALE = 1.25;
12 protected const float ENTRY_ANIMATION_SPEED = 7.5;
14 [
Attribute(
"150", UIWidgets.Slider,
"Entries distance from menu center in pixels",
"0 1000 1")]
15 protected int m_iEntriesRadius;
18 protected float m_fSelectedIndicatorOffset;
21 [
Attribute(
desc:
"Widget name for menu base wrapper that hould all important parts of radial circle")]
22 protected string m_sBaseWidget;
24 [
Attribute(
"1",
desc:
"Adjust base size in callback based on invoker from SCR_RadialMenu")]
25 protected bool m_bForceSize;
27 [
Attribute(
"0",
desc:
"Skips the slower fade animation on entry perform")]
28 protected bool m_bFastFadeOnPerform;
30 [
Attribute(
desc:
"Widget name for storing entry separator widgets")]
31 protected string m_sDividersParent;
33 [
Attribute(
".edds",
desc:
"Texture used for creating dividing lines")]
34 protected ResourceName m_sDividerTexture;
36 [
Attribute(
"0", UIWidgets.Slider,
"Dividers distance from menu center in pixels",
"0 1000 1")]
37 protected int m_iDividersRadius;
40 protected int m_iDividerSize;
42 [
Attribute(
"1", UIWidgets.Slider,
"Dividers texture opacity",
"0 1 0.01")]
43 protected float m_fDividersOpacity;
45 [
Attribute(
desc:
"Widget name for storing segment widgets of each entry")]
46 protected string m_sSegmentsParent;
48 [
Attribute(
".layout",
desc:
"Layout used for creating segment. Serves for entry split and colorize",
params:
"layout")]
49 protected ResourceName m_sSegmentLayout;
51 [
Attribute(
desc:
"Image widget name for displaying where player is pointing to")]
52 protected string m_sSelectorLine;
55 protected string m_sSelectorArrow;
57 [
Attribute(
desc:
"Widget name for selection pointing innner selector")]
58 protected string m_sInnerSelector;
60 [
Attribute(
desc:
"Image widget name for highlighting currently selected entry")]
61 protected string m_sSelectedIndicator;
64 protected string m_sSelectedIndicatorFeedback;
66 [
Attribute(
desc:
"Text widget name for displaying name of currently selected entry")]
67 protected string m_sSelectedName;
69 [
Attribute(
desc:
"Rich text widget name for displaying key e.g. required for quick use of selected entry")]
70 protected string m_sActionHint;
72 [
Attribute(
desc:
"Text widget name for displaying entry description or hint")]
73 protected string m_sDescription;
75 [
Attribute(
desc:
"Widget name with breadcrumbs component to find reference for breadcrumbs")]
76 protected string m_sBreadCrumbs;
78 [
Attribute(
"ImgInnerBackground",
desc:
"Name of image widget for inner circle background")]
79 protected string m_sInnerBackround;
81 [
Attribute(
desc:
"Widget holding icon and shadow of crosshair")]
82 protected string m_sCrosshairWrap;
85 protected string m_sBlur;
88 protected string m_sHintWrapper;
90 [
Attribute(
"325", UIWidgets.Slider,
"Hint distance from center in pixels",
"0 500 1")]
91 protected float m_fHintRadius;
93 [
Attribute(
"1000", UIWidgets.Slider,
"Time to display hint in miliseconds",
"0 5000 1")]
94 protected int m_iHintDisplayDelay;
97 protected int m_iCategoryIconRadius;
100 protected ResourceName m_sNumberHintLayout;
102 [
Attribute(
desc:
"Widget name for storing number hints widgets of each entry")]
103 protected string m_sNumberHintParent;
105 [
Attribute(
"320", UIWidgets.Slider,
"Entry number hints distance from center in pixels",
"0 500 1")]
106 protected float m_fNumberHintRadius;
108 protected Widget m_wBase;
109 protected Widget m_wDividersParent;
110 protected ref array<Widget> m_aDividers = {};
111 protected Widget m_wSegmentsParent;
112 protected ref array<SCR_RadialMenuSegmentComponent> m_aSegments = {};
114 protected Widget m_wNumberHints;
115 protected ref array<SCR_RadialMenuNumberHintComponent> m_aNumberHints = {};
117 protected ImageWidget m_wSelectorArrow;
118 protected ImageWidget m_wInnerSelector;
119 protected ImageWidget m_wSelectorLine;
120 protected ImageWidget m_wSelectedIndicator;
121 protected ImageWidget m_wSelectedIndicatorFeedback;
122 protected TextWidget m_wSelectedName;
123 protected TextWidget m_wActionHint;
124 protected TextWidget m_wDescription;
125 protected ImageWidget m_wInnerBackround;
126 protected Widget m_wCrosshairWrap;
127 protected BlurWidget m_wBlur;
129 protected Widget m_wHintWrapper;
134 protected float m_fSelectorArrowRadius;
137 protected float m_fDynamicEntriesRadius;
138 protected float m_fDynamicDividerSize;
139 protected float m_fDynamicDividersRadius;
140 protected float m_SizeRatio;
142 protected int m_iPrevCategoryLevel = 0;
150 override void DisplayStartDraw(IEntity owner)
152 super.DisplayStartDraw(owner);
156 m_wDividersParent =
GetRootWidget().FindAnyWidget(m_sDividersParent);
157 m_wSegmentsParent =
GetRootWidget().FindAnyWidget(m_sSegmentsParent);
159 m_wSelectorArrow = ImageWidget.Cast(
GetRootWidget().FindAnyWidget(m_sSelectorArrow));
160 m_wInnerSelector = ImageWidget.Cast(
GetRootWidget().FindAnyWidget(m_sInnerSelector));
161 m_wSelectorLine = ImageWidget.Cast(
GetRootWidget().FindAnyWidget(m_sSelectorLine));
162 m_wSelectedIndicator = ImageWidget.Cast(
GetRootWidget().FindAnyWidget(m_sSelectedIndicator));
163 m_wSelectedIndicatorFeedback = ImageWidget.Cast(
GetRootWidget().FindAnyWidget(m_sSelectedIndicatorFeedback));
165 m_wSelectedName = TextWidget.Cast(
GetRootWidget().FindAnyWidget(m_sSelectedName));
168 m_wActionHint = TextWidget.Cast(
GetRootWidget().FindAnyWidget(m_sActionHint));
170 m_wInnerBackround = ImageWidget.Cast(
GetRootWidget().FindAnyWidget(m_sInnerBackround));
171 m_wCrosshairWrap =
GetRootWidget().FindAnyWidget(m_sCrosshairWrap);
172 m_wBlur = BlurWidget.Cast(
GetRootWidget().FindAnyWidget(m_sBlur));
174 m_wHintWrapper =
GetRootWidget().FindAnyWidget(m_sHintWrapper);
176 m_wNumberHints =
GetRootWidget().FindAnyWidget(m_sNumberHintParent);
179 Widget breadCrumbs =
GetRootWidget().FindAnyWidget(m_sBreadCrumbs);
184 if (m_wSelectedIndicator)
185 m_wSelectedIndicator.SetVisible(
false);
187 if (m_wSelectedIndicatorFeedback)
189 m_wSelectedIndicatorFeedback.SetVisible(
true);
190 m_wSelectedIndicatorFeedback.SetOpacity(0);
191 AnimateWidget.StopAnimation(m_wSelectedIndicatorFeedback, WidgetAnimationOpacity);
195 m_wBlur.SetOpacity(0);
199 m_wDescription.SetVisible(
false);
202 m_wActionHint.SetVisible(
false);
206 override void DisplayStopDraw(IEntity owner)
208 super.DisplayStopDraw(owner);
212 m_RadialMenu.GetOnDisplaySizeChange().Remove(OnDisplaySizeChange);
213 m_RadialMenu.GetOnSetActionHint().Remove(OnSetActionHint);
218 override void DisplayUpdate(IEntity owner,
float timeSlice)
226 m_wNumberHints.SetVisible(
236 m_Menu.GetOnOpenCategory().Remove(OnMenuOpenCategory);
240 m_RadialMenu.GetOnDisplaySizeChange().Remove(OnDisplaySizeChange);
241 m_RadialMenu.GetOnSetActionHint().Remove(OnSetActionHint);
244 super.SetupMenu(menu);
250 m_RadialMenu.GetOnDisplaySizeChange().Insert(OnDisplaySizeChange);
251 m_RadialMenu.GetOnSetActionHint().Insert(OnSetActionHint);
252 m_RadialMenu.GetOnOpenCategory().Insert(OnMenuOpenCategory);
256 override protected void OnMenuOpen()
259 SetupSelectionVisuals();
261 if (m_wSelectedIndicatorFeedback)
262 m_wSelectedIndicatorFeedback.SetOpacity(0);
271 ShowCrosshair(radialInputs.m_bShowCrosshair);
272 ShowInnerBackground(radialInputs.m_bShowInnerBackground);
277 override protected void OnShownFinished(Widget w,
float targetOpacity, WidgetAnimationOpacity anim =
null)
279 super.OnShownFinished(w, targetOpacity, anim);
281 if (m_Menu && m_Menu.IsOpened())
286 protected void SetupMenuCenter()
293 m_wBase.GetScreenPos(posX, posY);
296 m_wBase.GetScreenSize(sizeX, sizeY);
298 float x = posX + sizeX * 0.5;
299 float y = posY + sizeY * 0.5;
301 GetGame().GetInputManager().SetCursorPosition(x, y);
305 override protected void OnMenuClose()
310 if (
m_RadialMenu.GetEntryPerformed() && !m_bFastFadeOnPerform)
311 Show(
false,
UIConstants.FADE_RATE_DEFAULT, EAnimationCurve.EASE_IN_EXPO);
317 override protected void OnMenuEntriesUpdate(
SCR_SelectionMenu menu, array<ref SCR_SelectionMenuEntry> entries)
319 if (!m_Menu.IsOpened())
322 super.OnMenuEntriesUpdate(menu, entries);
324 SetupSelectionVisuals();
328 override protected void SetupEntryWidget(notnull
SCR_SelectionMenuEntry entry, notnull Widget widget,
int id)
330 float angle = SetupFrameSlotPosition(widget,
id, m_fDynamicEntriesRadius,
m_RadialMenu.GetEntriesAngleDistance());
340 float entrySize = iconEntryCmp.GetOriginalSize() * m_SizeRatio;
341 iconEntryCmp.SetLayoutSize(entrySize);
345 CreateEntrySegment(entry, widget,
id);
349 CreateNumberHint(
id);
354 protected void CreateNumberHint(
int id)
360 Widget hint =
GetGame().GetWorkspace().CreateWidgets(m_sNumberHintLayout, m_wNumberHints);
365 float angle =
m_RadialMenu.GetEntriesAngleDistance() *
id * Math.DEG2RAD;
366 vector vec = GetPointOnCircle(m_fNumberHintRadius, angle);
367 FrameSlot.SetPos(hint, vec[0], vec[1]);
374 hintCmp.SetMessages((
id + 1).ToString(),
"");
375 m_aNumberHints.Insert(hintCmp);
383 if (m_wSelectedIndicator)
385 m_wSelectedIndicator.SetVisible(entry !=
null && entry.IsEnabled());
387 float angle =
m_RadialMenu.GetEntriesAngleDistance() * id;
388 float offset = m_fSelectedIndicatorOffset -
m_RadialMenu.GetEntriesAngleDistance() * 0.5;
389 m_wSelectedIndicator.SetRotation(angle + offset);
391 if (m_wSelectedIndicatorFeedback)
392 m_wSelectedIndicatorFeedback.SetRotation(angle + offset);
398 m_wSelectedName.SetVisible(entry !=
null);
401 m_wSelectedName.SetText(entry.GetName());
405 if (m_aSegments.IsIndexValid(m_iLastSelectedId))
407 if (m_LastSelectedEntry && m_LastSelectedEntry.IsEnabled())
408 m_aSegments[m_iLastSelectedId].GetBackgroundWidget().SetVisible(
true);
411 if (m_aSegments.IsIndexValid(
id))
412 m_aSegments[id].GetBackgroundWidget().SetVisible(
false);
415 if (m_aNumberHints.IsIndexValid(m_iLastSelectedId))
416 m_aNumberHints[m_iLastSelectedId].Highlight(
false);
418 if (m_aNumberHints.IsIndexValid(
id))
419 m_aNumberHints[id].Highlight(
true);
423 m_LastSelectedEntry = entry;
424 m_iLastSelectedId = id;
429 m_wDescription.SetVisible(entry && !entry.GetDescription().IsEmpty());
432 m_wDescription.SetText(entry.GetDescription());
437 m_wActionHint.SetVisible(entry && !m_wActionHint.GetText().IsEmpty());
440 SetupEntryHint(
"",
id);
446 if (m_wSelectedIndicatorFeedback)
448 m_wSelectedIndicatorFeedback.SetOpacity(0);
450 if (!AnimateWidget.IsAnimating(m_wSelectedIndicatorFeedback))
451 AnimateWidget.Opacity(m_wSelectedIndicatorFeedback, CHANGE_CONTENT_OPACITY,
UIConstants.FADE_RATE_FAST);
455 if (!entry || !entry.GetEntryComponent())
458 Widget entryW = entry.GetEntryComponent().GetRootWidget().GetChildren();
461 if (AnimateWidget.IsAnimating(entryW))
464 float originalSize = entry.GetEntryComponent().GetAdjustedSize();
465 float size[2] = {originalSize * ENTRY_ANIMATION_SCALE, originalSize * ENTRY_ANIMATION_SCALE};
467 WidgetAnimationFrameSize anim = AnimateWidget.Size(entryW, size, ENTRY_ANIMATION_SPEED);
468 anim.SetRepeat(
true);
469 anim.GetOnCycleCompleted().Insert(OnEntrySizeAnimCycleCompleted);
473 protected void OnEntrySizeAnimCycleCompleted(WidgetAnimationBase anim)
475 anim.SetRepeat(
false);
478 AnimateWidget.Opacity(m_wSelectedIndicatorFeedback, 0,
UIConstants.FADE_RATE_FAST);
480 anim.GetOnCycleCompleted().Remove(OnEntrySizeAnimCycleCompleted);
485 override protected void ClearEntryWidgets()
487 super.ClearEntryWidgets();
489 if (m_aSegments.Count() != m_aDividers.Count())
491 DebugPrint(
"ClearEntryWidgets",
"Different entries");
495 for (
int i = 0, count = m_aSegments.Count(); i < count; i++)
497 m_aSegments[i].GetRootWidget().RemoveFromHierarchy();
498 m_aDividers[i].RemoveFromHierarchy();
500 if (m_aNumberHints.IsIndexValid(i))
501 m_aNumberHints[i].GetRoot().RemoveFromHierarchy();
506 m_aNumberHints.Clear();
515 protected vector GetPointOnCircle(
float radius,
float degrees)
519 radius * Math.Cos(degrees - 0.5 * Math.PI),
520 radius * Math.Sin(degrees - 0.5 * Math.PI),
525 protected void VisualizeSelection(
float selectionAngle)
528 if (m_wSelectorArrow)
529 m_wSelectorArrow.SetVisible(!
m_RadialMenu.IsPointingToCenter());
531 if (m_wInnerSelector)
532 m_wInnerSelector.SetVisible(!
m_RadialMenu.IsPointingToCenter());
534 m_wSelectorLine.SetVisible(!
m_RadialMenu.IsPointingToCenter());
535 m_wSelectorLine.SetRotation(selectionAngle - 180);
538 if (!m_wSelectorArrow)
541 if (m_fSelectorArrowRadius == 0 && m_wSelectorArrow)
542 m_fSelectorArrowRadius = FrameSlot.GetPosY(m_wSelectorArrow) * m_SizeRatio;
544 if (m_fSelectorArrowRadius == 0)
548 vector vec = GetPointOnCircle(-m_fSelectorArrowRadius, selectionAngle * Math.DEG2RAD);
549 FrameSlot.SetPos(m_wSelectorArrow, vec[0], vec[1]);
551 m_wSelectorArrow.SetRotation(selectionAngle);
553 if (m_wInnerSelector)
555 m_wInnerSelector.SetRotation(selectionAngle);
561 protected float SetupFrameSlotPosition(out notnull Widget widget,
int id,
float distance,
float angle,
float angleOffset = 0)
564 float degs = angle *
id + angleOffset;
565 vector point = GetPointOnCircle(
distance, degs * Math.DEG2RAD);
568 FrameSlot.SetPos(widget, point[0], point[1]);
569 FrameSlot.SetAlignment(widget, 0.5, 0.5);
570 FrameSlot.SetAnchorMin(widget, 0.5, 0.5);
571 FrameSlot.SetAnchorMax(widget, 0.5, 0.5);
580 if (!m_wSegmentsParent || m_sSegmentLayout.IsEmpty())
582 DebugPrint(
"CreateEntrySegment",
"Can't create segments!");
586 Widget segment =
GetGame().GetWorkspace().CreateWidgets(m_sSegmentLayout, m_wSegmentsParent);
589 float angleDist =
m_RadialMenu.GetEntriesAngleDistance();
590 float range = angleDist / 360;
592 float rot = angleDist *
id - angleDist * 0.5;
595 segmentCmp.SetupSegment(range, angleDist *
id, isCategory);
598 m_aSegments.Insert(segmentCmp);
601 if (m_wDividersParent && !m_sDividerTexture.IsEmpty())
610 VisualizeEnableEntry(comp,
id, entry.IsEnabled());
615 protected void CreateDivider(
int id)
617 if (!m_wDividersParent || m_sDividerTexture.IsEmpty())
619 DebugPrint(
"CreateDivider",
"Can't create segments!");
623 Widget divider =
GetGame().GetWorkspace().CreateWidget(WidgetType.ImageWidgetTypeID,
624 WidgetFlags.VISIBLE | WidgetFlags.STRETCH | WidgetFlags.BLEND | WidgetFlags.INHERIT_CLIPPING,
625 Color.FromInt(Color.WHITE),
631 ImageWidget imgDivider = ImageWidget.Cast(divider);
633 imgDivider.LoadImageTexture(0, m_sDividerTexture);
634 imgDivider.SetOpacity(m_fDividersOpacity);
635 FrameSlot.SetSize(divider, m_fDynamicDividerSize, m_fDynamicDividerSize);
637 float angleDist =
m_RadialMenu.GetEntriesAngleDistance();
638 float angle = SetupFrameSlotPosition(divider,
id, m_fDynamicDividersRadius, angleDist, angleDist * 0.5);
640 imgDivider.SetRotation(angle);
643 m_aDividers.Insert(divider)
647 protected void SetupEntryHint(
string text,
int id)
652 m_wHintWrapper.SetOpacity(0);
653 AnimateWidget.StopAnimation(m_wHintWrapper, WidgetAnimationOpacity);
655 GetGame().GetCallqueue().Remove(DisplayEntryHint);
658 GetGame().GetCallqueue().CallLater(DisplayEntryHint, m_iHintDisplayDelay);
661 float angle =
m_RadialMenu.GetEntriesAngleDistance() *
id * Math.DEG2RAD;
662 vector vec = GetPointOnCircle(m_fHintRadius, angle);
664 float width = FrameSlot.GetSizeX(m_wHintWrapper);
665 vec[0] = vec[0] + Math.Sin(angle) * width * 0.5;
668 FrameSlot.SetPos(m_wHintWrapper, vec[0], vec[1]);
672 protected void DisplayEntryHint()
674 AnimateWidget.Opacity(m_wHintWrapper, 1,
UIConstants.FADE_RATE_DEFAULT);
680 if (!m_aSegments.IsIndexValid(
id))
683 m_aSegments[id].GetBackgroundWidget().SetVisible(enable);
684 entry.SetEnabled(enable);
689 protected void OnDisplaySizeChange(
SCR_RadialMenu menu,
float size)
695 m_SizeRatio = size / menu.SIZE_LARGE;
698 m_fDynamicEntriesRadius = m_iEntriesRadius * m_SizeRatio;
699 m_fDynamicDividerSize = m_iDividerSize * m_SizeRatio;
700 m_fDynamicDividersRadius = m_iDividersRadius * m_SizeRatio;
703 FrameSlot.SetSize(m_wBase, size, size);
708 protected void OnSetActionHint(
SCR_RadialMenu menu,
string action)
711 if (!action.IsEmpty())
712 m_wActionHint.SetTextFormat(HINT_BASE, action);
714 m_wActionHint.SetTextFormat(
string.Empty);
716 m_wActionHint.SetVisible(
m_RadialMenu.GetSelectionEntry() && !m_wActionHint.GetText().IsEmpty());
725 m_BreadCrumbs.Clear();
727 m_iPrevCategoryLevel = 0;
734 if (m_iPrevCategoryLevel < level)
735 m_BreadCrumbs.AddBreadCrumbElement(
category);
737 m_BreadCrumbs.RemoveLastBreadCrumbElement();
741 m_iPrevCategoryLevel = level;
744 GetGame().GetCallqueue().CallLater(LateMenuEntriesUpdate, 1000 /
UIConstants.FADE_RATE_SUPER_FAST);
747 m_wRoot.SetOpacity(CHANGE_CONTENT_OPACITY);
750 m_wBlur.SetOpacity(1);
754 protected void LateMenuEntriesUpdate()
756 SetupSelectionVisuals();
762 m_wBlur.SetOpacity(0);
771 m_BreadCrumbs.Clear();
773 m_iPrevCategoryLevel = 0;
780 if (m_iPrevCategoryLevel < level)
781 m_BreadCrumbs.AddBreadCrumbElement(
category);
783 m_BreadCrumbs.RemoveLastBreadCrumbElement();
787 m_iPrevCategoryLevel = level;
793 protected void SetupSelectionVisuals()
795 if (!m_wSelectedIndicator || !m_wSelectorLine)
799 float range =
m_RadialMenu.GetEntriesAngleDistance() / 360;
801 m_wSelectedIndicator.SetMaskProgress(range);
803 m_wSelectorLine.SetMaskRange(range * 0.5);
804 m_wSelectorLine.SetMaskTransitionWidth(range * 0.5);
806 if (m_wSelectedIndicatorFeedback)
807 m_wSelectedIndicatorFeedback.SetMaskProgress(range);
811 void ShowInnerBackground(
bool show)
813 if (m_wInnerBackround)
814 m_wInnerBackround.SetVisible(show);
818 void ShowCrosshair(
bool show)
820 if (m_wCrosshairWrap)
821 m_wCrosshairWrap.SetVisible(show);