diff --git a/CMakeLists.txt b/CMakeLists.txt index 8273d850..d88a6e52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ set(Impacto_Src src/ui/widgets/group.cpp src/ui/widgets/carousel.cpp src/ui/widgets/cgviewer.cpp + src/ui/widgets/clickbutton.cpp src/ui/widgets/mo6tw/titlebutton.cpp src/ui/widgets/mo6tw/saveentrybutton.cpp src/ui/widgets/mo6tw/tipsentrybutton.cpp @@ -244,6 +245,10 @@ set(Impacto_Src src/ui/widgets/cc/backlogentry.cpp src/ui/widgets/cc/titlebutton.cpp src/ui/widgets/cclcc/titlebutton.cpp + src/ui/widgets/cclcc/optionsentry.cpp + src/ui/widgets/cclcc/optionsbinarybutton.cpp + src/ui/widgets/cclcc/optionsslider.cpp + src/ui/widgets/cclcc/optionsvoiceslider.cpp src/ui/widgets/cclcc/saveentrybutton.cpp src/ui/widgets/cclcc/sysmenubutton.cpp src/ui/widgets/cclcc/tipsentrybutton.cpp @@ -538,6 +543,7 @@ set(Impacto_Header src/ui/widgets/group.h src/ui/widgets/carousel.h src/ui/widgets/cgviewer.h + src/ui/widgets/clickbutton.h src/ui/widgets/mo6tw/titlebutton.h src/ui/widgets/mo6tw/saveentrybutton.h src/ui/widgets/mo6tw/tipsentrybutton.h @@ -556,6 +562,10 @@ set(Impacto_Header src/ui/widgets/cc/backlogentry.h src/ui/widgets/cc/titlebutton.h src/ui/widgets/cclcc/titlebutton.h + src/ui/widgets/cclcc/optionsentry.h + src/ui/widgets/cclcc/optionsbinarybutton.h + src/ui/widgets/cclcc/optionsslider.h + src/ui/widgets/cclcc/optionsvoiceslider.h src/renderer/3d/camera.h src/renderer/3d/model.h diff --git a/profiles/cclcc/hud/optionsmenu.lua b/profiles/cclcc/hud/optionsmenu.lua index 6e7d87a3..ad6c0800 100644 --- a/profiles/cclcc/hud/optionsmenu.lua +++ b/profiles/cclcc/hud/optionsmenu.lua @@ -1,15 +1,272 @@ root.OptionsMenu = { Type = OptionsMenuType.CCLCC, DrawType = DrawComponentType.SystemMenu, + + FadeInDuration = 0.5, + FadeOutDuration = 0.5, + + HighlightColor = { X = 0.94, Y = 0.49, Z = 0.59 }, + BackgroundSprite = "OptionsBackground", - FadeInDuration = 0.2, - FadeOutDuration = 0.2, - SliderTrackSprite = "OptionsBackground", - SliderFillSprite = "OptionsBackground", - SliderThumbSprite = "OptionsBackground" + BackgroundPosition = { X = 179, Y = 0 }, + BackgroundFadeStartPosition = { X = 179, Y = -375 }, + + PointerSprite = "OptionsPointer", + PointerOffset = { X = 13, Y = 2 }, + + HeaderSprite = "OptionsHeader", + HeaderPosition = { X = 10, Y = 10 }, + PageHeaderSprites = {}, + PageHeaderPosition = { X = 696, Y = 182 }, + + PagePanelSprite = "OptionsPagePanel", + PagePanelPosition = { X = -114, Y = 0 }, + PagePanelFadeStartPosition = { X = -464, Y = 0 }, + PagePanelSprites = {}, + PagePanelIconOffsets = { + { X = 259, Y = 73 }, + { X = 235, Y = 333 }, + { X = 261, Y = 585 }, + { X = 238, Y = 831 } + }, + PagePanelHoverBounds = { + { X = 168, Y = 79, Width = 150, Height = 183 }, + { X = 147, Y = 343, Width = 154, Height = 177 }, + { X = 169, Y = 591, Width = 146, Height = 181 }, + { X = 149, Y = 837, Width = 150, Height = 179 }, + }, + PoleAnimation = "OptionsPoleAnimation", + + SliderTrackSprite = "OptionsSliderTrack", + SliderTrackOffset = { X = 766, Y = 0 }, + VoiceSliderTrackSprite = "OptionsVoiceSliderTrack", + VoiceSliderOffset = { X = 114, Y = 57 }, + BinaryBoxSprite = "OptionsBinaryBox", + BinaryBoxOffset = { X = 914, Y = 0 }, + SliderSpeed = 1.0, + + SkipReadSprite = "OptionsSkipRead", + SkipAllSprite = "OptionsSkipAll", + OnSprite = "OptionsOn", + OffSprite = "OptionsOff", + YesSprite = "OptionsYes", + NoSprite = "OptionsNo", + + GuideSprite = "OptionsGuide", + VoiceGuideSprite = "OptionsVoiceGuide", + GuidePosition = { X = 0, Y = 986 }, + GuideFadeStartPosition = { X = 0, Y = 1375 }, + + EntriesStartPosition = { X = 419, Y = 313 }, + EntriesVerticalOffset = 126, + SoundEntriesStartPosition = { X = 419, Y = 333 }, + SoundEntriesVerticalOffset = 70, + VoiceEntriesOffset = { X = 408, Y = 160 }, + EntryDimensions = { X = 1212, Y = 50 }, + VoiceEntryDimensions = { X = 374, Y = 104 }, + + LabelSprites = {}, + LabelOffset = { X = 102, Y = -2 }, + NametagSprites = {}, + NametagOffset = { X = 116, Y = 4 }, + PortraitSprites = {}, + PortraitOffset = { X = 3, Y = 2 }, + VoicePosition = { X = 454, Y = 310 }, + + MinButtonHoldTime = 0.5, + ButtonHoldTimeInterval = 0.05, + + MenuMask = "MenuMask", }; root.Sprites["OptionsBackground"] = { - Sheet = "Title", - Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 } -}; \ No newline at end of file + Sheet = "Config", + Bounds = { X = 0, Y = 0, Width = 1571, Height = 1089 } +}; + +root.Sprites["OptionsSliderTrack"] = { + Sheet = "Config", + Bounds = { X = 684, Y = 1291, Width = 446, Height = 50 } +}; + +root.Sprites["OptionsVoiceSliderTrack"] = { + Sheet = "Config", + Bounds = { X = 951, Y = 1239, Width = 250, Height = 35 } +}; + +root.Sprites["OptionsBinaryBox"] = { + Sheet = "Config", + Bounds = { X = 684, Y = 1343, Width = 298, Height = 50 } +}; + +root.Sprites["OptionsPointer"] = { + Sheet = "Config", + Bounds = { X = 697, Y = 1241, Width = 71, Height = 45 } +}; + +root.Sprites["OptionsHeader"] = { + Sheet = "Config", + Bounds = { X = 5, Y = 1243, Width = 663, Height = 267 } +}; + +root.Sprites["OptionsPagePanel"] = { + Sheet = "Config", + Bounds = { X = 1571, Y = 0, Width = 477, Height = 1080 } +}; + +root.Sprites["OptionsSkipRead"] = { + Sheet = "Config", + Bounds = { X = 952, Y = 1394, Width = 148, Height = 50 } +}; + +root.Sprites["OptionsSkipAll"] = { + Sheet = "Config", + Bounds = { X = 952, Y = 1448, Width = 148, Height = 50 } +}; + +root.Sprites["OptionsOn"] = { + Sheet = "Config", + Bounds = { X = 952, Y = 1499, Width = 148, Height = 50 } +}; + +root.Sprites["OptionsOff"] = { + Sheet = "Config", + Bounds = { X = 952, Y = 1551, Width = 148, Height = 50 } +}; + +root.Sprites["OptionsYes"] = { + Sheet = "Config", + Bounds = { X = 952, Y = 1603, Width = 148, Height = 50 } +}; + +root.Sprites["OptionsNo"] = { + Sheet = "Config", + Bounds = { X = 952, Y = 1655, Width = 148, Height = 50 } +}; + +root.Sprites["OptionsGuide"] = { + Sheet = "Config", + Bounds = { X = 0, Y = 2476, Width = 1926, Height = 57 } +}; + +root.Sprites["OptionsVoiceGuide"] = { + Sheet = "Config", + Bounds = { X = 0, Y = 2416, Width = 1926, Height = 57 } +}; + +root.Sprites["MenuMask"] = { + Sheet = "MenuChip", + Bounds = { X = 154, Y = 140, Width = 1900, Height = 1061 }, +}; + +for i = 0, 3 do + height = 80; + + root.Sprites["OptionsPageHeader" .. i] = { + Sheet = "Config", + Bounds = { + X = 0, + Y = 1510 + height * i, + Width = 942, + Height = height + } + }; + + root.OptionsMenu.PageHeaderSprites[#root.OptionsMenu.PageHeaderSprites + 1] = "OptionsPageHeader" .. i; +end + +-- Rearange the labels in the array so they're in order of appearance +labelIndices = {5, 6, 7, 1, + 2, 3, 8, + 9, 10, 11, 12, 13, 4, 15, 14, + 16}; +height = 52; +for i = 0, 15 do + offset = ((i > 11) and {104} or {0})[1]; + + root.Sprites["OptionsLabel" .. i] = { + Sheet = "Config", + Bounds = { + X = 1239, + Y = 1237 + height * i + offset, + Width = 809, + Height = height + } + }; + root.OptionsMenu.LabelSprites[labelIndices[i + 1]] = "OptionsLabel" .. i; +end + +for i = 0, 12 do + width = 218; + height = 53; + + root.Sprites["OptionsNametag" .. i] = { + Sheet = "Config", + Bounds = { + X = width * (i % 6), + Y = 1863 + height * (i // 6), + Width = width, + Height = height + } + }; + root.OptionsMenu.NametagSprites[#root.OptionsMenu.NametagSprites + 1] = "OptionsNametag" .. i; +end + +for i = 0, 7 do + -- Interweave highlighted and non-highlighted variants + width = 224; + offset = ((i % 2 == 1) and {width * 4} or {0})[1]; + + -- Non-highlighted Text and Sound panels are poorly placed in the spritesheet, for some reason + if (i == 3 or i == 5) then + offset = offset - 1; + end + + root.Sprites["OptionsPagePanel" .. i] = { + Sheet = "Config", + Bounds = { + X = offset + width * (i // 2), + Y = 2205, + Width = width, + Height = 195 + } + }; + root.OptionsMenu.PagePanelSprites[#root.OptionsMenu.PagePanelSprites + 1] = "OptionsPagePanel" .. i; +end + +for i = 0, 25 do + -- Interweave on and muted variants + width = 100; + + root.Sprites["OptionsPortrait" .. i] = { + Sheet = "ConfigEx", + Bounds = { + X = 768 + (width + 1) * (i // 2), + Y = ((i % 2 == 0) and {2256} or {2357})[1], + Width = width, + Height = width + } + }; + root.OptionsMenu.PortraitSprites[#root.OptionsMenu.PortraitSprites + 1] = "OptionsPortrait" .. i; +end + +MakeAnimation({ + Name = "OptionsPoleAnimation", + Sheet = "ConfigEx", + + FirstFrameX = 0, + FirstFrameY = 0, + + FrameWidth = 539, + ColWidth = 539, + FrameHeight = 1080, + RowHeight = 1096, + + Frames = 15, + Duration = 0.5, + Rows = 3, + Columns = 7, + + PrimaryDirection = AnimationDirections.Right, + SecondaryDirection = AnimationDirections.Down +}); \ No newline at end of file diff --git a/src/games/cclcc/optionsmenu.cpp b/src/games/cclcc/optionsmenu.cpp index 67184740..f36e8892 100644 --- a/src/games/cclcc/optionsmenu.cpp +++ b/src/games/cclcc/optionsmenu.cpp @@ -1,15 +1,13 @@ #include "optionsmenu.h" +#include "../../profile/game.h" #include "../../profile/ui/optionsmenu.h" #include "../../profile/games/cclcc/optionsmenu.h" -#include "../../renderer/renderer.h" -#include "../../mem.h" -#include "../../profile/scriptvars.h" -#include "../../inputsystem.h" +#include "../../profile/scriptinput.h" #include "../../vm/interface/input.h" -#include "../../ui/widgets/button.h" -#include "../../vm/vm.h" -#include "../../audio/audiochannel.h" +#include "../../ui/widgets/cclcc/optionsbinarybutton.h" +#include "../../ui/widgets/cclcc/optionsslider.h" +#include "../../ui/widgets/cclcc/optionsvoiceslider.h" namespace Impacto { namespace UI { @@ -18,6 +16,8 @@ namespace CCLCC { using namespace Impacto::Profile::OptionsMenu; using namespace Impacto::Profile::CCLCC::OptionsMenu; using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::UI::Widgets; +using namespace Impacto::UI::Widgets::CCLCC; using namespace Impacto::Vm::Interface; OptionsMenu::OptionsMenu() { @@ -25,14 +25,121 @@ OptionsMenu::OptionsMenu() { FadeAnimation.LoopMode = AnimationLoopMode::Stop; FadeAnimation.DurationIn = FadeInDuration; FadeAnimation.DurationOut = FadeOutDuration; + + PoleAnimation = Profile::CCLCC::OptionsMenu::PoleAnimation.Instantiate(); + + Pages.reserve(PageCount); + glm::vec2 pos = EntriesStartPosition; + glm::vec4 highlightTint(HighlightColor, 1.0f); + std::function select = + std::bind(&OptionsMenu::Select, this, std::placeholders::_1); + + PageButtons.reserve(PageCount); + std::function pageButtonOnClick = + std::bind(&OptionsMenu::PageButtonOnClick, this, std::placeholders::_1); + for (int i = 0; i < PageCount; i++) { + PageButtons.push_back( + ClickButton(i, PagePanelHoverBounds[i], pageButtonOnClick)); + } + + BasicPage = new Group(this); + for (int i = 0; i < 4; i++) { + BasicPage->Add( + new OptionsBinaryButton(BinaryBoxSprite, OnSprite, OffSprite, + LabelSprites[i], pos, highlightTint, select), + FDIR_DOWN); + + pos.y += EntriesVerticalOffset; + } + Pages.push_back(BasicPage); + + pos = EntriesStartPosition; + TextPage = new Group(this); + for (int i = 4; i < 6; i++) { + TextPage->Add(new OptionsSlider(SliderTrackSprite, LabelSprites[i], pos, + highlightTint, SliderSpeed, select), + FDIR_DOWN); + + pos.y += EntriesVerticalOffset; + } + TextPage->Add( + new OptionsBinaryButton(BinaryBoxSprite, SkipReadSprite, SkipAllSprite, + LabelSprites[6], pos, highlightTint, select), + FDIR_DOWN); + Pages.push_back(TextPage); + + pos = SoundEntriesStartPosition; + SoundPage = new Group(this); + for (int i = 7; i < 15; i++) { + Widget* widget = + (i < 11 || i == 14) + ? new OptionsSlider(SliderTrackSprite, LabelSprites[i], pos, + highlightTint, SliderSpeed, select) + : widget = new OptionsBinaryButton(BinaryBoxSprite, YesSprite, + NoSprite, LabelSprites[i], pos, + highlightTint, select); + SoundPage->Add(widget, FDIR_DOWN); + + pos.y += SoundEntriesVerticalOffset; + } + Pages.push_back(SoundPage); + + VoicePage = new Group(this); + constexpr int columns = 3; + constexpr int entries = 12; + for (int i = 0; i < entries; i++) { + glm::vec2 pos = VoicePosition; + pos += VoiceEntriesOffset * glm::vec2(i % columns, i / columns); + + Widget* widget = new OptionsVoiceSlider( + VoiceSliderTrackSprite, NametagSprites[i], PortraitSprites[2 * i], + PortraitSprites[2 * i + 1], pos, highlightTint, SliderSpeed, select); + VoicePage->Add(widget, FDIR_RIGHT); + } + + // Loop separately to overwrite the direction set at initial adding + // First entry won't set anything; skip + for (int i = 1; i < entries; i++) { + Widget* const widget = VoicePage->Children.at(i); + + if (i % columns != 0) { // Not on first column + Widget* const leftWidget = VoicePage->Children.at(i - 1); + widget->SetFocus(leftWidget, FDIR_LEFT); + leftWidget->SetFocus(widget, FDIR_RIGHT); + + if (i % columns == columns - 1) { // On last column + Widget* const rowStart = VoicePage->Children.at(i - columns + 1); + widget->SetFocus(rowStart, FDIR_RIGHT); + rowStart->SetFocus(widget, FDIR_LEFT); + } + } + if (i >= columns) { // Not on first row + Widget* const upWidget = VoicePage->Children.at(i - columns); + widget->SetFocus(upWidget, FDIR_UP); + upWidget->SetFocus(widget, FDIR_DOWN); + + if (i >= entries - columns) { // On last layer + Widget* const columnStart = VoicePage->Children.at(i % columns); + widget->SetFocus(columnStart, FDIR_DOWN); + columnStart->SetFocus(widget, FDIR_UP); + } + } + } + + Pages.push_back(VoicePage); + + CurrentPage = 0; } void OptionsMenu::Show() { if (State != Shown) { State = Showing; FadeAnimation.StartIn(); - // FirstPage->Show(); - if (UI::FocusedMenu != 0) { + PoleAnimation.StartIn(); + + Pages.at(CurrentPage)->Show(); + + if (UI::FocusedMenu != nullptr) { LastFocusedMenu = UI::FocusedMenu; LastFocusedMenu->IsFocused = false; } @@ -40,24 +147,26 @@ void OptionsMenu::Show() { UI::FocusedMenu = this; } } + void OptionsMenu::Hide() { if (State != Hidden) { State = Hiding; FadeAnimation.StartOut(); - if (LastFocusedMenu != 0) { + PoleAnimation.StartOut(); + + if (LastFocusedMenu != nullptr) { UI::FocusedMenu = LastFocusedMenu; LastFocusedMenu->IsFocused = true; } else { - UI::FocusedMenu = 0; + UI::FocusedMenu = nullptr; } IsFocused = false; } } void OptionsMenu::Update(float dt) { - UpdateInput(); - FadeAnimation.Update(dt); + PoleAnimation.Update(dt); if (ScrWork[SW_SYSSUBMENUCT] < 32 && State == Shown && ScrWork[SW_SYSSUBMENUNO] == 5) { Hide(); @@ -65,16 +174,259 @@ void OptionsMenu::Update(float dt) { ScrWork[SW_SYSSUBMENUNO] == 5) { Show(); } + + if (State != Hidden) { + UpdateInput(dt); + Pages.at(CurrentPage)->Update(dt); + } + + if (!FadeAnimation.IsIn() && !FadeAnimation.IsOut()) { + const glm::vec2 backgroundPosition = + glm::vec2(0.0f, glm::mix(BackgroundFadeStartPosition.y, + BackgroundPosition.y, FadeAnimation.Progress)); + Pages.at(CurrentPage)->MoveTo(backgroundPosition); + } + + if (FadeAnimation.IsIn()) { + State = Shown; + Pages.at(CurrentPage)->MoveTo(glm::vec2(0.0f, BackgroundPosition.y)); + } else if (State == Hiding && FadeAnimation.IsOut()) { + if (ScrWork[SW_SYSSUBMENUCT] == 0) { + State = Hidden; + + Pages.at(CurrentPage)->Hide(); + CurrentPage = 0; + CurrentlyFocusedElement = nullptr; + } else { + SetFlag(SF_SUBMENUEXIT, true); + } + } +} + +void OptionsMenu::UpdatePageInput(float dt) { + // Mouse input + for (ClickButton& button : PageButtons) { + const bool wasHovered = button.Hovered; + button.UpdateInput(); + if (!wasHovered && button.Hovered) + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); + } + + const bool pagePanelHighlighted = CurrentlyFocusedElement == nullptr; + + // Button input + + int direction = + (bool)(PADinputButtonIsDown & + (PAD1R1 | (PAD1DOWN * pagePanelHighlighted))) - + (bool)(PADinputButtonIsDown & (PAD1L1 | (PAD1UP * pagePanelHighlighted))); + + if (direction == 0) { + PageDirectionButtonHeldTime = 0.0f; + PageDirectionButtonWaitTime = 0.0f; + return; + } + + if (0.0f < PageDirectionButtonHeldTime && + PageDirectionButtonHeldTime < MinButtonHoldTime) { + PageDirectionButtonHeldTime += dt; + PageDirectionButtonWaitTime = 0.0f; + return; + } + + if (PageDirectionButtonWaitTime > 0.0f) { + PageDirectionButtonWaitTime -= dt; + return; + } + + // Page advancement fired + + PageDirectionButtonHeldTime += dt; + PageDirectionButtonWaitTime = ButtonHoldTimeInterval; + + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); + + const bool focusedElement = CurrentlyFocusedElement; + GoToPage((CurrentPage + direction) % Pages.size()); + + if (focusedElement) { + CurrentlyFocusedElement = Pages.at(CurrentPage)->GetFirstFocusableChild(); + CurrentlyFocusedElement->HasFocus = true; + } +} + +void OptionsMenu::UpdateEntryMovementInput(float dt) { + const int verticalMovement = (bool)(PADinputButtonIsDown & PAD1DOWN) - + (bool)(PADinputButtonIsDown & PAD1UP); + const int horizontalMovement = (bool)(PADinputButtonIsDown & PAD1RIGHT) - + (bool)(PADinputButtonIsDown & PAD1LEFT); + const bool moving = + verticalMovement || (CurrentPage == 3 && horizontalMovement); + + if (!moving) { + DirectionButtonHeldTime = 0.0f; + DirectionButtonWaitTime = 0.0f; + return; + } + + if (0.0f < DirectionButtonHeldTime && + DirectionButtonHeldTime < MinButtonHoldTime) { + DirectionButtonHeldTime += dt; + DirectionButtonWaitTime = 0.0f; + return; + } + + if (DirectionButtonWaitTime > 0.0f) { + DirectionButtonWaitTime -= dt; + return; + } + + // Advance entry + + DirectionButtonHeldTime += dt; + DirectionButtonWaitTime = ButtonHoldTimeInterval; + + const Widget* const lastHighlight = CurrentlyFocusedElement; + if (horizontalMovement != 0) { + const FocusDirection horizontalDirection = + horizontalMovement == -1 ? FDIR_LEFT : FDIR_RIGHT; + AdvanceFocus(horizontalDirection); + } + if (verticalMovement != 0) { + const FocusDirection verticalDirection = + verticalMovement == -1 ? FDIR_UP : FDIR_DOWN; + AdvanceFocus(verticalDirection); + } + if (CurrentlyFocusedElement != lastHighlight) + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); +} + +void OptionsMenu::UpdateInput(float dt) { + UpdatePageInput(dt); + + if (CurrentlyFocusedElement == nullptr) { + if (GetControlState(CT_Back)) { + if (!GetFlag(SF_SUBMENUEXIT)) + Audio::Channels[Audio::AC_REV]->Play("sysse", 3, false, 0.0f); + + Hide(); + return; + } + + if (PADinputButtonWentDown & PAD1A) { + // Don't have anything else consume the confirmation + PADinputButtonWentDown &= ~PAD1A; + + Audio::Channels[Audio::AC_REV]->Play("sysse", 2, false, 0.0f); + + CurrentlyFocusedElement = Pages.at(CurrentPage)->GetFirstFocusableChild(); + CurrentlyFocusedElement->HasFocus = true; + } + + return; + } + + // If something is selected, the option entry takes full control + if (static_cast(CurrentlyFocusedElement)->Selected) return; + + if (GetControlState(CT_Back) || PADinputMouseWentDown & PAD1B) { + Audio::Channels[Audio::AC_REV]->Play("sysse", 3, false, 0.0f); + + static_cast(CurrentlyFocusedElement)->Hide(); + CurrentlyFocusedElement = nullptr; + return; + } + + UpdateEntryMovementInput(dt); } void OptionsMenu::Render() { if (State != Hidden && ScrWork[SW_SYSSUBMENUCT] >= 32 && ScrWork[SW_SYSSUBMENUNO] == 5) { - // glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress); - Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f)); + glm::vec4 col(1.0f, 1.0f, 1.0f, FadeAnimation.Progress); + + glm::vec4 maskTint = col; + maskTint.a *= (float)0xa0 / 0x100; + + const glm::vec2 backgroundAnimationOffset = + glm::vec2(0.0f, FadeAnimation.Progress * BackgroundPosition.y + + (1.0f - FadeAnimation.Progress) * + BackgroundFadeStartPosition.y); + const glm::vec2 pagePanelPosition = + PagePanelPosition * FadeAnimation.Progress + + (1.0f - FadeAnimation.Progress) * PagePanelFadeStartPosition; + const glm::vec2 guidePosition = + GuidePosition * FadeAnimation.Progress + + (1.0f - FadeAnimation.Progress) * GuideFadeStartPosition; + + Renderer->DrawSprite(BackgroundSprite, + BackgroundPosition + backgroundAnimationOffset, col); + Renderer->DrawSprite(HeaderSprite, + HeaderPosition + backgroundAnimationOffset, col); + + Renderer->DrawSprite(PageHeaderSprites[CurrentPage], + PageHeaderPosition + backgroundAnimationOffset, col); + Pages.at(CurrentPage)->Tint = col; + Pages.at(CurrentPage)->Render(); + + Renderer->DrawSprite(PoleAnimation.CurrentSprite(), pagePanelPosition, col); + if (PoleAnimation.IsIn()) { + for (ClickButton& panel : PageButtons) { + if (panel.Id == CurrentPage || panel.Hovered) { + const bool highlighted = + panel.Id == CurrentPage && (bool)CurrentlyFocusedElement; + Renderer->DrawSprite( + PagePanelSprites[2 * panel.Id + !highlighted], + PagePanelPosition + PagePanelIconOffsets[panel.Id], col); + } + } + } + + Renderer->DrawSprite( + MenuMaskSprite, + RectF(0.0f, 0.0f, Profile::DesignWidth, Profile::DesignHeight), + maskTint); + + const Sprite& guideSprite = + CurrentPage == 3 ? VoiceGuideSprite : GuideSprite; + Renderer->DrawSprite(guideSprite, guidePosition, col); } } +void OptionsMenu::GoToPage(int pageNumber) { + if (CurrentPage == pageNumber) return; + + Pages.at(CurrentPage)->Hide(); + + CurrentPage = pageNumber; + Group& page = *Pages.at(CurrentPage); + + page.HasFocus = true; + page.Show(); +} + +void OptionsMenu::Select(OptionsEntry* toSelect) { + for (Widget* entry : Pages.at(CurrentPage)->Children) { + const bool select = entry == toSelect; + + static_cast(entry)->Selected = select; + entry->HasFocus = select; + } + + CurrentlyFocusedElement = toSelect; +} + +void OptionsMenu::PageButtonOnClick(ClickButton* target) { + if (target->Id != CurrentPage || !CurrentlyFocusedElement) + Audio::Channels[Audio::AC_REV]->Play("sysse", 2, false, 0.0f); + + if (target->Id == CurrentPage && CurrentlyFocusedElement) return; + + GoToPage(target->Id); + CurrentlyFocusedElement = Pages.at(CurrentPage)->GetFirstFocusableChild(); + CurrentlyFocusedElement->HasFocus = true; +} + } // namespace CCLCC } // namespace UI } // namespace Impacto \ No newline at end of file diff --git a/src/games/cclcc/optionsmenu.h b/src/games/cclcc/optionsmenu.h index ce951260..96b84543 100644 --- a/src/games/cclcc/optionsmenu.h +++ b/src/games/cclcc/optionsmenu.h @@ -1,6 +1,12 @@ #pragma once #include "../../ui/menu.h" +#include "../../spriteanimation.h" +#include "../../ui/widgets/group.h" +#include "../../ui/widgets/clickbutton.h" +#include "../../ui/widgets/cclcc/optionsentry.h" + +using namespace Impacto::UI::Widgets::CCLCC; namespace Impacto { namespace UI { @@ -10,13 +16,37 @@ class OptionsMenu : public Menu { public: OptionsMenu(); - void Show(); - void Hide(); - void Update(float dt); - void Render(); + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt); + void Render() override; private: + void GoToPage(int pageNumber); + void Select(OptionsEntry* entry); + void PageButtonOnClick(Widgets::ClickButton* target); + + void UpdatePageInput(float dt); + void UpdateEntryMovementInput(float dt); + Animation FadeAnimation; + SpriteAnimation PoleAnimation; + + Widgets::Group* BasicPage; + Widgets::Group* TextPage; + Widgets::Group* SoundPage; + Widgets::Group* VoicePage; + + int CurrentPage; + std::vector Pages; + std::vector PageButtons; + + float DirectionButtonHeldTime = 0.0f; + float DirectionButtonWaitTime = 0.0f; + + float PageDirectionButtonHeldTime = 0.0f; + float PageDirectionButtonWaitTime = 0.0f; }; } // namespace CCLCC diff --git a/src/games/chlcc/optionsmenu.cpp b/src/games/chlcc/optionsmenu.cpp index f5fe40a8..5025ad19 100644 --- a/src/games/chlcc/optionsmenu.cpp +++ b/src/games/chlcc/optionsmenu.cpp @@ -131,6 +131,10 @@ void OptionsMenu::Update(float dt) { TitleFade.Update(dt); UpdateTitles(); } + + if (GetControlState(CT_Back)) { + SetFlag(SF_SUBMENUEXIT, true); + } } inline void OptionsMenu::DrawCircles() { diff --git a/src/games/mo6tw/optionsmenu.cpp b/src/games/mo6tw/optionsmenu.cpp index 2b854b10..d4490b13 100644 --- a/src/games/mo6tw/optionsmenu.cpp +++ b/src/games/mo6tw/optionsmenu.cpp @@ -377,6 +377,10 @@ void OptionsMenu::Update(float dt) { } } } + + if (GetControlState(CT_Back)) { + SetFlag(SF_SUBMENUEXIT, true); + } } void OptionsMenu::Render() { diff --git a/src/profile/games/cclcc/optionsmenu.cpp b/src/profile/games/cclcc/optionsmenu.cpp index 7db28272..bc9805aa 100644 --- a/src/profile/games/cclcc/optionsmenu.cpp +++ b/src/profile/games/cclcc/optionsmenu.cpp @@ -13,6 +13,71 @@ namespace CCLCC { namespace OptionsMenu { void Configure() { + BackgroundPosition = EnsureGetMemberVec2("BackgroundPosition"); + BackgroundFadeStartPosition = + EnsureGetMemberVec2("BackgroundFadeStartPosition"); + + HighlightColor = EnsureGetMemberVec3("HighlightColor"); + + PointerSprite = EnsureGetMemberSprite("PointerSprite"); + PointerOffset = EnsureGetMemberVec2("PointerOffset"); + + HeaderSprite = EnsureGetMemberSprite("HeaderSprite"); + HeaderPosition = EnsureGetMemberVec2("HeaderPosition"); + GetMemberSpriteArray(PageHeaderSprites, PageCount, "PageHeaderSprites"); + PageHeaderPosition = EnsureGetMemberVec2("PageHeaderPosition"); + + PagePanelSprite = EnsureGetMemberSprite("PagePanelSprite"); + PagePanelPosition = EnsureGetMemberVec2("PagePanelPosition"); + PagePanelFadeStartPosition = + EnsureGetMemberVec2("PagePanelFadeStartPosition"); + GetMemberSpriteArray(PagePanelSprites, PagePanelSpriteCount, + "PagePanelSprites"); + GetMemberVec2Array(PagePanelIconOffsets, PageCount, "PagePanelIconOffsets"); + GetMemberRectFArray(PagePanelHoverBounds, PageCount, "PagePanelHoverBounds"); + PoleAnimation = EnsureGetMemberAnimation("PoleAnimation"); + + SliderTrackSprite = EnsureGetMemberSprite("SliderTrackSprite"); + SliderTrackOffset = EnsureGetMemberVec2("SliderTrackOffset"); + VoiceSliderTrackSprite = EnsureGetMemberSprite("VoiceSliderTrackSprite"); + VoiceSliderOffset = EnsureGetMemberVec2("VoiceSliderOffset"); + BinaryBoxSprite = EnsureGetMemberSprite("BinaryBoxSprite"); + BinaryBoxOffset = EnsureGetMemberVec2("BinaryBoxOffset"); + SliderSpeed = EnsureGetMemberFloat("SliderSpeed"); + + SkipReadSprite = EnsureGetMemberSprite("SkipReadSprite"); + SkipAllSprite = EnsureGetMemberSprite("SkipAllSprite"); + OnSprite = EnsureGetMemberSprite("OnSprite"); + OffSprite = EnsureGetMemberSprite("OffSprite"); + YesSprite = EnsureGetMemberSprite("YesSprite"); + NoSprite = EnsureGetMemberSprite("NoSprite"); + + GuideSprite = EnsureGetMemberSprite("GuideSprite"); + VoiceGuideSprite = EnsureGetMemberSprite("VoiceGuideSprite"); + GuidePosition = EnsureGetMemberVec2("GuidePosition"); + GuideFadeStartPosition = EnsureGetMemberVec2("GuideFadeStartPosition"); + + EntriesStartPosition = EnsureGetMemberVec2("EntriesStartPosition"); + EntriesVerticalOffset = EnsureGetMemberInt("EntriesVerticalOffset"); + SoundEntriesStartPosition = EnsureGetMemberVec2("SoundEntriesStartPosition"); + SoundEntriesVerticalOffset = EnsureGetMemberInt("SoundEntriesVerticalOffset"); + VoiceEntriesOffset = EnsureGetMemberVec2("VoiceEntriesOffset"); + EntryDimensions = EnsureGetMemberVec2("EntryDimensions"); + VoiceEntryDimensions = EnsureGetMemberVec2("VoiceEntryDimensions"); + + GetMemberSpriteArray(LabelSprites, LabelCount, "LabelSprites"); + LabelOffset = EnsureGetMemberVec2("LabelOffset"); + GetMemberSpriteArray(NametagSprites, NametagCount, "NametagSprites"); + NametagOffset = EnsureGetMemberVec2("NametagOffset"); + GetMemberSpriteArray(PortraitSprites, PortraitCount, "PortraitSprites"); + PortraitOffset = EnsureGetMemberVec2("PortraitOffset"); + VoicePosition = EnsureGetMemberVec2("VoicePosition"); + + MinButtonHoldTime = EnsureGetMemberFloat("MinButtonHoldTime"); + ButtonHoldTimeInterval = EnsureGetMemberFloat("ButtonHoldTimeInterval"); + + MenuMaskSprite = EnsureGetMemberSprite("MenuMask"); + auto drawType = Game::DrawComponentType::_from_integral_unchecked( EnsureGetMemberInt("DrawType")); diff --git a/src/profile/games/cclcc/optionsmenu.h b/src/profile/games/cclcc/optionsmenu.h index 1c1ad53d..7db6b6f3 100644 --- a/src/profile/games/cclcc/optionsmenu.h +++ b/src/profile/games/cclcc/optionsmenu.h @@ -1,12 +1,81 @@ #pragma once #include "../../../spritesheet.h" +#include "../../../spriteanimation.h" namespace Impacto { namespace Profile { namespace CCLCC { namespace OptionsMenu { +int constexpr PageCount = 4; +int constexpr PagePanelSpriteCount = PageCount * 2; +int constexpr LabelCount = 16; +int constexpr NametagCount = 13; +int constexpr PortraitCount = NametagCount * 2; + +inline glm::vec2 BackgroundPosition; +inline glm::vec2 BackgroundFadeStartPosition; + +inline glm::vec3 HighlightColor; + +inline Sprite PointerSprite; +inline glm::vec2 PointerOffset; + +inline Sprite HeaderSprite; +inline glm::vec2 HeaderPosition; +inline Sprite PageHeaderSprites[PageCount]; +inline glm::vec2 PageHeaderPosition; + +inline Sprite PagePanelSprite; +inline glm::vec2 PagePanelPosition; +inline glm::vec2 PagePanelFadeStartPosition; +inline Sprite PagePanelSprites[PagePanelSpriteCount]; +inline glm::vec2 PagePanelIconOffsets[PagePanelSpriteCount]; +inline SpriteAnimationDef PoleAnimation; +inline RectF PagePanelHoverBounds[PageCount]; + +inline Sprite SliderTrackSprite; +inline glm::vec2 SliderTrackOffset; +inline Sprite VoiceSliderTrackSprite; +inline glm::vec2 VoiceSliderOffset; +inline Sprite BinaryBoxSprite; +inline glm::vec2 BinaryBoxOffset; +inline float SliderSpeed; + +inline Sprite SkipReadSprite; +inline Sprite SkipAllSprite; +inline Sprite OnSprite; +inline Sprite OffSprite; +inline Sprite YesSprite; +inline Sprite NoSprite; + +inline Sprite GuideSprite; +inline Sprite VoiceGuideSprite; +inline glm::vec2 GuidePosition; +inline glm::vec2 GuideFadeStartPosition; + +inline glm::vec2 EntriesStartPosition; +inline int EntriesVerticalOffset; +inline glm::vec2 SoundEntriesStartPosition; +inline int SoundEntriesVerticalOffset; +inline glm::vec2 VoiceEntriesOffset; +inline glm::vec2 EntryDimensions; +inline glm::vec2 VoiceEntryDimensions; + +inline Sprite LabelSprites[LabelCount]; +inline glm::vec2 LabelOffset; +inline Sprite NametagSprites[NametagCount]; +inline glm::vec2 NametagOffset; +inline Sprite PortraitSprites[PortraitCount]; +inline glm::vec2 PortraitOffset; +inline glm::vec2 VoicePosition; + +inline float MinButtonHoldTime; +inline float ButtonHoldTimeInterval; + +inline Sprite MenuMaskSprite; + void Configure(); } // namespace OptionsMenu diff --git a/src/profile/games/mo6tw/optionsmenu.cpp b/src/profile/games/mo6tw/optionsmenu.cpp index b0a5e616..0ce96435 100644 --- a/src/profile/games/mo6tw/optionsmenu.cpp +++ b/src/profile/games/mo6tw/optionsmenu.cpp @@ -27,6 +27,10 @@ void Configure() { GetMemberSpriteArray(SectionHeaderSprites, SectionHeaderSpriteCount, "SectionHeaderSprites"); + SliderTrackSprite = EnsureGetMemberSprite("SliderTrackSprite"); + SliderFillSprite = EnsureGetMemberSprite("SliderFillSprite"); + SliderThumbSprite = EnsureGetMemberSprite("SliderThumbSprite"); + CheckboxBoxSprite = EnsureGetMemberSprite("CheckboxBoxSprite"); CheckboxTickSprite = EnsureGetMemberSprite("CheckboxTickSprite"); GetMemberSpriteArray(CheckboxLabelSprites, CheckboxLabelCount, diff --git a/src/profile/games/mo6tw/optionsmenu.h b/src/profile/games/mo6tw/optionsmenu.h index ce6c8a52..7e9b7abc 100644 --- a/src/profile/games/mo6tw/optionsmenu.h +++ b/src/profile/games/mo6tw/optionsmenu.h @@ -22,6 +22,10 @@ inline int VoiceTogglePerLine; inline Sprite SectionHeaderSprites[SectionHeaderSpriteCount]; +inline Sprite SliderTrackSprite; +inline Sprite SliderFillSprite; +inline Sprite SliderThumbSprite; + inline Sprite CheckboxBoxSprite; inline Sprite CheckboxTickSprite; inline Sprite CheckboxLabelSprites[CheckboxLabelCount]; diff --git a/src/profile/games/mo8/optionsmenu.cpp b/src/profile/games/mo8/optionsmenu.cpp index 80677921..5589f1dc 100644 --- a/src/profile/games/mo8/optionsmenu.cpp +++ b/src/profile/games/mo8/optionsmenu.cpp @@ -26,6 +26,10 @@ void Configure() { EnsureGetMemberSprite("BackButtonHighlightedSprite"); BackButtonPosition = EnsureGetMemberVec2("BackButtonPosition"); + SliderTrackSprite = EnsureGetMemberSprite("SliderTrackSprite"); + SliderFillSprite = EnsureGetMemberSprite("SliderFillSprite"); + SliderThumbSprite = EnsureGetMemberSprite("SliderThumbSprite"); + ButtonHighlight = EnsureGetMemberSprite("ButtonHighlight"); PageLabelPosition = EnsureGetMemberVec2("PageLabelPosition"); ListStartingPosition = EnsureGetMemberVec2("ListStartingPosition"); diff --git a/src/profile/games/mo8/optionsmenu.h b/src/profile/games/mo8/optionsmenu.h index 53a75833..e13b17e3 100644 --- a/src/profile/games/mo8/optionsmenu.h +++ b/src/profile/games/mo8/optionsmenu.h @@ -14,6 +14,10 @@ inline Sprite BackButtonSprite; inline Sprite BackButtonHighlightedSprite; inline glm::vec2 BackButtonPosition; +inline Sprite SliderTrackSprite; +inline Sprite SliderFillSprite; +inline Sprite SliderThumbSprite; + inline Sprite ButtonHighlight; inline glm::vec2 PageLabelPosition; inline glm::vec2 ListStartingPosition; diff --git a/src/profile/ui/optionsmenu.cpp b/src/profile/ui/optionsmenu.cpp index 389a782f..9d215c5e 100644 --- a/src/profile/ui/optionsmenu.cpp +++ b/src/profile/ui/optionsmenu.cpp @@ -25,9 +25,6 @@ void Configure() { FadeOutDuration = EnsureGetMemberFloat("FadeOutDuration"); BackgroundSprite = EnsureGetMemberSprite("BackgroundSprite"); - SliderTrackSprite = EnsureGetMemberSprite("SliderTrackSprite"); - SliderFillSprite = EnsureGetMemberSprite("SliderFillSprite"); - SliderThumbSprite = EnsureGetMemberSprite("SliderThumbSprite"); }; if (Type == +OptionsMenuType::MO6TW) { diff --git a/src/profile/ui/optionsmenu.h b/src/profile/ui/optionsmenu.h index 728a9387..f89564d2 100644 --- a/src/profile/ui/optionsmenu.h +++ b/src/profile/ui/optionsmenu.h @@ -10,9 +10,6 @@ namespace OptionsMenu { inline Impacto::UI::OptionsMenuType Type = Impacto::UI::OptionsMenuType::None; inline Sprite BackgroundSprite; -inline Sprite SliderTrackSprite; -inline Sprite SliderFillSprite; -inline Sprite SliderThumbSprite; inline float FadeInDuration; inline float FadeOutDuration; diff --git a/src/spritesheet.h b/src/spritesheet.h index b60c2b2a..5f727a1b 100644 --- a/src/spritesheet.h +++ b/src/spritesheet.h @@ -27,8 +27,8 @@ struct Sprite { RectF Bounds{0.0f, 0.0f, 0.0f, 0.0f}; glm::vec2 BaseScale; - float ScaledWidth() { return Bounds.Width * BaseScale.x; } - float ScaledHeight() { return Bounds.Height * BaseScale.y; } + float ScaledWidth() const { return Bounds.Width * BaseScale.x; } + float ScaledHeight() const { return Bounds.Height * BaseScale.y; } void SetScaledWidth(float scaledWidth) { Bounds.Width = scaledWidth / BaseScale.x; } diff --git a/src/ui/backlogmenu.cpp b/src/ui/backlogmenu.cpp index 3e73ea56..035a1abb 100644 --- a/src/ui/backlogmenu.cpp +++ b/src/ui/backlogmenu.cpp @@ -172,7 +172,7 @@ void BacklogMenu::UpdateScrollingInput(float dt) { if (!padScrolling) { DirectionalButtonHeldTime = 0.0f; return; - }; + } FocusDirection dir = (PADinputButtonIsDown & PAD1DOWN) ? FDIR_DOWN : FDIR_UP; diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 1f400fbd..4c07a711 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -22,7 +22,7 @@ void Widget::Hide() { WidgetType Widget::GetType() { return WT_NORMAL; } void Widget::Move(glm::vec2 relativePosition, float duration) { - MoveOrigin = glm::vec2(Bounds.X, Bounds.Y); + MoveOrigin = Bounds.GetPos(); MoveTarget = MoveOrigin + relativePosition; MoveAnimation.Progress = 0.0f; MoveAnimation.Direction = AnimationDirection::In; @@ -36,7 +36,7 @@ void Widget::Move(glm::vec2 relativePosition) { } void Widget::MoveTo(glm::vec2 pos, float duration) { - MoveOrigin = glm::vec2(Bounds.X, Bounds.Y); + MoveOrigin = Bounds.GetPos(); MoveTarget = pos; MoveAnimation.Progress = 0.0f; MoveAnimation.Direction = AnimationDirection::In; diff --git a/src/ui/widget.h b/src/ui/widget.h index f08f4f25..e18ac8c0 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -24,7 +24,7 @@ class Widget { virtual void Update(float dt); virtual void UpdateInput() = 0; - virtual void Render() = 0; + virtual void Render() {} virtual void Show(); virtual void Hide(); diff --git a/src/ui/widgets/cclcc/optionsbinarybutton.cpp b/src/ui/widgets/cclcc/optionsbinarybutton.cpp new file mode 100644 index 00000000..f4278a62 --- /dev/null +++ b/src/ui/widgets/cclcc/optionsbinarybutton.cpp @@ -0,0 +1,129 @@ +#include "optionsbinarybutton.h" + +#include "../../../profile/games/cclcc/optionsmenu.h" +#include "../../../renderer/renderer.h" +#include "../../../vm/interface/input.h" +#include "../../../inputsystem.h" + +using namespace Impacto::Profile::CCLCC::OptionsMenu; +using namespace Impacto::Vm::Interface; + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +OptionsBinaryButton::OptionsBinaryButton( + const Sprite& box, const Sprite& trueLabel, const Sprite& falseLabel, + const Sprite& label, glm::vec2 pos, glm::vec4 highlightTint, + std::function select) + : OptionsEntry(label, pos, highlightTint, select), + BoxSprite(box), + TrueSprite(trueLabel), + FalseSprite(falseLabel) { + Bounds.Width = BinaryBoxOffset.x + BoxSprite.ScaledWidth(); + EntryButton.Bounds.Width = Bounds.Width; + + glm::vec2 truePosition = pos + BinaryBoxOffset; + TrueButton = + ClickButton(0, + RectF(truePosition.x, truePosition.y, + TrueSprite.ScaledWidth(), TrueSprite.ScaledHeight()), + std::bind(&OptionsBinaryButton::TrueOnClick, this, + std::placeholders::_1)); + FalseButton = ClickButton( + 0, TrueButton.Bounds + RectF(box.ScaledWidth() / 2.0f, 0.0f, 0.0f, 0.0f), + std::bind(&OptionsBinaryButton::FalseOnClick, this, + std::placeholders::_1)); +} + +void OptionsBinaryButton::Render() { + OptionsEntry::Render(); + + RectF highlightBounds(0.0f, 0.0f, BoxSprite.ScaledWidth() / 2, + BoxSprite.ScaledHeight()); + glm::vec2 highlightPos = ((State) ? TrueButton : FalseButton).Bounds.GetPos(); + highlightBounds.X = highlightPos.x; + highlightBounds.Y = highlightPos.y; + + Renderer->DrawRect(highlightBounds, HighlightTint); + Renderer->DrawSprite(BoxSprite, TrueButton.Bounds.GetPos(), Tint); + + Renderer->DrawSprite(TrueSprite, TrueButton.Bounds.GetPos(), Tint, + glm::vec2(1.0f), 0.0f, !State); + Renderer->DrawSprite(FalseSprite, FalseButton.Bounds.GetPos(), Tint, + glm::vec2(1.0f), 0.0f, State); +} + +void OptionsBinaryButton::Update(float dt) { + OptionsEntry::Update(dt); + + TrueButton.Update(dt); + FalseButton.Update(dt); +} + +void OptionsBinaryButton::UpdateInput() { + // Handle mouse/touch input + TrueButton.UpdateInput(); + FalseButton.UpdateInput(); + + OptionsEntry::UpdateInput(); + + if (!Selected) return; + + // Handle keyboard/controller input + if (PADinputButtonWentDown & (PAD1LEFT | PAD1RIGHT)) { + const bool newState = PADinputButtonWentDown & PAD1LEFT; + + if (State != newState) + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); + + State = newState; + } +} + +void OptionsBinaryButton::Show() { + OptionsEntry::Show(); + + TrueButton.Show(); + FalseButton.Show(); +} + +void OptionsBinaryButton::Hide() { + OptionsEntry::Hide(); + + TrueButton.Hide(); + FalseButton.Hide(); +} + +void OptionsBinaryButton::Move(glm::vec2 relativePos) { + OptionsEntry::Move(relativePos); + TrueButton.Move(relativePos); + FalseButton.Move(relativePos); +} + +void OptionsBinaryButton::MoveTo(glm::vec2 pos) { + const glm::vec2 relativePosition = pos - Bounds.GetPos(); + OptionsEntry::MoveTo(pos); + TrueButton.Move(relativePosition); + FalseButton.Move(relativePosition); +} + +void OptionsBinaryButton::TrueOnClick(ClickButton* target) { + if (Selected && State != true) + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); + + State = true; +} + +void OptionsBinaryButton::FalseOnClick(ClickButton* target) { + if (Selected && State != false) + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); + + State = false; +} + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsbinarybutton.h b/src/ui/widgets/cclcc/optionsbinarybutton.h new file mode 100644 index 00000000..74cdb6da --- /dev/null +++ b/src/ui/widgets/cclcc/optionsbinarybutton.h @@ -0,0 +1,47 @@ +#pragma once + +#include "./optionsentry.h" +#include "../clickbutton.h" +#include "../../../spritesheet.h" + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +class OptionsBinaryButton : public OptionsEntry { + public: + OptionsBinaryButton(const Sprite& box, const Sprite& trueLabel, + const Sprite& falseLabel, const Sprite& label, + glm::vec2 pos, glm::vec4 highlightTint, + std::function select); + + void Render() override; + void Update(float dt) override; + void UpdateInput() override; + + void Show() override; + void Hide() override; + + void Move(glm::vec2 relativePos) override; + void MoveTo(glm::vec2 pos) override; + + private: + ClickButton TrueButton; + ClickButton FalseButton; + + const Sprite& TrueSprite; + const Sprite& FalseSprite; + + void TrueOnClick(ClickButton* target); + void FalseOnClick(ClickButton* target); + + const Sprite& BoxSprite; + + bool State = true; +}; + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsentry.cpp b/src/ui/widgets/cclcc/optionsentry.cpp new file mode 100644 index 00000000..892992a7 --- /dev/null +++ b/src/ui/widgets/cclcc/optionsentry.cpp @@ -0,0 +1,98 @@ +#include "optionsentry.h" + +#include "../../../profile/games/cclcc/optionsmenu.h" +#include "../../../renderer/renderer.h" +#include "../../../vm/interface/input.h" + +using namespace Impacto::Profile::CCLCC::OptionsMenu; +using namespace Impacto::Vm::Interface; + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +OptionsEntry::OptionsEntry(const Sprite& label, glm::vec2 pos, + glm::vec4 highlightTint, + std::function select) + : LabelSprite(label), HighlightTint(highlightTint), Select(select) { + Bounds = RectF(pos.x, pos.y, LabelOffset.x + LabelSprite.ScaledWidth(), + LabelOffset.y + LabelSprite.ScaledHeight()); + + std::function onClick = + std::bind(&OptionsEntry::EntryButtonOnClick, this, std::placeholders::_1); + EntryButton = ClickButton(0, Bounds, onClick); +} + +void OptionsEntry::Render() { + HighlightTint.a = Tint.a; + + if (HasFocus || EntryButton.Hovered) { + RectF highlightBoundBox(Bounds.X, Bounds.Y, EntryDimensions.x, + EntryDimensions.y); + Renderer->DrawRect(highlightBoundBox, HighlightTint); + Renderer->DrawRect(highlightBoundBox + RectF(2.0f, 2.0f, -4.0f, -4.0f), + glm::vec4(1.0f, 1.0f, 1.0f, Tint.a)); + + Renderer->DrawSprite(PointerSprite, Bounds.GetPos() + PointerOffset, Tint); + } + + Renderer->DrawSprite(LabelSprite, Bounds.GetPos() + LabelOffset, + Selected ? Tint : glm::vec4(0.0f, 0.0f, 0.0f, Tint.a)); +} + +void OptionsEntry::Update(float dt) { + Widget::Update(dt); + + EntryButton.Update(dt); +} + +void OptionsEntry::UpdateInput() { + const bool wasHovered = EntryButton.Hovered; + EntryButton.UpdateInput(); + if (!wasHovered && EntryButton.Hovered) + Audio::Channels[Audio::AC_REV]->Play("sysse", 1, false, 0.0f); + + if (!HasFocus) return; + + if (PADinputButtonWentDown & PAD1A) { + Selected = !Selected; + Audio::Channels[Audio::AC_REV]->Play("sysse", 2, false, 0.0f); + } + + if (PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) { + Selected = false; + Audio::Channels[Audio::AC_REV]->Play("sysse", 3, false, 0.0f); + } +} + +void OptionsEntry::Show() { EntryButton.Show(); } + +void OptionsEntry::Hide() { + Widget::Hide(); + EntryButton.Hide(); + Selected = false; +} + +void OptionsEntry::Move(glm::vec2 relativePos) { + Widget::Move(relativePos); + EntryButton.Move(relativePos); +} + +void OptionsEntry::MoveTo(glm::vec2 pos) { + const glm::vec2 relativePosition = pos - Bounds.GetPos(); + Widget::MoveTo(pos); + EntryButton.Move(relativePosition); +} + +void OptionsEntry::EntryButtonOnClick(ClickButton* target) { + if (Selected) return; + + Audio::Channels[Audio::AC_REV]->Play("sysse", 2, false, 0.0f); + Select(this); +} + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsentry.h b/src/ui/widgets/cclcc/optionsentry.h new file mode 100644 index 00000000..00751b76 --- /dev/null +++ b/src/ui/widgets/cclcc/optionsentry.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "../clickbutton.h" +#include "../../widget.h" +#include "../../../spritesheet.h" + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +class OptionsEntry : public Widget { + public: + OptionsEntry(const Sprite& label, glm::vec2 pos, glm::vec4 highlightTint, + std::function select); + + void Render() override; + void Update(float dt) override; + void UpdateInput() override; + + void Show() override; + void Hide() override; + + void Move(glm::vec2 relativePos) override; + void MoveTo(glm::vec2 pos) override; + + bool Selected = false; + + protected: + ClickButton EntryButton; + std::function Select; + void EntryButtonOnClick(ClickButton* target); + + const Sprite& LabelSprite; + glm::vec4 HighlightTint; +}; + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsslider.cpp b/src/ui/widgets/cclcc/optionsslider.cpp new file mode 100644 index 00000000..cfec4b33 --- /dev/null +++ b/src/ui/widgets/cclcc/optionsslider.cpp @@ -0,0 +1,76 @@ +#include "optionsslider.h" + +#include "../../../profile/games/cclcc/optionsmenu.h" +#include "../../../renderer/renderer.h" +#include "../../../vm/interface/input.h" + +using namespace Impacto::Profile::CCLCC::OptionsMenu; +using namespace Impacto::Vm::Interface; + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +OptionsSlider::OptionsSlider(const Sprite& box, const Sprite& label, + glm::vec2 pos, glm::vec4 highlightTint, + float sliderSpeed, + std::function select) + : OptionsEntry(label, pos, highlightTint, select), + BoxSprite(box), + Slider(0, pos + SliderTrackOffset, 0.0f, 1.0f, &Progress, + SBDIR_HORIZONTAL, + glm::vec2(BoxSprite.ScaledWidth(), BoxSprite.ScaledHeight())) { + Bounds.Width = SliderTrackOffset.x + BoxSprite.ScaledWidth(); + EntryButton.Bounds.Width = Bounds.Width; +} + +OptionsSlider::OptionsSlider(const Sprite& box, const Sprite& label, + glm::vec2 pos, glm::vec4 highlightTint, + RectF sliderBounds, float sliderSpeed, + std::function select) + : OptionsEntry(label, pos, highlightTint, select), + BoxSprite(box), + Slider(0, sliderBounds.GetPos(), 0.0f, 1.0f, &Progress, SBDIR_HORIZONTAL, + sliderBounds.GetSize()) {} + +void OptionsSlider::Render() { + OptionsEntry::Render(); + + RectF highlightBounds( + Bounds.X + SliderTrackOffset.x, Bounds.Y + SliderTrackOffset.y, + Progress * BoxSprite.ScaledWidth(), BoxSprite.ScaledHeight()); + Renderer->DrawRect(highlightBounds, HighlightTint); + + Renderer->DrawSprite(BoxSprite, Bounds.GetPos() + SliderTrackOffset, Tint); +} + +void OptionsSlider::Update(float dt) { + OptionsEntry::Update(dt); + + Slider.Update(dt); +} + +void OptionsSlider::UpdateInput() { + OptionsEntry::UpdateInput(); + + Slider.HasFocus = Selected; + Slider.UpdateInput(); + Slider.ClampValue(); +} + +void OptionsSlider::Move(glm::vec2 relativePos) { + OptionsEntry::Move(relativePos); + Slider.Move(relativePos); +} + +void OptionsSlider::MoveTo(glm::vec2 pos) { + const glm::vec2 relativePosition = pos - Bounds.GetPos(); + OptionsEntry::MoveTo(pos); + Slider.Move(relativePosition); +} + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsslider.h b/src/ui/widgets/cclcc/optionsslider.h new file mode 100644 index 00000000..b71bd576 --- /dev/null +++ b/src/ui/widgets/cclcc/optionsslider.h @@ -0,0 +1,39 @@ +#pragma once + +#include "./optionsentry.h" +#include "../scrollbar.h" +#include "../../../spritesheet.h" + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +class OptionsSlider : public OptionsEntry { + public: + OptionsSlider(const Sprite& box, const Sprite& label, glm::vec2 pos, + glm::vec4 highlightTint, float sliderSpeed, + std::function select); + + void Render() override; + void Update(float dt) override; + void UpdateInput() override; + + void Move(glm::vec2 relativePos) override; + void MoveTo(glm::vec2 pos) override; + + protected: + OptionsSlider(const Sprite& box, const Sprite& label, glm::vec2 pos, + glm::vec4 highlightTint, RectF sliderBounds, float sliderSpeed, + std::function select); + + const Sprite& BoxSprite; + + Scrollbar Slider; + float Progress = 0.0f; +}; + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsvoiceslider.cpp b/src/ui/widgets/cclcc/optionsvoiceslider.cpp new file mode 100644 index 00000000..9c47d02b --- /dev/null +++ b/src/ui/widgets/cclcc/optionsvoiceslider.cpp @@ -0,0 +1,109 @@ +#include "optionsvoiceslider.h" + +#include "../../../profile/games/cclcc/optionsmenu.h" +#include "../../../renderer/renderer.h" +#include "../../../vm/interface/input.h" + +using namespace Impacto::Profile::CCLCC::OptionsMenu; +using namespace Impacto::Vm::Interface; + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +OptionsVoiceSlider::OptionsVoiceSlider( + const Sprite& box, const Sprite& label, const Sprite& portrait, + const Sprite& mutedPortrait, glm::vec2 pos, glm::vec4 highlightTint, + float sliderSpeed, std::function select) + : OptionsSlider( + box, label, pos, highlightTint, + RectF(pos.x + VoiceSliderOffset.x, pos.y + VoiceSliderOffset.y, + box.ScaledWidth(), box.ScaledHeight()), + sliderSpeed, select), + Portrait(portrait), + MutedPortrait(mutedPortrait) { + Bounds = + RectF(Bounds.X, Bounds.Y, VoiceEntryDimensions.x, VoiceEntryDimensions.y); + EntryButton.Bounds = Bounds; + + std::function onClick = std::bind( + &OptionsVoiceSlider::MuteButtonOnClick, this, std::placeholders::_1); + const RectF muteButtonBounds(Bounds.GetPos().x + PortraitOffset.x, + Bounds.GetPos().y + PortraitOffset.y, + portrait.ScaledWidth(), portrait.ScaledHeight()); + MuteButton = ClickButton(0, muteButtonBounds, onClick); +} + +void OptionsVoiceSlider::Render() { + HighlightTint.a = Tint.a; + + if (HasFocus || EntryButton.Hovered) { + RectF highlightBoundBox(Bounds.X, Bounds.Y, VoiceEntryDimensions.x, + VoiceEntryDimensions.y); + Renderer->DrawRect(highlightBoundBox, HighlightTint); + Renderer->DrawRect(highlightBoundBox + RectF(2.0f, 2.0f, -4.0f, -4.0f), + glm::vec4(1.0f, 1.0f, 1.0f, Tint.a)); + } + + Renderer->DrawSprite(Muted ? MutedPortrait : Portrait, + Bounds.GetPos() + PortraitOffset, Tint); + Renderer->DrawSprite(LabelSprite, Bounds.GetPos() + NametagOffset, + Selected ? Tint : glm::vec4(0.0f, 0.0f, 0.0f, Tint.a)); + + RectF highlightBounds( + Bounds.X + VoiceSliderOffset.x, Bounds.Y + VoiceSliderOffset.y, + Progress * BoxSprite.ScaledWidth(), BoxSprite.ScaledHeight()); + Renderer->DrawRect(highlightBounds, HighlightTint); + Renderer->DrawSprite(BoxSprite, Bounds.GetPos() + VoiceSliderOffset, Tint); +} + +void OptionsVoiceSlider::Update(float dt) { + OptionsSlider::Update(dt); + + MuteButton.Update(dt); +} + +void OptionsVoiceSlider::UpdateInput() { + OptionsSlider::UpdateInput(); + MuteButton.UpdateInput(); + + if (!HasFocus) return; + + if (PADinputButtonWentDown & PAD1Y) { + Muted = !Muted; + Audio::Channels[Audio::AC_REV]->Play("sysse", 2, false, 0.0f); + } +} + +void OptionsVoiceSlider::Show() { + OptionsSlider::Show(); + MuteButton.Show(); +} + +void OptionsVoiceSlider::Hide() { + OptionsSlider::Hide(); + MuteButton.Hide(); +} + +void OptionsVoiceSlider::Move(glm::vec2 relativePos) { + OptionsSlider::Move(relativePos); + MuteButton.Move(relativePos); +} + +void OptionsVoiceSlider::MoveTo(glm::vec2 pos) { + const glm::vec2 relativePosition = pos - Bounds.GetPos(); + OptionsSlider::MoveTo(pos); + MuteButton.Move(relativePosition); +} + +void OptionsVoiceSlider::MuteButtonOnClick(ClickButton* target) { + if (HasFocus) Audio::Channels[Audio::AC_REV]->Play("sysse", 2, false, 0.0f); + + Muted = !Muted; +} + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/cclcc/optionsvoiceslider.h b/src/ui/widgets/cclcc/optionsvoiceslider.h new file mode 100644 index 00000000..ecac36d4 --- /dev/null +++ b/src/ui/widgets/cclcc/optionsvoiceslider.h @@ -0,0 +1,39 @@ +#pragma once + +#include "./optionsslider.h" +#include "../../../spritesheet.h" + +namespace Impacto { +namespace UI { +namespace Widgets { +namespace CCLCC { + +class OptionsVoiceSlider : public OptionsSlider { + public: + OptionsVoiceSlider(const Sprite& box, const Sprite& label, + const Sprite& portrait, const Sprite& mutedPortrait, + glm::vec2 pos, glm::vec4 highlightTint, float sliderSpeed, + std::function select); + void Render() override; + void Update(float dt) override; + void UpdateInput() override; + + void Show() override; + void Hide() override; + + void Move(glm::vec2 relativePos) override; + void MoveTo(glm::vec2 pos) override; + + private: + const Sprite& Portrait; + const Sprite& MutedPortrait; + + bool Muted = false; + ClickButton MuteButton; + void MuteButtonOnClick(ClickButton* target); +}; + +} // namespace CCLCC +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/clickbutton.cpp b/src/ui/widgets/clickbutton.cpp new file mode 100644 index 00000000..c0756a68 --- /dev/null +++ b/src/ui/widgets/clickbutton.cpp @@ -0,0 +1,53 @@ +#include "clickbutton.h" + +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" + +namespace Impacto { +namespace UI { +namespace Widgets { + +ClickButton::ClickButton(int id, RectF bounds, + std::function onClickHandler) + : Id(id), OnClickHandler(onClickHandler) { + Bounds = bounds; +} + +void ClickButton::UpdateInput() { + if (!Enabled) return; + + if (Input::CurrentInputDevice == Input::Device::Mouse && + (Input::PrevMousePos != Input::CurMousePos || + Vm::Interface::PADinputMouseWentDown)) { + Hovered = Bounds.ContainsPoint(Input::CurMousePos); + } else if (Input::CurrentInputDevice == Input::Device::Touch && + Input::TouchIsDown[0] && + Input::PrevTouchPos != Input::CurTouchPos) { + Hovered = Bounds.ContainsPoint(Input::CurTouchPos); + } + + if (Hovered && Vm::Interface::PADinputMouseWentDown & Vm::Interface::PAD1A) + OnClickHandler(this); +} + +void ClickButton::Show() { + Enabled = true; + + switch (Input::CurrentInputDevice) { + case Input::Device::Mouse: + Hovered = Bounds.ContainsPoint(Input::CurMousePos); + break; + case Input::Device::Touch: + Hovered = Bounds.ContainsPoint(Input::CurTouchPos); + break; + } +} + +void ClickButton::Hide() { + Enabled = false; + Hovered = false; +} + +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/clickbutton.h b/src/ui/widgets/clickbutton.h new file mode 100644 index 00000000..d844a04c --- /dev/null +++ b/src/ui/widgets/clickbutton.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "../widget.h" + +namespace Impacto { +namespace UI { +namespace Widgets { + +class ClickButton : public Widget { + public: + ClickButton() {} + ClickButton(int id, RectF bounds, + std::function onClickHandler); + + virtual void UpdateInput() override; + + virtual void Show(); + virtual void Hide(); + + int Id; + + std::function OnClickHandler; +}; + +} // namespace Widgets +} // namespace UI +} // namespace Impacto \ No newline at end of file diff --git a/src/ui/widgets/scrollbar.cpp b/src/ui/widgets/scrollbar.cpp index 87fa0963..5bbdd03b 100644 --- a/src/ui/widgets/scrollbar.cpp +++ b/src/ui/widgets/scrollbar.cpp @@ -10,6 +10,23 @@ namespace Widgets { using namespace Impacto::Profile::ScriptVars; using namespace Impacto::Vm::Interface; +Scrollbar::Scrollbar(int id, glm::vec2 pos, float start, float end, + float* value, ScrollbarDirection dir, + glm::vec2 trackBounds) + : Id(id), + StartValue(start), + EndValue(end), + Value(value), + Direction(dir), + TrackBounds(pos.x, pos.y, trackBounds.x, trackBounds.y), + Step((end - start) * 0.01f), + Length(dir == SBDIR_VERTICAL ? trackBounds.y : trackBounds.x), + ThumbBounds(0.0f, 0.0f, 0.0f, 0.0f), + ThumbLength(0.0f), + HasTrack(false) { + UpdatePosition(); +} + Scrollbar::Scrollbar(int id, glm::vec2 pos, float start, float end, float* value, ScrollbarDirection dir, Sprite const& thumb, glm::vec2 trackBounds, float thumbLength) @@ -97,7 +114,7 @@ void Scrollbar::UpdateInput() { Hovered = TrackBounds.ContainsPoint(Input::CurMousePos) || ThumbBounds.ContainsPoint(Input::CurMousePos); } - if (Hovered && Input::MouseButtonIsDown[SDL_BUTTON_LEFT]) { + if (Hovered && Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { Scrolling = true; } if (Input::MouseButtonIsDown[SDL_BUTTON_LEFT] && Scrolling) { diff --git a/src/ui/widgets/scrollbar.h b/src/ui/widgets/scrollbar.h index 70317bec..9a9919c9 100644 --- a/src/ui/widgets/scrollbar.h +++ b/src/ui/widgets/scrollbar.h @@ -11,6 +11,8 @@ enum ScrollbarDirection { SBDIR_VERTICAL, SBDIR_HORIZONTAL }; class Scrollbar : public Widget { public: + Scrollbar(int id, glm::vec2 pos, float start, float end, float* value, + ScrollbarDirection dir, glm::vec2 trackBounds); Scrollbar(int id, glm::vec2 pos, float start, float end, float* value, ScrollbarDirection dir, Sprite const& thumb, glm::vec2 trackBounds, float thumbLength); diff --git a/src/util.h b/src/util.h index 96ecbeeb..0d1f4c5f 100644 --- a/src/util.h +++ b/src/util.h @@ -72,6 +72,30 @@ struct RectF { rect.Y + rect.Height <= Y + Height); } + constexpr RectF operator+(RectF const& other) const { + return RectF(X + other.X, Y + other.Y, Width + other.Width, + Height + other.Height); + } + + constexpr RectF operator-(RectF const& other) const { + return RectF(X - other.X, Y - other.Y, Width - other.Width, + Height - other.Height); + } + + constexpr void operator+=(RectF const& other) { + X += other.X; + Y += other.Y; + Width += other.Width; + Height += other.Height; + } + + constexpr void operator-=(RectF const& other) { + X -= other.X; + Y -= other.Y; + Width -= other.Width; + Height -= other.Height; + } + constexpr bool operator==(RectF const& other) const { return X == other.X && Y == other.Y && Width == other.Width && Height == other.Height; @@ -80,6 +104,9 @@ struct RectF { return !(*this == other); } + constexpr glm::vec2 GetPos() const { return glm::vec2(X, Y); } + constexpr glm::vec2 GetSize() const { return glm::vec2(Width, Height); } + static RectF Coalesce(const RectF& first, const RectF& second); }; diff --git a/src/vm/inst_misc.cpp b/src/vm/inst_misc.cpp index 07649c85..5b602927 100644 --- a/src/vm/inst_misc.cpp +++ b/src/vm/inst_misc.cpp @@ -219,14 +219,6 @@ VmInstruction(InstOption) { "STUB instruction Option(type: Init)\n"); break; case 1: - if (!((Interface::PADinputButtonWentDown & Interface::PAD1B) || - (Interface::PADinputMouseWentDown & Interface::PAD1B))) { - ResetInstruction; - BlockThread; - } else { - SetFlag(SF_SUBMENUEXIT, true); - Interface::PADinputButtonWentDown |= Interface::PAD1A; - } ImpLogSlow(LL_Warning, LC_VMStub, "STUB instruction Option(type: Main)\n"); break;