The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
9.3KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #pragma once
  19. #include "../../Project/UI/jucer_UserAvatarComponent.h"
  20. //==============================================================================
  21. class LoginFormComponent : public Component
  22. {
  23. public:
  24. LoginFormComponent (MainWindow& window)
  25. : mainWindow (window)
  26. {
  27. addAndMakeVisible (emailBox);
  28. emailBox.setTextToShowWhenEmpty ("Email", Colours::black.withAlpha (0.2f));
  29. emailBox.setJustification (Justification::centredLeft);
  30. emailBox.onReturnKey = [this] { submitDetails(); };
  31. addAndMakeVisible (passwordBox);
  32. passwordBox.setTextToShowWhenEmpty ("Password", Colours::black.withAlpha (0.2f));
  33. passwordBox.setPasswordCharacter ((juce_wchar) 0x2022);
  34. passwordBox.setJustification (Justification::centredLeft);
  35. passwordBox.onReturnKey = [this] { submitDetails(); };
  36. addAndMakeVisible (logInButton);
  37. logInButton.onClick = [this] { submitDetails(); };
  38. addAndMakeVisible (enableGPLButton);
  39. enableGPLButton.onClick = [this]
  40. {
  41. ProjucerApplication::getApp().getLicenseController().setState (LicenseController::getGPLState());
  42. mainWindow.hideLoginFormOverlay();
  43. };
  44. addAndMakeVisible (userAvatar);
  45. addAndMakeVisible (createAccountLabel);
  46. createAccountLabel.setFont (Font (14.0f, Font::underlined));
  47. createAccountLabel.addMouseListener (this, false);
  48. createAccountLabel.setMouseCursor (MouseCursor::PointingHandCursor);
  49. addAndMakeVisible (errorMessageLabel);
  50. errorMessageLabel.setMinimumHorizontalScale (1.0f);
  51. errorMessageLabel.setFont (12.0f);
  52. errorMessageLabel.setColour (Label::textColourId, Colours::red);
  53. errorMessageLabel.setVisible (false);
  54. dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
  55. addAndMakeVisible (dismissButton);
  56. dismissButton.onClick = [this] { mainWindow.hideLoginFormOverlay(); };
  57. setWantsKeyboardFocus (true);
  58. setOpaque (true);
  59. lookAndFeelChanged();
  60. setSize (300, 350);
  61. }
  62. ~LoginFormComponent() override
  63. {
  64. ProjucerApplication::getApp().getLicenseController().cancelSignIn();
  65. }
  66. void resized() override
  67. {
  68. auto bounds = getLocalBounds().reduced (20);
  69. auto spacing = bounds.getHeight() / 20;
  70. userAvatar.setBounds (bounds.removeFromTop (iconHeight).reduced ((bounds.getWidth() / 2) - (iconHeight / 2), 0));
  71. errorMessageLabel.setBounds (bounds.removeFromTop (spacing));
  72. bounds.removeFromTop (spacing / 2);
  73. auto textEditorHeight = bounds.getHeight() / 5;
  74. emailBox.setBounds (bounds.removeFromTop (textEditorHeight));
  75. bounds.removeFromTop (spacing);
  76. passwordBox.setBounds (bounds.removeFromTop (textEditorHeight));
  77. bounds.removeFromTop (spacing * 2);
  78. emailBox.setFont (Font (textEditorHeight / 2.5f));
  79. passwordBox.setFont (Font (textEditorHeight / 2.5f));
  80. logInButton.setBounds (bounds.removeFromTop (textEditorHeight));
  81. auto slice = bounds.removeFromTop (textEditorHeight);
  82. createAccountLabel.setBounds (slice.removeFromLeft (createAccountLabel.getFont().getStringWidth (createAccountLabel.getText()) + 5));
  83. slice.removeFromLeft (15);
  84. enableGPLButton.setBounds (slice.reduced (0, 5));
  85. dismissButton.setBounds (getLocalBounds().reduced (10).removeFromTop (20).removeFromRight (20));
  86. }
  87. void paint (Graphics& g) override
  88. {
  89. g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
  90. }
  91. void mouseUp (const MouseEvent& event) override
  92. {
  93. if (event.eventComponent == &createAccountLabel)
  94. URL ("https://juce.com/verification/register").launchInDefaultBrowser();
  95. }
  96. void lookAndFeelChanged() override
  97. {
  98. enableGPLButton.setColour (TextButton::buttonColourId, findColour (secondaryButtonBackgroundColourId));
  99. }
  100. private:
  101. class ProgressButton : public TextButton,
  102. private Timer
  103. {
  104. public:
  105. ProgressButton (const String& buttonName)
  106. : TextButton (buttonName), text (buttonName)
  107. {
  108. }
  109. void setBusy (bool shouldBeBusy)
  110. {
  111. isInProgress = shouldBeBusy;
  112. if (isInProgress)
  113. {
  114. setEnabled (false);
  115. setButtonText ({});
  116. startTimerHz (30);
  117. }
  118. else
  119. {
  120. setEnabled (true);
  121. setButtonText (text);
  122. stopTimer();
  123. }
  124. }
  125. void paint (Graphics& g) override
  126. {
  127. TextButton::paint (g);
  128. if (isInProgress)
  129. {
  130. auto size = getHeight() - 10;
  131. auto halfSize = size / 2;
  132. getLookAndFeel().drawSpinningWaitAnimation (g, Colours::white,
  133. (getWidth() / 2) - halfSize, (getHeight() / 2) - halfSize,
  134. size, size);
  135. }
  136. }
  137. private:
  138. void timerCallback() override
  139. {
  140. repaint();
  141. }
  142. String text;
  143. bool isInProgress = false;
  144. };
  145. //==============================================================================
  146. void updateLoginButtonStates (bool isLoggingIn)
  147. {
  148. logInButton.setBusy (isLoggingIn);
  149. emailBox.setEnabled (! isLoggingIn);
  150. passwordBox.setEnabled (! isLoggingIn);
  151. }
  152. void submitDetails()
  153. {
  154. auto loginFormError = checkLoginFormsAreValid();
  155. if (loginFormError.isNotEmpty())
  156. {
  157. showErrorMessage (loginFormError);
  158. return;
  159. }
  160. updateLoginButtonStates (true);
  161. WeakReference<Component> weakThis (this);
  162. auto completionCallback = [this, weakThis] (const String& errorMessage)
  163. {
  164. if (weakThis == nullptr)
  165. return;
  166. updateLoginButtonStates (false);
  167. if (errorMessage.isNotEmpty())
  168. {
  169. showErrorMessage (errorMessage);
  170. }
  171. else
  172. {
  173. hideErrorMessage();
  174. mainWindow.hideLoginFormOverlay();
  175. ProjucerApplication::getApp().getCommandManager().commandStatusChanged();
  176. }
  177. };
  178. ProjucerApplication::getApp().getLicenseController().signIn (emailBox.getText(), passwordBox.getText(),
  179. std::move (completionCallback));
  180. }
  181. String checkLoginFormsAreValid() const
  182. {
  183. auto email = emailBox.getText();
  184. if (email.isEmpty() || email.indexOfChar ('@') < 0)
  185. return "Please enter a valid email.";
  186. auto password = passwordBox.getText();
  187. if (password.isEmpty() || password.length() < 8)
  188. return "Please enter a valid password.";
  189. return {};
  190. }
  191. void showErrorMessage (const String& errorMessage)
  192. {
  193. errorMessageLabel.setText (errorMessage, dontSendNotification);
  194. errorMessageLabel.setVisible (true);
  195. }
  196. void hideErrorMessage()
  197. {
  198. errorMessageLabel.setText ({}, dontSendNotification);
  199. errorMessageLabel.setVisible (false);
  200. }
  201. //==============================================================================
  202. static constexpr int iconHeight = 50;
  203. MainWindow& mainWindow;
  204. TextEditor emailBox, passwordBox;
  205. ProgressButton logInButton { "Sign In" };
  206. TextButton enableGPLButton { "Enable GPL Mode" };
  207. ShapeButton dismissButton { {},
  208. findColour (treeIconColourId),
  209. findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
  210. findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
  211. UserAvatarComponent userAvatar { false };
  212. Label createAccountLabel { {}, "Create an account" },
  213. errorMessageLabel { {}, {} };
  214. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LoginFormComponent)
  215. };