1+ /*
2+ * Copyright (c) 2024 PlayEveryWare
3+ *
4+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5+ * of this software and associated documentation files (the "Software"), to deal
6+ * in the Software without restriction, including without limitation the rights
7+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+ * copies of the Software, and to permit persons to whom the Software is
9+ * furnished to do so, subject to the following conditions:
10+ *
11+ * The above copyright notice and this permission notice shall be included in all
12+ * copies or substantial portions of the Software.
13+ *
14+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+ * SOFTWARE.
21+ */
22+
23+
24+ namespace PlayEveryWare . EpicOnlineServices . Editor . Windows
25+ {
26+ using PlayEveryWare . EpicOnlineServices . Editor . Utility ;
27+ using System ;
28+ using UnityEditor ;
29+ using UnityEngine ;
30+
31+ /// <summary>
32+ /// Represents an editor window that is modal in nature - it is designed to
33+ /// only provide input for a single value, and not be dismissed from focus
34+ /// unless canceled or closed.
35+ /// </summary>
36+ /// <typeparam name="TInputType">
37+ /// The type of value that the modal window is designed to edit.
38+ /// </typeparam>
39+ public abstract class ModalEOSEditorWindow < TInputType > : EOSEditorWindow
40+ {
41+ /// <summary>
42+ /// The value being edited.
43+ /// </summary>
44+ protected TInputType _input ;
45+
46+ /// <summary>
47+ /// The prompt to display indicating what the value means.
48+ /// </summary>
49+ private string _inputPrompt ;
50+
51+ /// <summary>
52+ /// The prompt to display if the input is not valid.
53+ /// </summary>
54+ private string _errorPrompt ;
55+
56+ /// <summary>
57+ /// The action to take when the modal window is submitted.
58+ /// </summary>
59+ private Action < TInputType > _onSubmit ;
60+
61+ /// <summary>
62+ /// The function used to validate the input.
63+ /// </summary>
64+ private Func < TInputType , bool > _validateFunction ;
65+
66+ /// <summary>
67+ /// Flag that indicates whether the error prompt should be displayed.
68+ /// </summary>
69+ private bool _showError ;
70+
71+ // Keep a reference to prevent focus loss
72+ private static ModalEOSEditorWindow < TInputType > s_currentWindow ;
73+
74+ /// <summary>
75+ /// Constructs a noew modal eos editor window.
76+ /// </summary>
77+ /// <param name="windowTitle">Window title.</param>
78+ /// <param name="width">The fixed width of the window.</param>
79+ /// <param name="height">The fixed height of the window.</param>
80+ protected ModalEOSEditorWindow ( string windowTitle , float width , float height ) : base ( windowTitle , height , width , false )
81+ {
82+ }
83+
84+ /// <summary>
85+ /// Helper to schedule the showing of the modal window.
86+ /// </summary>
87+ /// <typeparam name="TWindowType">Type of value modal window is designed to edit.</typeparam>
88+ /// <param name="input">The value that the modal window provides editing of.</param>
89+ /// <param name="onSubmitCallback">The action that takes place when the modal window is submitted with a valid value.</param>
90+ /// <param name="validateFunction">Function used to validate the input.</param>
91+ /// <param name="inputPrompt">The prompt to display in the modal window.</param>
92+ /// <param name="errorPrompt">The error to display if the input value is invalid.</param>
93+ protected static void ScheduleShow < TWindowType > (
94+ TInputType input ,
95+ Action < TInputType > onSubmitCallback ,
96+ Func < TInputType , bool > validateFunction ,
97+ string inputPrompt ,
98+ string errorPrompt
99+ ) where TWindowType : ModalEOSEditorWindow < TInputType >
100+ {
101+ void ShowAndUnsubscribe ( )
102+ {
103+ // Unsubscribe first to ensure it runs only once
104+ EditorApplication . update -= ShowAndUnsubscribe ;
105+
106+ // Open the modal window
107+ ShowWindow < TWindowType > ( input , onSubmitCallback , validateFunction , inputPrompt , errorPrompt ) ;
108+ }
109+
110+ // Subscribe the delegate to execute on the next frame
111+ EditorApplication . update += ShowAndUnsubscribe ;
112+ }
113+
114+ public static void ShowWindow < TWindowType > (
115+ TInputType input ,
116+ Action < TInputType > onSubmitCallback ,
117+ Func < TInputType , bool > validateFunction ,
118+ string inputPrompt ,
119+ string errorPrompt ) where TWindowType : ModalEOSEditorWindow < TInputType >
120+ {
121+ if ( s_currentWindow != null )
122+ {
123+ s_currentWindow . Focus ( ) ;
124+ return ;
125+ }
126+
127+ ModalEOSEditorWindow < TInputType > window = CreateInstance < TWindowType > ( ) ;
128+ window . _input = input ;
129+ window . _inputPrompt = inputPrompt ;
130+ window . _onSubmit = onSubmitCallback ;
131+ window . _validateFunction = validateFunction ;
132+ window . _errorPrompt = errorPrompt ;
133+
134+ // Center the modal window relative to the parent window
135+ if ( focusedWindow != null )
136+ {
137+ Rect parentRect = focusedWindow . position ;
138+ float width = 300f ; // Width of the modal window
139+ float height = 150f ; // Height of the modal window
140+ window . position = new Rect (
141+ parentRect . x + ( parentRect . width - width ) / 2 ,
142+ parentRect . y + ( parentRect . height - height ) / 2 ,
143+ width ,
144+ height
145+ ) ;
146+ }
147+ else
148+ {
149+ // Default size and position if no parent is specified
150+ window . position = new Rect ( Screen . width / 2 - 150 , Screen . height / 2 - 75 , 300 , 150 ) ;
151+ }
152+
153+ window . ShowModalUtility ( ) ; // Makes it modal-like
154+ window . SetIsEmbedded ( false ) ;
155+
156+ s_currentWindow = window ;
157+ }
158+
159+ private void OnLostFocus ( )
160+ {
161+ // Refocus if the window loses focus
162+ Focus ( ) ;
163+ }
164+
165+ protected new void OnEnable ( )
166+ {
167+ // Overridden to remove base functionality.
168+ }
169+
170+ protected new void OnDisable ( )
171+ {
172+ // Overridden to remove base functionality.
173+ }
174+
175+ protected override void OnDestroy ( )
176+ {
177+ s_currentWindow = null ;
178+ base . OnDestroy ( ) ;
179+ }
180+
181+ /// <summary>
182+ /// Deriving modal window implementations should implement this function
183+ /// to render the contents of the input.
184+ /// </summary>
185+ protected abstract void RenderModalContents ( ) ;
186+
187+ protected override void RenderWindow ( )
188+ {
189+ bool shouldClose = false ;
190+
191+ // Render the prompt text
192+ EditorGUILayout . LabelField ( _inputPrompt , GUILayout . Width (
193+ GUIEditorUtility . MeasureLabelWidth ( _inputPrompt ) )
194+ ) ;
195+
196+ // Display error if it needs to be displayed.
197+ if ( _showError )
198+ {
199+ EditorGUILayout . HelpBox ( _errorPrompt , MessageType . Warning ) ;
200+ }
201+
202+ // Render the contents that are unique to the modal window implementation.
203+ RenderModalContents ( ) ;
204+
205+ EditorGUILayout . Space ( ) ;
206+
207+ GUILayout . BeginHorizontal ( ) ;
208+ if ( GUILayout . Button ( "Save" ) )
209+ {
210+ // Try to validate the value being submitted
211+ if ( _validateFunction ( _input ) )
212+ {
213+ // If successful, then call the submit action.
214+ _onSubmit ? . Invoke ( _input ) ;
215+ shouldClose = true ;
216+ }
217+ else
218+ {
219+ // Otherwise, turn on the flag that indicates the error should be displayed.
220+ _showError = true ;
221+ }
222+ }
223+
224+ GUI . SetNextControlName ( "CancelButton" ) ;
225+ if ( GUILayout . Button ( "Cancel" ) )
226+ {
227+ shouldClose = true ;
228+ }
229+ GUILayout . EndHorizontal ( ) ;
230+
231+ // Close if it should be.
232+ if ( shouldClose )
233+ {
234+ Close ( ) ;
235+ }
236+
237+ // Force focus back to the window
238+ if ( s_currentWindow != this )
239+ {
240+ Focus ( ) ;
241+ s_currentWindow = this ;
242+ }
243+ }
244+ }
245+ }
0 commit comments