1
0

InstrumentMenu.xaml.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. using System.IO;
  2. using System.Numerics;
  3. using System.Threading.Tasks;
  4. using Content.Client.Interactable;
  5. using Content.Shared.ActionBlocker;
  6. using Robust.Client.AutoGenerated;
  7. using Robust.Client.Player;
  8. using Robust.Client.UserInterface;
  9. using Robust.Client.UserInterface.CustomControls;
  10. using Robust.Client.UserInterface.XAML;
  11. using Robust.Shared.Containers;
  12. using Robust.Shared.Input;
  13. using Robust.Shared.Timing;
  14. using static Robust.Client.UserInterface.Controls.BaseButton;
  15. using Range = Robust.Client.UserInterface.Controls.Range;
  16. namespace Content.Client.Instruments.UI
  17. {
  18. [GenerateTypedNameReferences]
  19. public sealed partial class InstrumentMenu : DefaultWindow
  20. {
  21. [Dependency] private readonly IEntityManager _entManager = default!;
  22. [Dependency] private readonly IFileDialogManager _dialogs = default!;
  23. [Dependency] private readonly IPlayerManager _player = default!;
  24. private bool _isMidiFileDialogueWindowOpen;
  25. public event Action? OnOpenBand;
  26. public event Action? OnOpenChannels;
  27. public event Action? OnCloseBands;
  28. public event Action? OnCloseChannels;
  29. public EntityUid Entity;
  30. public InstrumentMenu()
  31. {
  32. RobustXamlLoader.Load(this);
  33. IoCManager.InjectDependencies(this);
  34. InputButton.OnToggled += MidiInputButtonOnOnToggled;
  35. BandButton.OnPressed += BandButtonOnPressed;
  36. BandButton.OnToggled += BandButtonOnToggled;
  37. FileButton.OnPressed += MidiFileButtonOnOnPressed;
  38. LoopButton.OnToggled += MidiLoopButtonOnOnToggled;
  39. ChannelsButton.OnPressed += ChannelsButtonOnPressed;
  40. StopButton.OnPressed += MidiStopButtonOnPressed;
  41. PlaybackSlider.OnValueChanged += PlaybackSliderSeek;
  42. PlaybackSlider.OnKeyBindUp += PlaybackSliderKeyUp;
  43. MinSize = SetSize = new Vector2(400, 150);
  44. }
  45. public void SetInstrument(Entity<InstrumentComponent> entity)
  46. {
  47. Entity = entity;
  48. var component = entity.Comp;
  49. component.OnMidiPlaybackEnded += InstrumentOnMidiPlaybackEnded;
  50. LoopButton.Disabled = !component.IsMidiOpen;
  51. LoopButton.Pressed = component.LoopMidi;
  52. ChannelsButton.Disabled = !component.IsRendererAlive;
  53. StopButton.Disabled = !component.IsMidiOpen;
  54. PlaybackSlider.MouseFilter = component.IsMidiOpen ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
  55. }
  56. public void RemoveInstrument(InstrumentComponent component)
  57. {
  58. component.OnMidiPlaybackEnded -= InstrumentOnMidiPlaybackEnded;
  59. }
  60. public void SetMIDI(bool available)
  61. {
  62. UnavailableOverlay.Visible = !available;
  63. }
  64. private void BandButtonOnPressed(ButtonEventArgs obj)
  65. {
  66. if (!PlayCheck())
  67. return;
  68. OnOpenBand?.Invoke();
  69. }
  70. private void BandButtonOnToggled(ButtonToggledEventArgs obj)
  71. {
  72. if (obj.Pressed)
  73. return;
  74. if (_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
  75. {
  76. _entManager.System<InstrumentSystem>().SetMaster(Entity, instrument.Master);
  77. }
  78. }
  79. private void ChannelsButtonOnPressed(ButtonEventArgs obj)
  80. {
  81. OnOpenChannels?.Invoke();
  82. }
  83. private void InstrumentOnMidiPlaybackEnded()
  84. {
  85. MidiPlaybackSetButtonsDisabled(true);
  86. }
  87. public void MidiPlaybackSetButtonsDisabled(bool disabled)
  88. {
  89. if (disabled)
  90. {
  91. OnCloseChannels?.Invoke();
  92. }
  93. LoopButton.Disabled = disabled;
  94. StopButton.Disabled = disabled;
  95. // Whether to allow the slider to receive events..
  96. PlaybackSlider.MouseFilter = !disabled ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
  97. }
  98. private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
  99. {
  100. if (_isMidiFileDialogueWindowOpen)
  101. return;
  102. OnCloseBands?.Invoke();
  103. var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
  104. // TODO: Once the file dialogue manager can handle focusing or closing windows, improve this logic to close
  105. // or focus the previously-opened window.
  106. _isMidiFileDialogueWindowOpen = true;
  107. await using var file = await _dialogs.OpenFile(filters);
  108. _isMidiFileDialogueWindowOpen = false;
  109. // did the instrument menu get closed while waiting for the user to select a file?
  110. if (Disposed)
  111. return;
  112. // The following checks are only in place to prevent players from playing MIDI songs locally.
  113. // There are equivalents for these checks on the server.
  114. if (file == null)
  115. return;
  116. if (!PlayCheck())
  117. return;
  118. await using var memStream = new MemoryStream((int) file.Length);
  119. await file.CopyToAsync(memStream);
  120. if (!_entManager.TryGetComponent<InstrumentComponent>(Entity, out var instrument))
  121. {
  122. return;
  123. }
  124. if (!_entManager.System<InstrumentSystem>()
  125. .OpenMidi(Entity,
  126. memStream.GetBuffer().AsSpan(0, (int) memStream.Length),
  127. instrument))
  128. {
  129. return;
  130. }
  131. MidiPlaybackSetButtonsDisabled(false);
  132. if (InputButton.Pressed)
  133. InputButton.Pressed = false;
  134. }
  135. private void MidiInputButtonOnOnToggled(ButtonToggledEventArgs obj)
  136. {
  137. OnCloseBands?.Invoke();
  138. if (obj.Pressed)
  139. {
  140. if (!PlayCheck())
  141. return;
  142. MidiStopButtonOnPressed(null);
  143. if (_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
  144. _entManager.System<InstrumentSystem>().OpenInput(Entity, instrument);
  145. }
  146. else
  147. {
  148. _entManager.System<InstrumentSystem>().CloseInput(Entity, false);
  149. OnCloseChannels?.Invoke();
  150. }
  151. }
  152. private bool PlayCheck()
  153. {
  154. // TODO all of these checks should also be done server-side.
  155. if (!_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
  156. return false;
  157. var localEntity = _player.LocalEntity;
  158. // If we don't have a player or controlled entity, we return.
  159. if (localEntity == null)
  160. return false;
  161. // By default, allow an instrument to play itself and skip all other checks
  162. if (localEntity == Entity)
  163. return true;
  164. var container = _entManager.System<SharedContainerSystem>();
  165. // If we're a handheld instrument, we might be in a container. Get it just in case.
  166. container.TryGetContainingContainer((Entity, null, null), out var conMan);
  167. // If the instrument is handheld and we're not holding it, we return.
  168. if (instrument.Handheld && (conMan == null || conMan.Owner != localEntity))
  169. return false;
  170. if (!_entManager.System<ActionBlockerSystem>().CanInteract(localEntity.Value, Entity))
  171. return false;
  172. // We check that we're in range unobstructed just in case.
  173. return _entManager.System<InteractionSystem>().InRangeUnobstructed(localEntity.Value, Entity);
  174. }
  175. private void MidiStopButtonOnPressed(ButtonEventArgs? obj)
  176. {
  177. MidiPlaybackSetButtonsDisabled(true);
  178. _entManager.System<InstrumentSystem>().CloseMidi(Entity, false);
  179. OnCloseChannels?.Invoke();
  180. }
  181. private void MidiLoopButtonOnOnToggled(ButtonToggledEventArgs obj)
  182. {
  183. var instrument = _entManager.System<InstrumentSystem>();
  184. if (_entManager.TryGetComponent(Entity, out InstrumentComponent? instrumentComp))
  185. {
  186. instrumentComp.LoopMidi = obj.Pressed;
  187. }
  188. instrument.UpdateRenderer(Entity);
  189. }
  190. private void PlaybackSliderSeek(Range _)
  191. {
  192. // Do not seek while still grabbing.
  193. if (PlaybackSlider.Grabbed)
  194. return;
  195. _entManager.System<InstrumentSystem>().SetPlayerTick(Entity, (int)Math.Ceiling(PlaybackSlider.Value));
  196. }
  197. private void PlaybackSliderKeyUp(GUIBoundKeyEventArgs args)
  198. {
  199. if (args.Function != EngineKeyFunctions.UIClick)
  200. return;
  201. _entManager.System<InstrumentSystem>().SetPlayerTick(Entity, (int)Math.Ceiling(PlaybackSlider.Value));
  202. }
  203. protected override void FrameUpdate(FrameEventArgs args)
  204. {
  205. base.FrameUpdate(args);
  206. if (!_entManager.TryGetComponent(Entity, out InstrumentComponent? instrument))
  207. return;
  208. var hasMaster = instrument.Master != null;
  209. BandButton.ToggleMode = hasMaster;
  210. BandButton.Pressed = hasMaster;
  211. BandButton.Disabled = instrument.IsMidiOpen || instrument.IsInputOpen;
  212. ChannelsButton.Disabled = !instrument.IsRendererAlive;
  213. if (!instrument.IsMidiOpen)
  214. {
  215. PlaybackSlider.MaxValue = 1;
  216. PlaybackSlider.SetValueWithoutEvent(0);
  217. return;
  218. }
  219. if (PlaybackSlider.Grabbed)
  220. return;
  221. PlaybackSlider.MaxValue = instrument.PlayerTotalTick;
  222. PlaybackSlider.SetValueWithoutEvent(instrument.PlayerTick);
  223. }
  224. }
  225. }