From cddea48d4693d87f66572fa15c150bfabdcc0c0f Mon Sep 17 00:00:00 2001 From: Akinimaginable Date: Sun, 22 Jun 2025 20:39:56 +0200 Subject: [PATCH 1/3] Quality improvements --- .clang-format | 83 ++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + include/AccountComponent.h | 39 ++-- include/BasePedalComponent.h | 163 ++++++++------- include/ConnectionComponent.h | 56 ----- include/DelayEffect.h | 136 ++++++------ include/LoginComponent.h | 25 +++ include/MainComponent.h | 2 +- include/ToggleButtonComponent.h | 11 +- resources/icons/export.png | Bin 0 -> 6883 bytes resources/icons/mute.png | Bin 3647 -> 1819 bytes resources/icons/power.png | Bin 3499 -> 2091 bytes resources/icons/settings.png | Bin 4671 -> 3558 bytes resources/icons/sync.png | Bin 0 -> 566 bytes resources/icons/unmute.png | Bin 3329 -> 2872 bytes resources/icons/xmark.png | Bin 0 -> 997 bytes src/Main.cpp | 135 ++++++------ src/MainComponent.cpp | 131 ++++++------ src/components/AccountComponent.cpp | 194 ++++++++---------- src/components/LoginComponent.cpp | 61 +++--- src/components/effects/BasePedalComponent.cpp | 94 ++++----- .../effects/DelayEffectComponent.cpp | 122 ++++++----- src/utils/ModalOverlayComponent.cpp | 4 +- src/utils/ToggleButtonComponent.cpp | 53 +++-- 24 files changed, 687 insertions(+), 627 deletions(-) create mode 100644 .clang-format create mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 include/ConnectionComponent.h create mode 100644 include/LoginComponent.h create mode 100644 resources/icons/export.png create mode 100644 resources/icons/sync.png create mode 100644 resources/icons/xmark.png diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..61e646b --- /dev/null +++ b/.clang-format @@ -0,0 +1,83 @@ +# Google C/C++ Code Style settings +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# Author: Kehan Xue, kehan.xue (at) gmail.com + +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignOperands: Align +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never # To avoid conflict, set this "Never" and each "if statement" should include brace when coding +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterStruct: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 80 +CompactNamespaces: false +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false # Make sure the * or & align on the left +EmptyLineBeforeAccessModifier: LogicalBlock +FixNamespaceComments: true +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +ReflowComments: false +# SeparateDefinitionBlocks: Always # Only support since clang-format 14 +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++11 +TabWidth: 4 +UseTab: Always \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/include/AccountComponent.h b/include/AccountComponent.h index 77ae47d..bd2756a 100644 --- a/include/AccountComponent.h +++ b/include/AccountComponent.h @@ -2,28 +2,33 @@ #include -class AccountComponent : public juce::Component -{ +class AccountComponent : public Component { public: - AccountComponent(); - void paint(juce::Graphics&) override; - void resized() override; + AccountComponent(); + void paint(Graphics&) override; + void resized() override; private: - void apiResponseReceived(const juce::String& content); - void saveSettings(); - void importSettings(); - void changePassword(); + void apiResponseReceived(const String& content); + void saveSettings(); + void importSettings(); - juce::Label titleLabel; - juce::Label responseLabel; + void setupLabels(); + void setupButtons(); + void setupGrid(); - juce::Label emailLabel, usernameLabel; - juce::Label emailValueLabel, usernameValueLabel; + Label titleLabel{"titleLabel", "Mon compte"}; + Label responseLabel{"responseLabel", "En attente de réponse..."}; - juce::TextButton saveButton, importButton, changePasswordButton; + Label emailLabel{"emailLabel", "Adresse mail :"}; + Label usernameLabel{"usernameLabel", "Nom d'utilisateur :"}; + Label emailValueLabel{"emailValueLabel", "utilisateur@example.com"}; + Label usernameValueLabel{"usernameValueLabel", "NomUtilisateur"}; - juce::Grid grid; + TextButton saveButton{"Sauvegarder les réglages"}; + TextButton importButton{"Importer les réglages"}; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AccountComponent) -}; + Grid grid; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AccountComponent) +}; \ No newline at end of file diff --git a/include/BasePedalComponent.h b/include/BasePedalComponent.h index ed61d3f..3997706 100644 --- a/include/BasePedalComponent.h +++ b/include/BasePedalComponent.h @@ -7,89 +7,88 @@ * @brief Base class for pedal components. * This class provides a common interface and functionality for all pedal components. */ -class BasePedalComponent : public EffectComponent -{ +class BasePedalComponent : public EffectComponent { public: - /** - * @brief Initializes a new instance of the BasePedalComponent class. - * @param effect The effect associated with this pedal component. - */ - BasePedalComponent(AbstractEffect* effect); - - /** - * @brief Destroys the instance of the BasePedalComponent class. - */ - ~BasePedalComponent() override; - - /** - * @brief Paints the component. - * @param g The JUCE graphics context that paints the component. - */ - void paint(juce::Graphics &g) override; - - /** - * @brief Resizes the component. - */ - void resized() override; + /** + * @brief Initializes a new instance of the BasePedalComponent class. + * @param effect The effect associated with this pedal component. + */ + BasePedalComponent(AbstractEffect* effect); + + /** + * @brief Destroys the instance of the BasePedalComponent class. + */ + ~BasePedalComponent() override; + + /** + * @brief Paints the component. + * @param g The JUCE graphics context that paints the component. + */ + void paint(Graphics& g) override; + + /** + * @brief Resizes the component. + */ + void resized() override; protected: - /** - * @brief The default width of the component. - */ - const float DEFAULT_WIDTH = 200.0f; - - /** - * @brief The default height of the component. - */ - const float DEFAULT_HEIGHT = 300.0f; - - /** - * @brief The main layout for the pedal component. - */ - juce::Grid pedalLayout; - - /** - * @brief The primary color of the pedal component. Used as background color. - */ - juce::Colour primaryColor; - - /** - * @brief The secondary color of the pedal component. Used as background color. - */ - juce::Colour secondaryColor; - - /** - * @brief The layout for the pedal settings. - */ - PedalSettingsLayoutComponent* settingsLayout; - - /** - * @brief The label for the pedal name. - */ - juce::Label* pedalLabel; - - /** - * @brief The button to enable/disable the pedal. - */ - juce::ImageButton enablePedalButton; - - /** - * @brief The indicator for the pedal power status. - */ - PedalPowerIndicatorComponent* isEnabledIndicator; - - /** - * @brief Tells if the pedal is enabled or not. - */ - bool* isEnabled; - - /** - * @brief Called when the enable button is clicked. - */ - void onEnableButtonClicked(); - - /** - * @brief Initializes the pedal layout to display it. - */ - void initializePedal(); + /** + * @brief The default width of the component. + */ + const float DEFAULT_WIDTH = 200.0f; + + /** + * @brief The default height of the component. + */ + const float DEFAULT_HEIGHT = 300.0f; + + /** + * @brief The main layout for the pedal component. + */ + Grid pedalLayout; + + /** + * @brief The primary color of the pedal component. Used as background color. + */ + Colour primaryColor; + + /** + * @brief The secondary color of the pedal component. Used as background color. + */ + Colour secondaryColor; + + /** + * @brief The layout for the pedal settings. + */ + PedalSettingsLayoutComponent* settingsLayout; + + /** + * @brief The label for the pedal name. + */ + Label* pedalLabel; + + /** + * @brief The button to enable/disable the pedal. + */ + ImageButton enablePedalButton; + + /** + * @brief The indicator for the pedal power status. + */ + PedalPowerIndicatorComponent* isEnabledIndicator; + + /** + * @brief Tells if the pedal is enabled or not. + */ + bool* isEnabled; + + /** + * @brief Called when the enable button is clicked. + */ + void onEnableButtonClicked(); + + /** + * @brief Initializes the pedal layout to display it. + */ + void initializePedal(); }; diff --git a/include/ConnectionComponent.h b/include/ConnectionComponent.h deleted file mode 100644 index bd55dd9..0000000 --- a/include/ConnectionComponent.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include - -/** - * @brief Represents a graphical component that contains and displays the login form. - */ -class LoginComponent final : public juce::Component { - public: - /** - * @brief Initializes a new instance of the LoginComponent class. - */ - LoginComponent(); - - /** - * @brief Destroys the instance of the LoginComponent class. - */ - ~LoginComponent() override; - - /** - * @brief Determines how to display the component. - * @param g The JUCE graphics context that paints the component. - */ - void paint(juce::Graphics &g) override; - - /** - * @brief Determines what to do when the component is resized. - */ - void resized() override; - private: - /** - * @brief The username field. - */ - juce::TextEditor usernameField; - - /** - * @brief The password field. - */ - juce::TextEditor passwordField; - - /** - * @brief The login button. - */ - juce::TextButton loginButton; - - /** - * @brief The skip button. - */ - juce::TextButton skipButton; - - /** - * @brief Determines what to do when the skip button is clicked. - */ - void skipButtonClicked(); - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LoginComponent) -}; \ No newline at end of file diff --git a/include/DelayEffect.h b/include/DelayEffect.h index babd770..06d00bd 100644 --- a/include/DelayEffect.h +++ b/include/DelayEffect.h @@ -9,84 +9,86 @@ */ class DelayEffect : public AbstractEffect { public: - /** - * @brief Initializes a new instance of the DelayEffect class. - */ - DelayEffect(); + /** + * @brief Initializes a new instance of the DelayEffect class. + */ + DelayEffect(); - /** - * @brief Destroys the instance of the DelayEffect class. - */ - ~DelayEffect() override; + /** + * @brief Destroys the instance of the DelayEffect class. + */ + ~DelayEffect() override; - /** - * @brief Applies the delay effect to the given audio buffer. - * @param bufferToFill The audio buffer to apply the effect to. - */ - void apply(const AudioSourceChannelInfo &bufferToFill) override; + /** + * @brief Applies the delay effect to the given audio buffer. + * @param bufferToFill The audio buffer to apply the effect to. + */ + void apply(const AudioSourceChannelInfo& bufferToFill) override; - /** - * @brief Sets the rate of the delay effect. - * @param rate The rate of the delay effect. - */ - void setRate(float rate); + /** + * @brief Sets the rate of the delay effect. + * @param rate The rate of the delay effect. + */ + void setRate(float rate); - /** - * @brief Sets the delay of the effect. - * @param delay The delay of the effect. - */ - void setDelay(float delay); + /** + * @brief Sets the delay of the effect. + * @param delay The delay of the effect. + */ + void setDelay(float delay); - /** - * @brief Compares the effect with another given effect. - * @param effect The effect to compare with. - * @return True if the effect is equal to the given effect, false otherwise. - */ - bool operator==(const AbstractEffect *effect) override; + /** + * @brief Compares the effect with another given effect. + * @param effect The effect to compare with. + * @return True if the effect is equal to the given effect, false otherwise. + */ + bool operator==(const AbstractEffect* effect) override; - /** - * @brief Gets the type name of the effect for serialization purposes. - * @return A string representing the effect type. - */ - [[nodiscard]] String getEffectType() const override { return "DelayEffect"; } + /** + * @brief Gets the type name of the effect for serialization purposes. + * @return A string representing the effect type. + */ + [[nodiscard]] String getEffectType() const override { + return "DelayEffect"; + } - /** - * @brief Serializes the delay effect to a JSON object. - * @return JSON object containing serialized effect data. - */ - [[nodiscard]] var toJSON() const override { - auto obj = AbstractEffect::toJSON(); - if (auto *dynamicObj = obj.getDynamicObject()) { - dynamicObj->setProperty("rate", rate); - dynamicObj->setProperty("delay", delay); - } - return obj; - } + /** + * @brief Serializes the delay effect to a JSON object. + * @return JSON object containing serialized effect data. + */ + [[nodiscard]] var toJSON() const override { + auto obj = AbstractEffect::toJSON(); + if (auto* dynamicObj = obj.getDynamicObject()) { + dynamicObj->setProperty("rate", rate); + dynamicObj->setProperty("delay", delay); + } + return obj; + } - /** - * @brief Deserializes the delay effect from a JSON object. - * @param json JSON object containing serialized effect data. - */ - void fromJSON(const var &json) override { - AbstractEffect::fromJSON(json); + /** + * @brief Deserializes the delay effect from a JSON object. + * @param json JSON object containing serialized effect data. + */ + void fromJSON(const var& json) override { + AbstractEffect::fromJSON(json); - if (const auto *obj = json.getDynamicObject()) { - rate = static_cast(obj->getProperty("rate")); - delay = static_cast(obj->getProperty("delay")); - } - } + if (const auto* obj = json.getDynamicObject()) { + rate = static_cast(obj->getProperty("rate")); + delay = static_cast(obj->getProperty("delay")); + } + } private: - /** - * @brief The rate of the delay. - */ - float rate; + /** + * @brief The rate of the delay. + */ + float rate; - /** - * @brief The delay of the effect. - */ - float delay; + /** + * @brief The delay of the effect. + */ + float delay; - std::vector circularBuffer; - int writePosition = 0; + std::vector circularBuffer; + int writePosition = 0; }; diff --git a/include/LoginComponent.h b/include/LoginComponent.h new file mode 100644 index 0000000..2d545cb --- /dev/null +++ b/include/LoginComponent.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class LoginComponent final : public Component { +public: + LoginComponent(); + ~LoginComponent() override; + + void paint(Graphics&) override; + void resized() override; + +private: + void setupFields(); + void setupButtons(); + void layoutComponents(); + void skipButtonClicked(); + + TextEditor usernameField; + TextEditor passwordField; + TextButton loginButton{"Login"}; + TextButton skipButton{"Skip"}; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LoginComponent) +}; \ No newline at end of file diff --git a/include/MainComponent.h b/include/MainComponent.h index 647c1db..645f75f 100644 --- a/include/MainComponent.h +++ b/include/MainComponent.h @@ -1,7 +1,7 @@ #pragma once #include "BottomMenuBarComponent.h" -#include "ConnectionComponent.h" +#include "LoginComponent.h" #include "Manager.h" #include "PedalboardComponent.h" #include "TopMenuBarComponent.h" diff --git a/include/ToggleButtonComponent.h b/include/ToggleButtonComponent.h index 3cd17df..6b2e9b3 100644 --- a/include/ToggleButtonComponent.h +++ b/include/ToggleButtonComponent.h @@ -2,10 +2,9 @@ #include -class ToggleButtonComponent : public ToggleButton -{ +class ToggleButtonComponent : public ToggleButton { public: - ToggleButtonComponent(); - ~ToggleButtonComponent() override; - void paint(juce::Graphics& g) override; -}; \ No newline at end of file + ToggleButtonComponent(); + ~ToggleButtonComponent() override; + void paint(Graphics& g) override; +}; diff --git a/resources/icons/export.png b/resources/icons/export.png new file mode 100644 index 0000000000000000000000000000000000000000..69f11e2f12fc81576dfaaae971dacae798447e4c GIT binary patch literal 6883 zcmeHsXH-+cw{M6+0tynUO7DskMHJ~RNC)X1L^_5l1Tl1@^r9e0@4W{SdJz!m9f5?7 zgch1Kk@n(U>%EWf)Bk?BYuyjCW}lgTetTxlK70RWCj5n#G9|@b3IG5=sj33iCDuy- z0Es5}I#D{l3pOKGByV(;6#x}O%p1f9sGYH@y{0CBpD2R?B%w|K&_5Moxl1enz_lzA zz%^n`@~gq(r>7s7>eBE8foWCKx=uJgysL#d zn7p1(Tt7=ksq=J`q4T((XFW*{|6m#G&eKZ&WbCmm*~y)KCajF&u`tcb+ssCo5QGG3 zoMT%X%1Kg@CPYp|{tOlEi~|@=tv#^YeO>XO zs+RWY50$V=#y$D*9wM}D`}_JNz?ANVTR(;=&myzY=k?hdPuJAn37dm3Fnb3i*lpJk zXl}G+--mHt!0%lY{`Sa2{xKu7s(0rN0;dP#J@`l}3gJe*n4WQ&s=;d|Cv`NOjK|l4 zDNFBiGBa!lU8n>xImeT`*9aL%8Od@xD653Q3n}xtLDWlNS`HG|vK@!s`6pcu@x_&$ zJY63Y3Go&0sO6B-i(RwaB-o5$P`&&>K;xgwhM#P_975};^hrhTAaE$SY32*)kE=gM zam458RP@VJ`+ObfL`h~WC*3w z6-OGoPQ|SxM+Al%vn=|U5I?dBtI&dIaAD6UX-M1W$j&9n>LeA5Vh@7odBxTg7pK=y z1MiOVs%Of;JY~G6Z&OH{!ph8(Rrx$>NlveU&%A_7PoHdernFukGhPjoG5HExC7($6c) z>gMa&xwo8`Uw(S#5Pc$S`g4CY7{Dn2(4qaU@q>h*Wor-lqIwU1O^UB35Y zd2r^@=`i|%#rZ0}+h0ycxi&Bo!08wHT`p4;607r`mdtsg3B?kSaepaFtc1R$U3S#+ zlA(7&2Zdk?h!lbir4{a6UU5 zCdmphp0bS&{OM)s?i4!j5ZPIH<8O8Y(e+6RgL&ugJHJ9ORC*>cvj}GkIK5d%@xlHk zTp1ntmJmMS8$;A-m+vfF%iJd=>dU=Nf!UxQ>(9Lzn<)ivuQr>w^;$Oa-MCrZVycWd zIzaKWaHK9+HngAUb$vgG)eU|z28#e+`)v=@M_2Yq-DX9V-Z&iu-us1jyz9G@%W?Cwi>UJt zt&~g?)uP!z)Wt&wOWq5}X7@3M6|#)!{RplkMMqA%6cs2`aNT)#W@xCtM6hS4bWDUP?Fb1&hF-g!HNQMI+d0awl@k*ej8?B zCC&BvhsZPD4_0ao#REa367%dtO~2exN9EB zOmS%Tw*bwM&D(|T8xfd6uspU4^|YFRw$=m<&jOt{-uOlfpKxR?w^_EP#2TRM1rI&M z(5>bkzUQCr)2SrioiaqGfVcP|M*SFXjWCvu^gMD{sFzZNMegCf59f}|aHM;+GP|G* zeAEFEk8>8>a=xfeS|E-3T ziJ|?d2@64`d&`WyQlyTmQbx4L*Uw&{b3FVlY#O_L&EYfzeTPid#?o}~(Ugyso%5|V zn7;+>k>rp}KYoXq^w57T>+<>O-nsM+^St})q!zNnxlZ3Vv7cLRxAV#HA9FvaD0uya z-ArKPGr#fhLD^luXCK9wjnFjNmz!8pcgaVK2KLUL?=tK6A_nJTyPX{Z+PG7<*8O~x ztyCLJzJR}QG;!-3Y9-j`R8HneRi5SxsYm-(cfU&`xE4ODER>D$P7UqPD#b0f>_j%J z(hZw_w2f|62^$ovpIduK`2Ks%*C$3%rPEtS4ALslT*(0SE5&9Ogxj=zayApaTyWaU zjF&d2?hvNHXt(;7L#L^S(n(S9ChTR)`LtUumeHe1)>+T);70pbvn&^$l%HzI=%~Sg zVjDW$Ga_y}&-}GqyhO|({`*1kL3_n`F|W+< zJ*=Xw?5!}YR+o9v(|MmIke5X(KbkK4vzx;&Eq#dr)~(MBW^TAxLkrL}sqU7u2dX8d zpX3;_>kJ^ve=0@gdF>>V_(1YK0nQp|!bPr+r0 zL=qx6K9toD1waz-Gj-{z1H-Bkog7B&ID^3b36!sjd5A2gzxMIE2t*EP*LS^o;xmA= z%np0UE14K{MeN=iJF5a9KSj20Hfj*Dl2P&kXd*#)oeaNj!Ar!_m|;&erIACTH6-5) z(Gsx=VASe@E z(LJI@2XTHW=?r2^oC#FFV7*gmNC#5e08cm1=2@SK=9uN2o@L1_a5Pzj_!U*lU^_IC z6th(KZ^ilR+6(*T%dsl~F`-v-ovlT_(%PAoNpp0sZHFQ@ z`J_57tbA4n2tGc2sVPy-^y0dZztX)QaBKg>0eBl7SdKk4Xf8}d@12P>;>`(KT4~z- zZ9@uf+m6_kB^K=S`oL?WdnoUEQUzxM7Y-f{D}Cvvj|6~Z?t!eBK#E3?zVM61S#N5G zi4On}FW65*vO4wys)sI{874^fBUB=r6~sOwzo@a{_&x@G=Yfb)(Yv)i!@iZaQ=L3(bAdXT|F`&^}6cIh{IaN;k(%SfiD41s?KD!*Qf4{!4cqtDU^|I zW&iWbs{B{$Mhu6gFGjaz$Lsq7DyyUmY?a@@3p@p9+LNdzx^w+hwFt*U6?S(ZB|kI! z38JqFuR?0pYImFkwuAfDynNnKbm$cw3H8NGPo(OPQg!c~*1n!Q_S-r}N!xb5{pHGd z)Kc?xF85MIy$O6JQIojPSNN-sm8?NWe$rcbS(#S*8%T7)Qg2n62_F9>;TdLPnSCZ0 z^~AP1;$n1@Ujb*QO-Cwf!shCKkb2f8+_Umsgm!_(XP5p>k@>WhBLZ6HIFtS8G~#cu z7h>O1^I97AA(Y@?+!Y^l%&eOg5F`P zz8&FB;oO#sr%x7=^9_&)ho3!dV*w}WmEY32YvKjzzzHcq_>FjqJv~%-r@`9GLazfc z4u$QQZL%%=9RI3$V8QjH?KBss>%pI>N75_=Bu&zB4L62=%WnuAcogC@+_i`2fA!CI zTF1)hu*dNS*U!hlEt)@y;e6kk(j-tmkFn_66d-ow7bm_yXd@xpEL?eLeCzXgSri9ba+*nEjDvW3@U9B##{P#Fr}wGp0< zN!q37*i(#NQ7Wv4&F%bv*2|yur7hUZxx_zaPLXd94T1~7KX)V7btDe$In5V3Yy4(+ zevye$-{T4v*OK>MA5=#QZ&iM`(UM=j4z%NTuM9yowMQgsKw4{xjL=uIWNh_8a!%xq zd^UBKGON@tL+4@I>mF0j5b~jf#hgPBUJ^Q;Ek?VeX>9?88&uUcM^2JiUg={tG5L6{KT#3Vm$f zW{|Gki$5;KO$X#AW*SIJK2}pjwo`<6ncyDs+-ox5Ki%KoZ$Y1z!N^Ru*tE|seClA0 zo7m5vZ<9d3s&sHy%}{t_y6Q2BW<5XS1QQ0QFiFJY95n!hH}M(y5Ypj!F*x{VXG+A) zaBDesL81?r`$1etiS{XYzi{L$0U!lmgmcQ-iHzf0o=9FTk*w7IR;r~Za+3#N^*<&s z5Um)thG9a8q=huTFK&#;cgFh|M-AhM^ya1R9bHpmhv_LNmV&ql^cwt%YQ!C|#se#; znLKgkA*yyNPDDcFv>NI7Km`CXd{}(*{{If*Ke-Z%JPN|HPNQ7dMo5^*@Zbg$6}J(5 zFZc#HAwB4al+~Dw3hh$P@fYz{1Sl!jEJf~R5l9N}8#=FnZ1cI@ZAvBMP7;iwHO6tY zQ?(?6t$f^@gWumWm&+o6o_c|+h*2~&mq`*7`VX(YfQrPPHAcwGy*d5AbZSAwgT^Ey zcQbJiF*M_7TNfAK(vmh5yk{ce0v3H+ujct@;V^gcu{aWxC^+@yl^KY(p0D}AB5^(};+PY2J zzhLT*!3oLj4rw7ZO?G(nnFvHi5P|kr9NJahtn4lk$Bo?t=5uA8N}0&BtkX@?+Su5n zPm!ZN%~{@VQ%-%Vv`GVYQ-b>B7H^VTC-F(C6%4_&9arbY7=^C1Caq%A&83z=KrZeq z-*YGLwk8{}Z13+$x+NA9rZ*LjGsi^%u}wETKgvA@(-CdFUY0EXniTUhS>koXo{T|Y zey-{zJ>2+R{&kc|0Anq(-4X0KkA1SMZu{Wb^S!SDf2HGB-4u{mM+cXkzLFO&=XjkF z<<}2?kot|#0Tp?&PTz$|q;VX`e0x@hlS@G;;4Uv*zfhFbDa%%jwQD7{&D6sRB z3~DzWjdl4X4!pTF9o1CY+MV)*E-0NOMG#ljFywps`P|}ulO^}CXd3 zfhJ{Q2N1I|TykCf0^}GY0d;&ZxhElr$2t({d0b@slQEqU^hr$TsJ)Mb06Jc@uIFvK zH7$iY7zwHB9Env&vS@4AJ{g{+HB}Nf&iY`1S`^6tXD_PVO@F)S1xZSO1!rxvr&lZk zU*CMy?mvRgM}j*@24t?Al)BhxLrg8in++ms?7*WzFWBQW+d*&B4qx3;ys6)K$qPHl zSqRgs-BC!kIL{-V^}9igi()Zkev9P>OPL``jzT!2nPqLjUHa7SNAh zII=Y~w>*YPZ9#j>)zxCy0Xz{GbRl(?NNDE-in?CgCNGz-ajdM5mC5AA6*a{0fSeUx zW_0^vx=!?B_wfwb-a;G|MIWSdtWQ#~-bxgp5{*SwO4k$u8BNw4WJQ-Eyy{MHm!nVq zuCSyE#{Z49I#@p_D12KPwcG0&|L5I2S&Bom5GQQgIAqpz;a1$tf$8xq11Yc=@^DF- zJC&-z@_*WVh}L;Y={;up2WXvDzDwq`+L>DixAxlc7*MpMHY%nkq%| w@A$2Ql}A*MSCfGMJvSx)nH!j!@7u4g4V&m-g57Ps{#`JsDrrG06kdk>AA(>M`2YX_ literal 0 HcmV?d00001 diff --git a/resources/icons/mute.png b/resources/icons/mute.png index 76b5156890be799bf107676df885281a2111336b..88dc4e0ad32cdd306822d952a39cca59dbb2d81a 100644 GIT binary patch literal 1819 zcmV+$2juvPP)Ys?)*9mao8DOkBwY6uuEg(fzjD8)!50gXV=#1M!I6fEVE zwm^d|hzSKrK?FpQa1%ZV+G<;&71|F_F+w0AsV0?v;?gD!F@R~y2QC^Kswt%pA9nlF z_b{`2&YazI_MDt2d6V~@{m;&mUuSk^XJ@7`(nuqXG}1^TjWp6oBQ;v;w}HB3dm=Cc zI2HI1_&cys((by-97>qBrvncH(^@XA1{OL|Pp***t&oKJ%U zI1jk8uCjwQ)Aj+tTHuI0X)CZm(x$qMR3Y07fdwUDn(*nw!d75 ziEfp*qT4732{7IE+7oJD3InwLJ-V2$ zoY8MD5P>Oa3vg#7Pa}ZZJ`z|7oEQmt8d%;tM+LpPq-DSzk;{DrnwPthaE}e}proGy z&-cz%0ZbSNG&C;j3sBpq04sn)1DAILw@7-Zm!9^h5GFJWsO__XM+kQyJArE@t?Y%D zkV;_!NLo&xo4oD0z}>*Ufy*BNmrMFvogT{67EBle`pd}W+JM@gVEaa35#g7NcY%u~ zt*^T5PSrL{7zZ@gWXT*^N4a3FDf42sHq=p|qEe0<|sM({0}XJOE4zmGMJDI^h4k$z;i9}JAnC;)>i4UP9?*%{Y~2!wWe1D^uNGmlGdgA>xYtH z<{#K@0B*89yY+ew^mfvK{u!7h>7^8J{ZwkrJQZ?*= zPTlMGJwRu*2BiQUWTnD9s^xD1=Y|5?1p1XkAB|Ef%p(G6Z^eS!0J?M-fTqIyOdzdr zK%E1T`6NVFL-(bQR&|4W2pnZi2An7rN z1Zdx3!telX1WXtXppAhEV?ckwfWCS*3MN3lRwxE^G7SdWIGE5j(69E^Rk<1o6WRj0 zv}r&a3ljzpXrp1m7|>ft1NvTFzCuccxvNqaC6cs?G@!2m&(>&doKj(iCW1rDqAODh z(EPjnwe$#RD$Ff`v@g~DJtq<9E_wtsX(Oub(}CXxF6|^+H<_~3?;YSp;4fv%U~FGV z1~ugWePE8HS6cE+1nvXA3!E-#TY|?zrNZ1Fc!qkK_CJAZB>ktvLpMNw)fkunw!aVD zRiT%?^*S(D($*3WsX!-7s=TM~W@z*z*|xwsR;En@R@pwTC@A$AS_ep|VDrB1Z;&mO zITo1MwWIOcPPR3>a3@7d4Xs0!orGX}U$XD%M+5tJkaa)cEa2Ob>pOw#CH=Le{0qq5 zmih*?F~>^So=>)LE97Z6S*KCBv9t}Kb0xjpt=E_u0~5gZ56D)2#2y#F1MVprI`tE1 zqhJEq{ub~X;*W|afgAI|cy2@}BfRI+A$ zOeEx0;0j6a7TjYd*>|O$0BtNx0NbA@+p=;(B;-x94OagyxJS=`HhMlYA6#DoybuXF z8dzC0!6NB#;AY|j`z5wx|5zQV(J%od{SUZ`YCV-@OfZ4!5 zN6kkRO{DA#p)({9bbnqr_!+5tKgFkx&X>muNO ziWf;t4JB3qbf7z+m~4LyxQ9>gqSJ+qe+xl;*~E91kT-82gNJJaAH; zv>w>h&S}`T&n26r?g;46!t7QhK(_&Bw$|{Wd1h8ENo#<`grA{@1E;knj$jt31nBl2 z+Zhho{-N!cZEwzh*7V!cK+Ehsr07e>Vq>-Acj{(kBCgf<(s~G?Q002ov JPDHLkV1kL?N0I;l literal 3647 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 VUx@u}i3tDz002ovPDHLkV1jF${2c%Q literal 3499 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 literal 4671 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 Bo>5q5g`O5r1C2W zegPYmC}^XFC^p)ON*cBBNe~+$Y6>wDwJ{RRvAFy6k>tMr77>4NVV1pfW@dNTUHGS^ z_N#O1Ep>ORe$fJW7jPLk0{j40fHM*CxtVX)1Y8E@fTO@O;5{$^+)#%)hNtqcr;Mk8 zL0}j-3+x4Ms{5M-D1zTstLkJL-&LO^yQ02OAF8FcGY95@>4=z5<47M7@hVmD2>97i z_8Tg>r#@}RXH!XPM!vqaEI$?m8{$vp%Mr1uf>IrD3YY{cz|J(k4jd~;><+Lm+27R# z@{YO`5o-XY1ik>ABJbnZz#?FR7wG{|PWA|J0+;~C)Y*twLp`m&N!?K{A~jocb%%O2 zv1TD(vx$`Dxrq4IAk&75h*e+)SWQeaU>sPCh~-Yf8v-l<6}2w|4ggEtg6p&d^aBG8 z&z5erec^?N&KCMaw6V@8^s0?wy@zXI>bkKtA|0_;il zwTQU*8=eB3B){gyfrGU08mrR>;3e=pBJQ{0_g_8MFGhpce&dJ99RL6T07*qoM6N<$ Eg3565`~Uy| literal 0 HcmV?d00001 diff --git a/resources/icons/unmute.png b/resources/icons/unmute.png index 84bb7f797d9dc9ecefe2560eb2ca02e01b0d9c71..d1e4d796c71b3029e18828bbc51e816286f9b989 100644 GIT binary patch literal 2872 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 FzX21VPKW>i diff --git a/resources/icons/xmark.png b/resources/icons/xmark.png new file mode 100644 index 0000000000000000000000000000000000000000..4937ee7ec738e5db33ff6c6afa7ec405cfda4ab1 GIT binary patch literal 997 zcmV)Vrs>{Ii zkakajc@g=Qa5TAwRn3_L&vOk}|Fm_B+dp#mP@1@I{Y zmVkF-4(y!-2l%hRB@xj~M1BARHi-mB_<@Lgfc&;+8cqSXfZbynclyHU0l#YgA@-+z zmQV*C@+;v+MC1!@r^8a0P6tQ$lVx})Re;r@OC>x3&bM6&9`J96eK=G=(Q)S`G=+yT z0*V1TDra+NSIBEPHp3z1Op;2@wFurm@u!h@rLV&K*#goXzX0mTGblMn+Q zJOvaJNiHE4Jk$_SOemQI2l#K~D?D#_D6Irt({0S|!% zKs^aa+Ryh%t2Romn`2X7Ke!nlo+orO0EAz{>3+0Mh8 zG?nn5fIh*SG?7r=F5f$Nle!Y@A52uieYcyNAp zI0RgT{D+hGfte8p_6pvPLnW*M`!ZnXhy!{H4^EY^1=yZ3{2SX|!<$q{SOq?#s=HM6 zw)LapyQ;b;?NsANRi{+-y2ZGxs(azz+u#iO3zCl_vfkEYV@6e{fgQkma~u2%tHbAo T;2Fz&00000NkvXXu0mjfPNl4* literal 0 HcmV?d00001 diff --git a/src/Main.cpp b/src/Main.cpp index a403b1a..895ebdb 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,91 +1,80 @@ #include - #include "EffectsFactory.h" #include "MainComponent.h" class GuiAppApplication final : public JUCEApplication { public: - GuiAppApplication() = default; - - // We inject these as compile definitions from the CMakeLists.txt - // If you've enabled the juce header with `juce_generate_juce_header()` - // you could `#include ` and use `ProjectInfo::projectName` etc. instead. - const String getApplicationName() override { return JUCE_APPLICATION_NAME_STRING; } - const String getApplicationVersion() override { return JUCE_APPLICATION_VERSION_STRING; } - bool moreThanOneInstanceAllowed() override { return false; } - - void initialise(const String &commandLine) override { - // This method is where you should put your application's initialisation code.. - ignoreUnused(commandLine); - - mainWindow = std::make_unique(getApplicationName()); - } - - void shutdown() override { - mainWindow = nullptr; // (deletes our window) - } - - void systemRequestedQuit() override { - // This is called when the app is being asked to quit: you can ignore this - // request and let the app carry on running, or call quit() to allow the app to close. - quit(); - } - - void anotherInstanceStarted(const String &commandLine) override { - // When another instance of the app is launched while this one is running, - // this method is invoked, and the commandLine parameter tells you what - // the other instance's command-line arguments were. - ignoreUnused(commandLine); - } - - /* - This class implements the desktop window that contains an instance of - our MainComponent class. - */ - class MainWindow final : public DocumentWindow { - public: - explicit MainWindow (String name) - : DocumentWindow (name, - Desktop::getInstance().getDefaultLookAndFeel() - .findColour (backgroundColourId), - allButtons) - { - setUsingNativeTitleBar (true); - Pedalboard* pedalboard = new Pedalboard(); - pedalboard->appendAll(EffectsFactory::createAllEffects()); - Manager* manager = new Manager(pedalboard); - setContentOwned (new MainComponent(*manager), true); + GuiAppApplication() = default; + + const String getApplicationName() override { + return JUCE_APPLICATION_NAME_STRING; + } + + const String getApplicationVersion() override { + return JUCE_APPLICATION_VERSION_STRING; + } + + bool moreThanOneInstanceAllowed() override { return false; } + + void initialise(const String& commandLine) override { + // This method is where you should put your application's initialisation code.. + ignoreUnused(commandLine); + + mainWindow = std::make_unique(getApplicationName()); + } + + void shutdown() override { + mainWindow = nullptr; // (deletes our window) + } + + void systemRequestedQuit() override { + // This is called when the app is being asked to quit: you can ignore this + // request and let the app carry on running, or call quit() to allow the app to close. + quit(); + } + + void anotherInstanceStarted(const String& commandLine) override { + // When another instance of the app is launched while this one is running, + // this method is invoked, and the commandLine parameter tells you what + // the other instance's command-line arguments were. + ignoreUnused(commandLine); + } + + class MainWindow final : public DocumentWindow { + public: + explicit MainWindow(const String& name) : + DocumentWindow( + name, Desktop::getInstance().getDefaultLookAndFeel().findColour( + backgroundColourId), allButtons) { + setUsingNativeTitleBar(true); + Pedalboard* pedalboard = new Pedalboard(); + pedalboard->appendAll(EffectsFactory::createAllEffects()); + Manager* manager = new Manager(pedalboard); + setContentOwned(new MainComponent(*manager), true); #if JUCE_IOS || JUCE_ANDROID setFullScreen (true); #else - setResizable(false, false); - centreWithSize(1280, 720); + setResizable(true, true); + setSize(1280, 854); + centreWithSize(1280, 854); #endif + Component::setVisible(true); + } - Component::setVisible(true); - } - - void closeButtonPressed() override { - // This is called when the user tries to close this window. Here, we'll just - // ask the app to quit when this happens, but you can change this to do - // whatever you need. - getInstance()->systemRequestedQuit(); - } - - /* Note: Be careful if you override any DocumentWindow methods - the base - class uses a lot of them, so by overriding you might break its functionality. - It's best to do all your work in your content component instead, but if - you really have to override any DocumentWindow methods, make sure your - subclass also calls the superclass's method. - */ + void closeButtonPressed() override { + // This is called when the user tries to close this window. Here, we'll just + // ask the app to quit when this happens, but you can change this to do + // whatever you need. + getInstance()->systemRequestedQuit(); + } - // private: - // JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow) - }; + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow) + }; private: - std::unique_ptr mainWindow; + std::unique_ptr mainWindow; }; // This macro generates the main() routine that launches the app. diff --git a/src/MainComponent.cpp b/src/MainComponent.cpp index 1d3a185..275967a 100644 --- a/src/MainComponent.cpp +++ b/src/MainComponent.cpp @@ -1,84 +1,71 @@ #include "MainComponent.h" -#include "SettingsComponent.h" #include "ResourceManager.h" - - - -MainComponent::MainComponent(const Manager &manager): pedalboardComponent(manager.getPedalboard()), - topMenuBarComponent(this->deviceManager, &isSoundMuted), manager(manager) -{ - setAudioChannels(2, 2); - - Image background = ResourceManager::loadImage("resources/images/background.png"); - if (background.isValid()) { - backgroundImage.setImage(background); - backgroundImage.setImagePlacement(juce::RectanglePlacement::stretchToFit); - addAndMakeVisible(backgroundImage); - } else { - DBG("Erreur : image de fond introuvable ou invalide."); - } - - pedalboardContainer.setViewedComponent(&pedalboardComponent, true); - pedalboardContainer.setScrollBarsShown(true, false); - - addAndMakeVisible(pedalboardContainer); - addAndMakeVisible(topMenuBarComponent); - addAndMakeVisible(bottomMenuBarComponent); - addAndMakeVisible(connectionComponent); +MainComponent::MainComponent(const Manager& manager): + pedalboardComponent(manager.getPedalboard()), + topMenuBarComponent(this->deviceManager, &isSoundMuted), manager(manager) { + setAudioChannels(2, 2); + + const Image background = ResourceManager::loadImage( + "resources/images/background.png"); + if (background.isValid()) { + backgroundImage.setImage(background); + backgroundImage.setImagePlacement(RectanglePlacement::stretchToFit); + addAndMakeVisible(backgroundImage); + } else { + DBG("Error : resources/images/background.png not found."); + } + + pedalboardContainer.setViewedComponent(&pedalboardComponent, true); + pedalboardContainer.setScrollBarsShown(true, false); + + addAndMakeVisible(pedalboardContainer); + addAndMakeVisible(topMenuBarComponent); + addAndMakeVisible(bottomMenuBarComponent); + addAndMakeVisible(connectionComponent); } //============================================================================== -void MainComponent::paint (juce::Graphics& g) -{ -} - -void MainComponent::resized() -{ - // This is called when the MainComponent is resized. - // If you add any child components, this is where you should - // update their positions. - backgroundImage.setBounds(getLocalBounds()); - connectionComponent.setBounds(getLocalBounds()); - - int pedalboardWidth = getWidth(); - int pedalboardHeight = pedalboardComponent.getRequiredHeight(getWidth()); - pedalboardComponent.setSize(pedalboardWidth, pedalboardHeight); - - using Track = juce::Grid::TrackInfo; - using Px = juce::Grid::Px; - using Fr = juce::Grid::Fr; - grid.templateRows = { Track (Px (50)), Track (Fr(1)) }; - grid.templateColumns = { Track (Fr(1)) }; - grid.items = { - juce::GridItem(topMenuBarComponent), - juce::GridItem(pedalboardContainer) - }; - grid.performLayout(getLocalBounds()); +void MainComponent::paint(Graphics& g) {} + +void MainComponent::resized() { + backgroundImage.setBounds(getLocalBounds()); + connectionComponent.setBounds(getLocalBounds()); + + int pedalboardWidth = getWidth(); + int pedalboardHeight = pedalboardComponent.getRequiredHeight(getWidth()); + pedalboardComponent.setSize(pedalboardWidth, pedalboardHeight); + + using Track = Grid::TrackInfo; + using Px = Grid::Px; + using Fr = Grid::Fr; + grid.templateRows = {Track(Px(50)), Track(Fr(1))}; + grid.templateColumns = {Track(Fr(1))}; + grid.items = { + GridItem(topMenuBarComponent), + GridItem(pedalboardContainer) + }; + grid.performLayout(getLocalBounds()); } -void MainComponent::prepareToPlay(int samplesPerBlockExpected, double sampleRate) { - +void MainComponent::prepareToPlay(int samplesPerBlockExpected, + double sampleRate) {} + +void MainComponent::getNextAudioBlock( + const AudioSourceChannelInfo& bufferToFill) { + if (!this->isSoundMuted) { + if (&bufferToFill == nullptr) { + return; + } + if (bufferToFill.buffer == nullptr) { + return; + } + this->manager.apply(bufferToFill); + } else { + bufferToFill.clearActiveBufferRegion(); + } } -void MainComponent::getNextAudioBlock(const AudioSourceChannelInfo &bufferToFill) { - if (!this->isSoundMuted) - { - if (&bufferToFill == nullptr) { - return; - } - if (bufferToFill.buffer == nullptr) { - return; - } - this->manager.apply(bufferToFill); - } - else - { - bufferToFill.clearActiveBufferRegion(); - } -} - -void MainComponent::releaseResources() { -} +void MainComponent::releaseResources() {} diff --git a/src/components/AccountComponent.cpp b/src/components/AccountComponent.cpp index c5a0f2d..09662e2 100644 --- a/src/components/AccountComponent.cpp +++ b/src/components/AccountComponent.cpp @@ -1,122 +1,104 @@ #include "AccountComponent.h" #include "ApiClient.h" -AccountComponent::AccountComponent() -{ - // --- Titre --- - titleLabel.setText("Mon compte", juce::dontSendNotification); - titleLabel.setFont(juce::Font(24.0f, juce::Font::bold)); - titleLabel.setJustificationType(juce::Justification::centred); - addAndMakeVisible(titleLabel); - - // --- Labels de champs --- - emailLabel.setText("Adresse mail :", juce::dontSendNotification); - usernameLabel.setText("Nom d'utilisateur :", juce::dontSendNotification); - - for (auto* label : { &emailLabel, &usernameLabel }) - { - label->setFont(juce::Font(16.0f)); - label->setJustificationType(juce::Justification::centredLeft); - addAndMakeVisible(*label); - } - - // --- Valeurs (non éditables) --- - emailValueLabel.setText("utilisateur@example.com", juce::dontSendNotification); - usernameValueLabel.setText("NomUtilisateur", juce::dontSendNotification); - - for (auto* val : { &emailValueLabel, &usernameValueLabel }) - { - val->setFont(juce::Font(16.0f)); - val->setJustificationType(juce::Justification::centredLeft); - val->setColour(juce::Label::textColourId, juce::Colours::black); - addAndMakeVisible(*val); - } - - // --- Boutons --- - saveButton.setButtonText("Sauvegarder les réglages"); - importButton.setButtonText("Importer les réglages"); - changePasswordButton.setButtonText("Changer le mot de passe"); - - saveButton.onClick = [this]() { saveSettings(); }; - importButton.onClick = [this]() { importSettings(); }; - changePasswordButton.onClick = [this]() { changePassword(); }; - - for (auto* button : { &saveButton, &importButton, &changePasswordButton }) - addAndMakeVisible(*button); - - // --- Label de réponse (API ou autres actions) --- - responseLabel.setFont(juce::Font(14.0f)); - responseLabel.setJustificationType(juce::Justification::centred); - responseLabel.setColour(juce::Label::textColourId, juce::Colours::darkgrey); - responseLabel.setText("En attente de réponse...", juce::dontSendNotification); - addAndMakeVisible(responseLabel); - - // --- Appel API fictif --- - auto replyFunc = [this](const juce::String& content) { - apiResponseReceived(content); - }; - ApiClient::runHTTP({ "https://dummyjson.com/test" }, replyFunc); +namespace { +constexpr float titleFontSize = 24.0f; +constexpr float labelFontSize = 16.0f; +constexpr float responseFontSize = 14.0f; } -void AccountComponent::paint(juce::Graphics& g) -{ - g.fillAll(juce::Colours::lightgrey); +AccountComponent::AccountComponent() { + setupLabels(); + setupButtons(); + setupGrid(); + + // Dummy API call + auto replyFunc = [this](const String& content) { + apiResponseReceived(content); + }; + ApiClient::runHTTP({"https://dummyjson.com/test"}, replyFunc); +} + +void AccountComponent::setupLabels() { + titleLabel.setFont(FontOptions(titleFontSize, Font::bold)); + titleLabel.setJustificationType(Justification::centred); + addAndMakeVisible(titleLabel); + + for (auto* label : {&emailLabel, &usernameLabel}) { + label->setFont(FontOptions(labelFontSize)); + label->setJustificationType(Justification::centredLeft); + addAndMakeVisible(*label); + } + + for (auto* val : {&emailValueLabel, &usernameValueLabel}) { + val->setFont(FontOptions(labelFontSize)); + val->setJustificationType(Justification::centredLeft); + val->setColour(Label::textColourId, Colours::black); + addAndMakeVisible(*val); + } + + responseLabel.setFont(FontOptions(responseFontSize)); + responseLabel.setJustificationType(Justification::centred); + responseLabel.setColour(Label::textColourId, Colours::darkgrey); + addAndMakeVisible(responseLabel); +} + +void AccountComponent::setupButtons() { + saveButton.onClick = [this] { saveSettings(); }; + importButton.onClick = [this] { importSettings(); }; + + for (auto* button : {&saveButton, &importButton}) + addAndMakeVisible(*button); +} + +void AccountComponent::setupGrid() { + using namespace juce; + grid.templateRows = { + Grid::TrackInfo(40_px), // Title + Grid::TrackInfo(30_px), // Email label + Grid::TrackInfo(30_px), // Email value + Grid::TrackInfo(30_px), // Username label + Grid::TrackInfo(30_px), // Username value + Grid::TrackInfo(20_px), // Spacer + Grid::TrackInfo(40_px), // Change password button + Grid::TrackInfo(40_px), // Save button + Grid::TrackInfo(40_px), // Import button + Grid::TrackInfo(30_px) // Response label + }; + + grid.templateColumns = {Grid::TrackInfo(1_fr)}; + + grid.items = { + GridItem(titleLabel), + GridItem(emailLabel), + GridItem(emailValueLabel), + GridItem(usernameLabel), + GridItem(usernameValueLabel), + GridItem().withArea(6, 1), // Spacer + GridItem(saveButton), + GridItem(importButton), + GridItem(responseLabel) + }; } -void AccountComponent::resized() -{ - using namespace juce; - - grid.templateRows = { - Grid::TrackInfo(40_px), // Titre - Grid::TrackInfo(30_px), // Label email - Grid::TrackInfo(30_px), // Valeur email - Grid::TrackInfo(30_px), // Label username - Grid::TrackInfo(30_px), // Valeur username - Grid::TrackInfo(20_px), - Grid::TrackInfo(40_px), // Bouton changer mdp - Grid::TrackInfo(40_px), // Bouton sauvegarder - Grid::TrackInfo(40_px), // Bouton importer - Grid::TrackInfo(30_px) // Label réponse - }; - - grid.templateColumns = { Grid::TrackInfo(1_fr) }; - - grid.items = { - GridItem(titleLabel), - GridItem(emailLabel), - GridItem(emailValueLabel), - GridItem(usernameLabel), - GridItem(usernameValueLabel), - GridItem().withArea(6, 1), // espacement - GridItem(changePasswordButton), - GridItem(saveButton), - GridItem(importButton), - GridItem(responseLabel) - }; - - grid.performLayout(getLocalBounds().reduced(40)); +void AccountComponent::paint(Graphics& g) { + g.fillAll(Colours::lightgrey); } -void AccountComponent::apiResponseReceived(const juce::String& content) -{ - responseLabel.setText("Réponse API : " + content, juce::dontSendNotification); +void AccountComponent::resized() { + grid.performLayout(getLocalBounds().reduced(40)); } -void AccountComponent::saveSettings() -{ - responseLabel.setText("✅ Réglages sauvegardés !", juce::dontSendNotification); +void AccountComponent::apiResponseReceived(const String& content) { + responseLabel.setText("Réponse API : " + content, dontSendNotification); } -void AccountComponent::importSettings() -{ - emailValueLabel.setText("import@example.com", juce::dontSendNotification); - usernameValueLabel.setText("UtilisateurImporté", juce::dontSendNotification); - responseLabel.setText("📥 Réglages importés", juce::dontSendNotification); +void AccountComponent::saveSettings() { + responseLabel.setText("✅ Réglages sauvegardés !", dontSendNotification); } -void AccountComponent::changePassword() -{ - // Action fictive pour changer le mot de passe - responseLabel.setText("🔐 Redirection vers le changement de mot de passe...", juce::dontSendNotification); +void AccountComponent::importSettings() { + emailValueLabel.setText("import@example.com", dontSendNotification); + usernameValueLabel.setText("UtilisateurImporté", dontSendNotification); + responseLabel.setText("📥 Réglages importés", dontSendNotification); } diff --git a/src/components/LoginComponent.cpp b/src/components/LoginComponent.cpp index 0325044..77dbaaa 100644 --- a/src/components/LoginComponent.cpp +++ b/src/components/LoginComponent.cpp @@ -1,36 +1,49 @@ -#include "ConnectionComponent.h" +#include "LoginComponent.h" LoginComponent::LoginComponent() { - usernameField.setTextToShowWhenEmpty("Username", juce::Colours::grey); - passwordField.setTextToShowWhenEmpty("Password", juce::Colours::grey); - passwordField.setPasswordCharacter('*'); - skipButton.setButtonText("Skip"); - loginButton.setButtonText("Login"); - skipButton.onClick = [this] { skipButtonClicked(); }; + setupFields(); + setupButtons(); + layoutComponents(); } LoginComponent::~LoginComponent() = default; -void LoginComponent::skipButtonClicked() { - //addAndMakeVisible(new PedalboardComponent(manager.getPedalboard())); - setVisible(false); +void LoginComponent::setupFields() { + usernameField.setTextToShowWhenEmpty("Username", Colours::grey); + passwordField.setTextToShowWhenEmpty("Password", Colours::grey); + passwordField.setPasswordCharacter('*'); + addAndMakeVisible(usernameField); + addAndMakeVisible(passwordField); +} + +void LoginComponent::setupButtons() { + loginButton.onClick = [] { + // TODO: Implement login logic + }; + skipButton.onClick = [this] { skipButtonClicked(); }; + addAndMakeVisible(loginButton); + addAndMakeVisible(skipButton); } -void LoginComponent::paint(juce::Graphics &g) { - g.fillAll(juce::Colours::white); - addAndMakeVisible(usernameField); - addAndMakeVisible(passwordField); - addAndMakeVisible(loginButton); - addAndMakeVisible(skipButton); +void LoginComponent::paint(Graphics& g) { + g.fillAll(Colours::white); } void LoginComponent::resized() { - const auto area = getLocalBounds().reduced(50); - const auto halfWidth = area.getWidth() / 2; - const auto halfHeight = area.getHeight() / 2; - - usernameField.setBounds(halfWidth - 100, halfHeight - 80, 300, 40); - passwordField.setBounds(halfWidth - 100, halfHeight - 30, 300, 40); - loginButton.setBounds(halfWidth - 100, halfHeight + 30, 300, 40); - skipButton.setBounds(halfWidth - 100, halfHeight + 80, 300, 40); + layoutComponents(); +} + +void LoginComponent::layoutComponents() { + auto area = getLocalBounds().reduced(50); + auto halfWidth = area.getWidth() / 2; + auto halfHeight = area.getHeight() / 2; + + usernameField.setBounds(halfWidth - 100, halfHeight - 80, 300, 40); + passwordField.setBounds(halfWidth - 100, halfHeight - 30, 300, 40); + loginButton.setBounds(halfWidth - 100, halfHeight + 30, 300, 40); + skipButton.setBounds(halfWidth - 100, halfHeight + 80, 300, 40); +} + +void LoginComponent::skipButtonClicked() { + setVisible(false); } \ No newline at end of file diff --git a/src/components/effects/BasePedalComponent.cpp b/src/components/effects/BasePedalComponent.cpp index c72108a..9409b25 100644 --- a/src/components/effects/BasePedalComponent.cpp +++ b/src/components/effects/BasePedalComponent.cpp @@ -1,68 +1,68 @@ #include "BasePedalComponent.h" #include "ResourceManager.h" -BasePedalComponent::BasePedalComponent(AbstractEffect* effect) : EffectComponent(effect) -{ - isEnabled = effect->isEnabled; +BasePedalComponent::BasePedalComponent(AbstractEffect* effect) : + EffectComponent(effect) { + isEnabled = effect->isEnabled; } BasePedalComponent::~BasePedalComponent() = default; -void BasePedalComponent::paint(juce::Graphics &g) { - g.setColour(primaryColor); - g.fillRoundedRectangle(0, 0, getWidth(), getHeight(), 15); +void BasePedalComponent::paint(Graphics& g) { + g.setColour(primaryColor); + g.fillRoundedRectangle(0, 0, getWidth(), getHeight(), 15); } void BasePedalComponent::resized() { - pedalLayout.performLayout(getLocalBounds()); + pedalLayout.performLayout(getLocalBounds()); } void BasePedalComponent::onEnableButtonClicked() { - *isEnabled = !(*isEnabled); - enablePedalButton.setToggleState(*isEnabled, juce::dontSendNotification); - isEnabledIndicator->togglePower(*isEnabled); + *isEnabled = !(*isEnabled); + enablePedalButton.setToggleState(*isEnabled, dontSendNotification); + isEnabledIndicator->togglePower(*isEnabled); } void BasePedalComponent::initializePedal() { - isEnabledIndicator = new PedalPowerIndicatorComponent(*isEnabled); - pedalLabel = new juce::Label(); - pedalLabel->setText(getEffect()->effectName, juce::dontSendNotification); - pedalLabel->setJustificationType(juce::Justification::centred); - pedalLabel->setFont(juce::Font(30.0f, juce::Font::bold)); + isEnabledIndicator = new PedalPowerIndicatorComponent(*isEnabled); + pedalLabel = new Label(); + pedalLabel->setText(getEffect()->effectName, dontSendNotification); + pedalLabel->setJustificationType(Justification::centred); + pedalLabel->setFont(FontOptions(30.0f, Font::bold)); - juce::Image powerImage = ResourceManager::loadImage("resources/icons/power.png"); - if (powerImage.isValid()) { - enablePedalButton.setImages(true, true, true, - powerImage, 1.0f, {}, - powerImage, 1.0f, {}, - powerImage, 1.0f, {}); - enablePedalButton.onClick = [this] { - this->onEnableButtonClicked(); - }; - addAndMakeVisible(enablePedalButton); - } else { - DBG("Erreur : image power.png introuvable ou invalide."); - } + Image powerImage = ResourceManager::loadImage("resources/icons/power.png"); + if (powerImage.isValid()) { + enablePedalButton.setImages(true, true, true, + powerImage, 1.0f, {}, + powerImage, 1.0f, {}, + powerImage, 1.0f, {}); + enablePedalButton.onClick = [this] { + this->onEnableButtonClicked(); + }; + addAndMakeVisible(enablePedalButton); + } else { + DBG("Erreur : image power.png introuvable ou invalide."); + } - addAndMakeVisible(*pedalLabel); - addAndMakeVisible(*isEnabledIndicator); + addAndMakeVisible(*pedalLabel); + addAndMakeVisible(*isEnabledIndicator); - using Track = juce::Grid::TrackInfo; - using Fr = juce::Grid::Fr; - pedalLayout.templateRows = { - Track (Fr (2)), - Track (Fr (1)), - Track (Fr (1)), - Track (Fr (1)) - }; - pedalLayout.templateColumns = { - Track (Fr (1)) - }; + using Track = Grid::TrackInfo; + using Fr = Grid::Fr; + pedalLayout.templateRows = { + Track(Fr(2)), + Track(Fr(1)), + Track(Fr(1)), + Track(Fr(1)) + }; + pedalLayout.templateColumns = { + Track(Fr(1)) + }; - pedalLayout.items = { - juce::GridItem(*settingsLayout), - juce::GridItem(enablePedalButton), - juce::GridItem(*isEnabledIndicator), - juce::GridItem(*pedalLabel), - }; + pedalLayout.items = { + GridItem(*settingsLayout), + GridItem(enablePedalButton), + GridItem(*isEnabledIndicator), + GridItem(*pedalLabel), + }; } \ No newline at end of file diff --git a/src/components/effects/DelayEffectComponent.cpp b/src/components/effects/DelayEffectComponent.cpp index a38ade3..744780c 100644 --- a/src/components/effects/DelayEffectComponent.cpp +++ b/src/components/effects/DelayEffectComponent.cpp @@ -1,61 +1,71 @@ #include "DelayEffectComponent.h" -DelayEffectComponent::DelayEffectComponent() : BasePedalComponent(new DelayEffect()) {} - -DelayEffectComponent::DelayEffectComponent(AbstractEffect* effect) : BasePedalComponent(effect) { - if (DelayEffect* delayEffect = dynamic_cast(effect)) { - primaryColor = juce::Colours::mediumblue; - using Track = juce::Grid::TrackInfo; - using Fr = juce::Grid::Fr; - grid.templateRows = { Track (Fr (1)), Track (Fr (3)) }; - grid.templateColumns = { Track (Fr (1)), Track (Fr (1)) }; - grid.items = { - juce::GridItem(rateLabel), - juce::GridItem(delayLabel), - juce::GridItem(rateSlider), - juce::GridItem(delaySlider), - }; - rateSlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); - rateSlider.setTextBoxStyle(juce::Slider::TextBoxBelow,false, 100, 20); - rateSlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite); - rateSlider.setTextValueSuffix("%"); - rateSlider.setTitle("Rate"); - rateSlider.setRange(0.0, 100.0, 1); - rateSlider.setValue(50.0); - rateSlider.onValueChange = [this, delayEffect] { delayEffect->setRate(rateSlider.getValue()); }; - - rateLabel.setText("Rate", juce::dontSendNotification); - rateLabel.setJustificationType(Justification::centred); - rateLabel.attachToComponent(&rateSlider, false); - - delaySlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); - delaySlider.setTextValueSuffix("ms"); - delaySlider.setTitle("Delay"); - delaySlider.setTextBoxStyle(juce::Slider::TextBoxBelow,false, 100, 20); - delaySlider.setColour(juce::Slider::textBoxOutlineColourId, juce::Colours::transparentWhite); - delaySlider.setRange(0.0, 3000.0, 10); - delaySlider.setValue(500.0); - delaySlider.onValueChange = [this, delayEffect] { delayEffect->setDelay(delaySlider.getValue()); }; - - delayLabel.setText("Delay", juce::dontSendNotification); - delayLabel.setJustificationType(Justification::centred); - delayLabel.attachToComponent(&delaySlider, false); - - rateSlider.setBounds(0, 0, 200, 200); - delaySlider.setBounds(300, 0, 200, 200); - rateLabel.setBounds(rateSlider.getX(), rateSlider.getY() + 20, rateSlider.getWidth(), 20); - delayLabel.setBounds(delaySlider.getX(), delaySlider.getY() + 20, delaySlider.getWidth(), 20); - - settingsLayout = new PedalSettingsLayoutComponent(&grid); - - addAndMakeVisible(rateSlider); - addAndMakeVisible(rateLabel); - addAndMakeVisible(delaySlider); - addAndMakeVisible(delayLabel); - - this->initializePedal(); - setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); - } +DelayEffectComponent::DelayEffectComponent() : BasePedalComponent( + new DelayEffect()) {} + +DelayEffectComponent::DelayEffectComponent(AbstractEffect* effect) : + BasePedalComponent(effect) { + if (auto delayEffect = dynamic_cast(effect)) { + primaryColor = Colours::mediumblue; + using Track = Grid::TrackInfo; + using Fr = Grid::Fr; + grid.templateRows = {Track(Fr(1)), Track(Fr(3))}; + grid.templateColumns = {Track(Fr(1)), Track(Fr(1))}; + grid.items = { + GridItem(rateLabel), + GridItem(delayLabel), + GridItem(rateSlider), + GridItem(delaySlider), + }; + rateSlider.setSliderStyle(Slider::SliderStyle::RotaryVerticalDrag); + rateSlider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20); + rateSlider.setColour(Slider::textBoxOutlineColourId, + Colours::transparentWhite); + rateSlider.setTextValueSuffix("%"); + rateSlider.setTitle("Rate"); + rateSlider.setRange(0.0, 100.0, 1); + rateSlider.setValue(50.0); + rateSlider.onValueChange = [this, delayEffect] { + delayEffect->setRate(rateSlider.getValue()); + }; + + rateLabel.setText("Rate", dontSendNotification); + rateLabel.setJustificationType(Justification::centred); + rateLabel.attachToComponent(&rateSlider, false); + + delaySlider.setSliderStyle(Slider::SliderStyle::RotaryVerticalDrag); + delaySlider.setTextValueSuffix("ms"); + delaySlider.setTitle("Delay"); + delaySlider.setTextBoxStyle(Slider::TextBoxBelow, false, 100, 20); + delaySlider.setColour(Slider::textBoxOutlineColourId, + Colours::transparentWhite); + delaySlider.setRange(0.0, 3000.0, 10); + delaySlider.setValue(500.0); + delaySlider.onValueChange = [this, delayEffect] { + delayEffect->setDelay(delaySlider.getValue()); + }; + + delayLabel.setText("Delay", dontSendNotification); + delayLabel.setJustificationType(Justification::centred); + delayLabel.attachToComponent(&delaySlider, false); + + rateSlider.setBounds(0, 0, 200, 200); + delaySlider.setBounds(300, 0, 200, 200); + rateLabel.setBounds(rateSlider.getX(), rateSlider.getY() + 20, + rateSlider.getWidth(), 20); + delayLabel.setBounds(delaySlider.getX(), delaySlider.getY() + 20, + delaySlider.getWidth(), 20); + + settingsLayout = new PedalSettingsLayoutComponent(&grid); + + addAndMakeVisible(rateSlider); + addAndMakeVisible(rateLabel); + addAndMakeVisible(delaySlider); + addAndMakeVisible(delayLabel); + + this->initializePedal(); + setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + } } DelayEffectComponent::~DelayEffectComponent() = default; \ No newline at end of file diff --git a/src/utils/ModalOverlayComponent.cpp b/src/utils/ModalOverlayComponent.cpp index c4c7302..936a1a5 100644 --- a/src/utils/ModalOverlayComponent.cpp +++ b/src/utils/ModalOverlayComponent.cpp @@ -15,13 +15,13 @@ ModalOverlayComponent::ModalOverlayComponent(std::string viewName, juce::Compone viewNameLabel.setFont(juce::FontOptions(32.0f, juce::Font::bold)); viewNameLabel.setJustificationType(juce::Justification::centred); - juce::Image closeImage = ResourceManager::loadImage("resources/icons/close.png"); + juce::Image closeImage = ResourceManager::loadImage("resources/icons/xmark.png"); if (closeImage.isValid()) { closeOverlayButton.setImages(true, true, true,closeImage, 1.0f, {}, closeImage, 1.0f, {},closeImage, 1.0f, {}); closeOverlayButton.setSize(closeImage.getWidth(), closeImage.getHeight()); addAndMakeVisible(closeOverlayButton); } else { - DBG("Erreur : image close.png introuvable ou invalide."); + DBG("Erreur : image xmark.png introuvable ou invalide."); } closeOverlayButton.onClick = [this]() { this->onCloseOverlayButtonClicked(); }; } diff --git a/src/utils/ToggleButtonComponent.cpp b/src/utils/ToggleButtonComponent.cpp index dde3a5f..07b3baf 100644 --- a/src/utils/ToggleButtonComponent.cpp +++ b/src/utils/ToggleButtonComponent.cpp @@ -1,25 +1,42 @@ #include "ToggleButtonComponent.h" -ToggleButtonComponent::ToggleButtonComponent() -{ - setMouseCursor(juce::MouseCursor::PointingHandCursor); - setSize(100, 50); +namespace { +constexpr int buttonWidth = 100; +constexpr int buttonHeight = 50; +constexpr float toggleWidth = 50.0f; +constexpr float toggleHeight = 25.0f; +constexpr float toggleRadius = 10.0f; +constexpr float ellipseDiameter = 19.0f; +constexpr float ellipseYOffset = 3.0f; +constexpr float ellipseXOffsetOn = 0.0f; +constexpr float ellipseXOffsetOff = -22.0f; +} + +ToggleButtonComponent::ToggleButtonComponent() { + setMouseCursor(MouseCursor::PointingHandCursor); + setSize(buttonWidth, buttonHeight); } ToggleButtonComponent::~ToggleButtonComponent() = default; -void ToggleButtonComponent::paint(juce::Graphics& g) -{ - g.setColour(juce::Colours::darkgrey); - g.fillRoundedRectangle(getWidth() / 2 - 25, getHeight() / 2 - 12.5, 50, 25, 10.0f); - if (getToggleState()) - { - g.setColour(juce::Colours::green); - g.fillEllipse(getWidth() / 2, (getHeight() / 2 - 12.5) + 3, 19, 19); - } - else - { - g.setColour(juce::Colours::red); - g.fillEllipse(getWidth() / 2 - 22, (getHeight() / 2 - 12.5) + 3, 19, 19); - } +void ToggleButtonComponent::paint(Graphics& g) { + const auto centerX = static_cast(getWidth()) / 2.0f; + const auto centerY = static_cast(getHeight()) / 2.0f; + + g.setColour(Colours::darkgrey); + g.fillRoundedRectangle(centerX - toggleWidth / 2.0f, + centerY - toggleHeight / 2.0f, + toggleWidth, toggleHeight, toggleRadius); + + if (getToggleState()) { + g.setColour(Colours::green); + g.fillEllipse(centerX + ellipseXOffsetOn, + centerY - toggleHeight / 2.0f + ellipseYOffset, + ellipseDiameter, ellipseDiameter); + } else { + g.setColour(Colours::red); + g.fillEllipse(centerX + ellipseXOffsetOff, + centerY - toggleHeight / 2.0f + ellipseYOffset, + ellipseDiameter, ellipseDiameter); + } } \ No newline at end of file From 4831bb46eb1774573e81b896dfc1bf7b13ecfc29 Mon Sep 17 00:00:00 2001 From: Akinimaginable Date: Sun, 22 Jun 2025 21:29:36 +0200 Subject: [PATCH 2/3] Topbar responsivity --- include/TopMenuBarComponent.h | 89 ++++----- src/components/TopMenuBarComponent.cpp | 240 +++++++++++++------------ 2 files changed, 159 insertions(+), 170 deletions(-) diff --git a/include/TopMenuBarComponent.h b/include/TopMenuBarComponent.h index 5778bbe..ad810ad 100644 --- a/include/TopMenuBarComponent.h +++ b/include/TopMenuBarComponent.h @@ -1,61 +1,38 @@ #pragma once -#include -#include - +#include +#include "AccountComponent.h" #include "ModalOverlayComponent.h" #include "SettingsComponent.h" -#include "AccountComponent.h" -/** - * @brief Represents a graphical component that contains and displays the top menu bar. - */ -class TopMenuBarComponent : public juce::Component { - public: - /** - * @brief Initializes a new instance of the TopMenuBarComponent class. - */ - explicit TopMenuBarComponent(juce::AudioDeviceManager& deviceManager, bool* isMuted = nullptr); - - /** - * @brief Destroys the instance of the TopMenuBarComponent class. - */ - ~TopMenuBarComponent() override; - - /** - * @brief Determines how to display the component. - * @param g The JUCE graphics context that paints the component. - */ - void paint(juce::Graphics &g) override; - - /** - * @brief Determines what to do when the component is resized. - */ - void resized() override; - - private: - /** - * @brief The flexBox component that contains the menu items. - */ - juce::FlexBox flexBox; - - ModalOverlayComponent* modalOverlay; - - /** - * @brief The image button to open settings. - */ - juce::ImageButton settingsButton; - SettingsComponent* settingsComponent; - - juce::ImageButton accountButton; - AccountComponent* accountComponent; - - bool* isSoundMuted = nullptr; - juce::ImageButton muteButton; - - void openSettingsPopup(juce::AudioDeviceManager& deviceManager); - void openAccountPopup(); - void toggleMute(); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopMenuBarComponent) -}; \ No newline at end of file +class TopMenuBarComponent : public Component { +public: + explicit TopMenuBarComponent(AudioDeviceManager& deviceManager, + bool* isMuted = nullptr); + ~TopMenuBarComponent() override; + + void paint(Graphics& g) override; + void resized() override; + +private: + int buttonSize = 32; + float gap = 16; + + FlexBox flexBox; + + ImageButton accountButton; + ImageButton muteButton; + ImageButton settingsButton; + + AccountComponent* accountComponent; + ModalOverlayComponent* modalOverlay; + SettingsComponent* settingsComponent; + + bool* isSoundMuted = nullptr; + + void openSettingsPopup(AudioDeviceManager& deviceManager); + void openAccountPopup(); + void toggleMute(); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopMenuBarComponent) +}; diff --git a/src/components/TopMenuBarComponent.cpp b/src/components/TopMenuBarComponent.cpp index 970fe4e..322d024 100644 --- a/src/components/TopMenuBarComponent.cpp +++ b/src/components/TopMenuBarComponent.cpp @@ -1,136 +1,148 @@ #include "AccountComponent.h" +#include "ModalOverlayComponent.h" #include "PopupContentComponent.h" #include "SettingsComponent.h" #include "TopMenuBarComponent.h" - -#include "ModalOverlayComponent.h" #include "ResourceManager.h" - -TopMenuBarComponent::TopMenuBarComponent(juce::AudioDeviceManager& deviceManager, bool* isSoundMuted) -{ - this->isSoundMuted = isSoundMuted; - - - juce::Image settingsImage = ResourceManager::loadImage("resources/icons/settings.png"); - if (settingsImage.isValid()) { - settingsButton.setImages(true, true, true,settingsImage, 1.0f, {},settingsImage, 1.0f, {},settingsImage, 1.0f, {}); - settingsButton.setSize(settingsImage.getWidth(), settingsImage.getHeight()); - addAndMakeVisible(settingsButton); - } else { - DBG("Erreur : image settings.png introuvable ou invalide."); - } - - juce::Image accountImage = ResourceManager::loadImage("resources/icons/account.png"); - if (accountImage.isValid()) { - accountButton.setImages(true, true, true,accountImage, 1.0f, {}, accountImage, 1.0f, {},accountImage, 1.0f, {}); - accountButton.setSize(accountImage.getWidth(), accountImage.getHeight()); - addAndMakeVisible(accountButton); - } else { - DBG("Erreur : image account.png introuvable ou invalide."); - } - - juce::Image muteImage = ResourceManager::loadImage("resources/icons/unmute.png"); - if (muteImage.isValid()) { - muteButton.setImages(true, true, true, muteImage, 1.0f, {}, muteImage, 1.0f, {}, muteImage, 1.0f, {}); - muteButton.setSize(muteImage.getWidth(), muteImage.getHeight()); - addAndMakeVisible(muteButton); - } else { - DBG("Erreur : image mute.png introuvable ou invalide."); - } - - - #if !JUCE_IOS - settingsButton.onClick = [this, &deviceManager]() { openSettingsPopup(deviceManager); }; - #endif - accountButton.onClick = [this]() { openAccountPopup(); }; - muteButton.onClick = [this]() { toggleMute(); }; - - settingsButton.setBounds(0, 0, 100, 50); - accountButton.setBounds(0, 0, 100, 50); - muteButton.setBounds(0, 0, 100, 50); - flexBox.justifyContent = juce::FlexBox::JustifyContent::flexEnd; - flexBox.alignItems = juce::FlexBox::AlignItems::center; - flexBox.items.add( - juce::FlexItem(muteButton).withWidth(muteButton.getWidth()).withHeight(muteButton.getHeight())); - flexBox.items.add( - juce::FlexItem(settingsButton).withWidth(settingsButton.getWidth()).withHeight(settingsButton.getHeight())); - flexBox.items.add( - juce::FlexItem(accountButton).withWidth(accountButton.getWidth()).withHeight(accountButton.getHeight())); +TopMenuBarComponent::TopMenuBarComponent(AudioDeviceManager& deviceManager, + bool* isMuted) { + this->isSoundMuted = isMuted; + + Image settingsImage = ResourceManager::loadImage( + "resources/icons/settings.png"); + if (settingsImage.isValid()) { + settingsButton.setImages(true, true, true, settingsImage, 1.0f, {}, + settingsImage, 1.0f, {}, settingsImage, 1.0f, + {}); + settingsButton.setSize(settingsImage.getWidth(), + settingsImage.getHeight()); + addAndMakeVisible(settingsButton); + } else { + DBG("Erreur : image settings.png introuvable ou invalide."); + } + + Image accountImage = ResourceManager::loadImage( + "resources/icons/account.png"); + if (accountImage.isValid()) { + accountButton.setImages(true, true, true, accountImage, 1.0f, {}, + accountImage, 1.0f, {}, accountImage, 1.0f, {}); + accountButton.setSize(accountImage.getWidth(), + accountImage.getHeight()); + addAndMakeVisible(accountButton); + } else { + DBG("Erreur : image account.png introuvable ou invalide."); + } + + Image muteImage = ResourceManager::loadImage("resources/icons/unmute.png"); + if (muteImage.isValid()) { + muteButton.setImages(true, true, true, muteImage, 1.0f, {}, muteImage, + 1.0f, {}, muteImage, 1.0f, {}); + muteButton.setSize(muteImage.getWidth(), muteImage.getHeight()); + addAndMakeVisible(muteButton); + } else { + DBG("Erreur : image mute.png introuvable ou invalide."); + } + +#if !JUCE_IOS + settingsButton.onClick = [this, &deviceManager] { + openSettingsPopup(deviceManager); + }; +#endif + accountButton.onClick = [this] { openAccountPopup(); }; + muteButton.onClick = [this] { toggleMute(); }; + + flexBox.justifyContent = FlexBox::JustifyContent::flexEnd; + flexBox.alignItems = FlexBox::AlignItems::center; + flexBox.items.add( + FlexItem(muteButton).withWidth(buttonSize).withHeight(buttonSize). + withMargin({0, gap, 0, 0})); + flexBox.items.add( + FlexItem(settingsButton).withWidth(buttonSize).withHeight(buttonSize). + withMargin({0, gap, 0, 0})); + flexBox.items.add( + FlexItem(accountButton).withWidth(buttonSize).withHeight(buttonSize). + withMargin({0, gap, 0, 0})); } TopMenuBarComponent::~TopMenuBarComponent() = default; -void TopMenuBarComponent::paint(juce::Graphics& g) -{ +void TopMenuBarComponent::paint(Graphics& g) { + const ColourGradient gradient(Colours::black, 0, 0, + Colours::transparentBlack, 0, + static_cast(getHeight()), false); + g.setGradientFill(gradient); + g.fillAll(); + + const FontOptions font("Times New Roman", 24.0f, Font::bold | Font::italic); + g.setFont(font); + g.setColour(Colours::white); + const int topMargin = (getHeight() - 24) / 2; + + g.drawText("kAmp", gap, topMargin, 80, 24, Justification::left); } -void TopMenuBarComponent::resized() -{ - auto* mainWindow = getTopLevelComponent(); - if (mainWindow == nullptr) - return; - if (modalOverlay != nullptr) - { - modalOverlay->setBounds(mainWindow->getLocalBounds()); - } - if (settingsComponent != nullptr) - { - settingsComponent->setBounds(mainWindow->getLocalBounds()); - } - if (accountComponent != nullptr) - { - accountComponent->setBounds(mainWindow->getLocalBounds()); - } - flexBox.performLayout(getLocalBounds()); +void TopMenuBarComponent::resized() { + auto* mainWindow = getTopLevelComponent(); + if (mainWindow == nullptr) + return; + if (modalOverlay != nullptr) { + modalOverlay->setBounds(mainWindow->getLocalBounds()); + } + if (settingsComponent != nullptr) { + settingsComponent->setBounds(mainWindow->getLocalBounds()); + } + if (accountComponent != nullptr) { + accountComponent->setBounds(mainWindow->getLocalBounds()); + } + flexBox.performLayout(getLocalBounds()); } -void TopMenuBarComponent::openSettingsPopup(juce::AudioDeviceManager& deviceManager) -{ - settingsComponent = new SettingsComponent(deviceManager); - auto* mainWindow = getTopLevelComponent(); - if (mainWindow == nullptr) - return; +void TopMenuBarComponent::openSettingsPopup(AudioDeviceManager& deviceManager) { + settingsComponent = new SettingsComponent(deviceManager); + auto* mainWindow = getTopLevelComponent(); + if (mainWindow == nullptr) + return; - modalOverlay = new ModalOverlayComponent("Audio settings", settingsComponent); - mainWindow->addAndMakeVisible(modalOverlay); - modalOverlay->setBounds(mainWindow->getLocalBounds()); + modalOverlay = new ModalOverlayComponent("Audio settings", + settingsComponent); + mainWindow->addAndMakeVisible(modalOverlay); + modalOverlay->setBounds(mainWindow->getLocalBounds()); } -void TopMenuBarComponent::openAccountPopup() -{ - accountComponent = new AccountComponent(); - auto* mainWindow = getTopLevelComponent(); - if (mainWindow == nullptr) - return; +void TopMenuBarComponent::openAccountPopup() { + accountComponent = new AccountComponent(); + auto* mainWindow = getTopLevelComponent(); + if (mainWindow == nullptr) + return; - modalOverlay = new ModalOverlayComponent("Account", accountComponent); - mainWindow->addAndMakeVisible(modalOverlay); - modalOverlay->setBounds(mainWindow->getLocalBounds()); + modalOverlay = new ModalOverlayComponent("Account", accountComponent); + mainWindow->addAndMakeVisible(modalOverlay); + modalOverlay->setBounds(mainWindow->getLocalBounds()); } -void TopMenuBarComponent::toggleMute() -{ - if (*this->isSoundMuted) - { - juce::Image muteImage = ResourceManager::loadImage("resources/icons/unmute.png"); - if (muteImage.isValid()) { - muteButton.setImages(false, true, true, muteImage, 1.0f, {}, muteImage, 1.0f, {}, muteImage, 1.0f, {}); - addAndMakeVisible(muteButton); - } else { - DBG("Erreur : image mute.png introuvable ou invalide."); - } - } - else - { - juce::Image muteImage = ResourceManager::loadImage("resources/icons/mute.png"); - if (muteImage.isValid()) { - muteButton.setImages(false, true, true, muteImage, 1.0f, {}, muteImage, 1.0f, {}, muteImage, 1.0f, {}); - addAndMakeVisible(muteButton); - } else { - DBG("Erreur : image mute.png introuvable ou invalide."); - } - } - *(this->isSoundMuted) = !(*(this->isSoundMuted)); +void TopMenuBarComponent::toggleMute() { + if (*this->isSoundMuted) { + Image muteImage = ResourceManager::loadImage( + "resources/icons/unmute.png"); + if (muteImage.isValid()) { + muteButton.setImages(false, true, true, muteImage, 1.0f, {}, + muteImage, 1.0f, {}, muteImage, 1.0f, {}); + addAndMakeVisible(muteButton); + } else { + DBG("Erreur : image mute.png introuvable ou invalide."); + } + } else { + Image muteImage = + ResourceManager::loadImage("resources/icons/mute.png"); + if (muteImage.isValid()) { + muteButton.setImages(false, true, true, muteImage, 1.0f, {}, + muteImage, 1.0f, {}, muteImage, 1.0f, {}); + addAndMakeVisible(muteButton); + } else { + DBG("Erreur : image mute.png introuvable ou invalide."); + } + } + *(this->isSoundMuted) = !(*(this->isSoundMuted)); } From 3198f15998cf46e18ea18c5419332f8ceb80a585 Mon Sep 17 00:00:00 2001 From: Akinimaginable Date: Mon, 23 Jun 2025 09:35:09 +0200 Subject: [PATCH 3/3] Use JUCE fork and remove docs from repo --- .gitmodules | 5 +---- docs | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 160000 docs diff --git a/.gitmodules b/.gitmodules index 8851e64..cb168bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "docs"] - path = docs - url = git@github.com:k147-studio/docs.git [submodule "JUCE"] path = JUCE - url = https://github.com/juce-framework/JUCE.git + url = https://github.com/k147-studio/JUCE.git diff --git a/docs b/docs deleted file mode 160000 index 80463c5..0000000 --- a/docs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 80463c5e6fd68b4b4966701ce6b1dfa24e52da98