12 const glm::vec2 MenuPosition(40.f, 70.f);
13 const sf::Color LabelColor(0, 60, 130);
14 constexpr
unsigned int FontSize = 30;
15 constexpr
float FontSizeF =
static_cast<float>(FontSize);
16 constexpr
unsigned int LabelSize = FontSize + 5;
17 constexpr
unsigned int SmallSize = 22;
19 sf::VideoMode defaultVideoMode() {
23 sf::VideoMode bestFullscreenMode() {
24 const unsigned int ar =
25 sf::VideoMode::getDesktopMode().width / sf::VideoMode::getDesktopMode().height;
26 const std::vector<sf::VideoMode>& modes = sf::VideoMode::getFullscreenModes();
27 sf::VideoMode
const* best =
nullptr;
28 unsigned int highest = 0;
30 for (
const sf::VideoMode& mode : modes) {
31 const unsigned int mar = mode.width / mode.height;
33 const unsigned int s = mode.width * mode.height;
34 if (s > highest || best ==
nullptr) {
41 if (best ==
nullptr) {
42 BL_LOG_ERROR <<
"Failed to select a valid fullscreen video mode. Attempting default mode";
43 return defaultVideoMode();
48 std::string volumeString() {
49 return "Volume: " + std::to_string(
static_cast<int>(bl::audio::AudioSystem::getVolume())) +
"%";
54 using namespace bl::menu;
62 :
State(s,
bl::engine::StateMask::Menu)
63 , state(MenuState::TopMenu)
64 , volumeEntry(s.engine()) {}
70 const auto joinPath = bl::util::FileUtil::joinPath;
75 engine.renderer().texturePool().getOrLoadTexture(joinPath(Path,
"background.png"));
76 background.create(engine, bgndTexture);
78 hint.create(engine, font,
"", 18, sf::Color(65, 10, 0));
80 hint.getTransform().setPosition(590.f, 440.f);
81 hint.setParent(background);
83 const auto makeBack = [
this, &font]() {
84 Item::Ptr bi = TextItem::create(
"Back", font, sf::Color::Black, FontSize);
85 bi->getSignal(Item::Activated).willAlwaysCall(std::bind(&SettingsMenu::back,
this));
86 bi->getSignal(Item::Selected).willAlwaysCall([
this]() { setHint(
""); });
92 engine, engine.renderer().getObserver(), ArrowSelector::create(10.f, sf::Color::Black));
93 Item::Ptr videoItem = TextItem::create(
"Video", font, sf::Color::Black, FontSize);
94 videoItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
95 enterState(MenuState::VideoMenu);
97 videoItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
98 setHint(
"Settings related to the Peoplemon window.");
100 Item::Ptr audioItem = TextItem::create(
"Audio", font, sf::Color::Black, FontSize);
101 audioItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
102 enterState(MenuState::AudioMenu);
104 audioItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
105 setHint(
"Settings related to music and sounds.");
107 Item::Ptr controlsItem = TextItem::create(
"Controls", font, sf::Color::Black, FontSize);
108 controlsItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
109 enterState(MenuState::ControlsTopMenu);
111 controlsItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
112 setHint(
"Change your controls for both keyboard/mouse and controller.");
115 topMenu.setRootItem(videoItem);
116 topMenu.addItem(audioItem, videoItem.get(), Item::Bottom);
117 topMenu.addItem(controlsItem, audioItem.get(), Item::Bottom);
118 topMenu.addItem(makeBack(), controlsItem.get(), Item::Bottom);
119 topMenu.setPosition(MenuPosition);
123 engine, engine.renderer().getObserver(), ArrowSelector::create(10.f, sf::Color::Black));
124 Item::Ptr videoLabel = TextItem::create(
"Video Settings", font, LabelColor, LabelSize);
125 videoLabel->setSelectable(
false);
126 windowModeTextItem = TextItem::create(windowModeString(), font, sf::Color::Black, FontSize);
127 windowModeDropdownItem =
128 SubmenuItem::create(videoMenu, windowModeTextItem, Item::Right, Item::Bottom);
129 windowModeDropdownItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
132 windowModeDropdownItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
133 setHint(
"Set whether to be fullscreen or windowed.");
135 fullscreenItem = TextItem::create(
"Fullscreen", font, sf::Color::Black, FontSize);
136 fullscreenItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
137 onWindowModeChange(
true);
139 fullscreenItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
140 setHint(
"The Peoplemon window will be fullscreen.");
142 windowedItem = TextItem::create(
"Windowed", font, sf::Color::Black, FontSize);
143 windowedItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
144 onWindowModeChange(
false);
146 windowedItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
148 "The Peoplemon window will be a regular window that can be moved, resized, etc.");
150 windowModeDropdownItem->addOption(fullscreenItem);
151 windowModeDropdownItem->addOption(windowedItem);
152 windowModeDropdownItem->addOption(makeBack());
153 vsyncItem = ToggleTextItem::create(
"Vsync Enabled:", font, sf::Color::Black, FontSize);
154 vsyncItem->setBoxProperties(
155 sf::Color::White, sf::Color::Black, FontSizeF, 2.f, FontSizeF * 0.4f,
false);
156 vsyncItem->getSignal(Item::Activated).willAlwaysCall([
this]() { onVsyncUpdate(); });
157 vsyncItem->setChecked(
systems.
engine().settings().windowParameters().vsyncEnabled());
158 vsyncItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
160 "Vsync limits the render rate to your monitor refresh rate to prevent tearing.");
163 videoMenu.setRootItem(windowModeDropdownItem);
164 videoMenu.addItem(videoLabel, windowModeDropdownItem.get(), Item::Top);
165 videoMenu.addItem(vsyncItem, windowModeDropdownItem.get(), Item::Bottom);
166 videoMenu.addItem(makeBack(), vsyncItem.get(), Item::Bottom);
167 videoMenu.setPosition(MenuPosition);
168 videoMenu.setPadding({25.f, 10.f});
172 engine, engine.renderer().getObserver(), ArrowSelector::create(10.f, sf::Color::Black));
173 Item::Ptr audioLabel = TextItem::create(
"Audio Settings", font, LabelColor, LabelSize);
174 audioLabel->setSelectable(
false);
175 muteItem = ToggleTextItem::create(
"Mute", font, sf::Color::Black, FontSize);
176 muteItem->setChecked(bl::audio::AudioSystem::getMuted());
177 muteItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
178 bl::audio::AudioSystem::setMuted(muteItem->isChecked());
180 muteItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
181 setHint(
"Enable and disable all sounds and music.");
183 volumeItem = TextItem::create(volumeString(), font, sf::Color::Black, FontSize);
184 volumeItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
185 enterState(MenuState::AudioSelectVolume);
187 volumeItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
188 setHint(
"Sets the volume of all sounds and music.");
191 audioMenu.setRootItem(muteItem);
192 audioMenu.addItem(audioLabel, muteItem.get(), Item::Top);
193 audioMenu.addItem(volumeItem, muteItem.get(), Item::Bottom);
194 audioMenu.addItem(makeBack(), volumeItem.get(), Item::Bottom);
195 audioMenu.setPosition(MenuPosition);
198 controlsTopMenu.create(
199 engine, engine.renderer().getObserver(), ArrowSelector::create(10.f, sf::Color::Black));
200 Item::Ptr topCtrlLabel = TextItem::create(
"Control Settings", font, LabelColor, LabelSize);
201 topCtrlLabel->setSelectable(
false);
203 TextItem::create(
"Keyboard and mouse", font, sf::Color::Black, FontSize);
204 kbmItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
205 enterState(MenuState::ControlsKBMMenu);
207 kbmItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
208 setHint(
"Configure your controls when using keyboard and mouse.");
210 Item::Ptr padItem = TextItem::create(
"Controller", font, sf::Color::Black, FontSize);
211 padItem->getSignal(Item::Activated).willAlwaysCall([
this]() {
212 enterState(MenuState::ControlsPadMenu);
214 padItem->getSignal(Item::Selected).willAlwaysCall([
this]() {
215 setHint(
"Configure your controls when using a controller.");
218 controlsTopMenu.setRootItem(kbmItem);
219 controlsTopMenu.addItem(topCtrlLabel, kbmItem.get(), Item::Top);
220 controlsTopMenu.addItem(padItem, kbmItem.get(), Item::Bottom);
221 controlsTopMenu.addItem(makeBack(), padItem.get(), Item::Bottom);
222 controlsTopMenu.setPosition(MenuPosition);
224 const auto makeCtrl = [
this, &font](
unsigned int ctrl,
bool kbm) {
226 TextItem::create(ctrlString(ctrl, kbm), font, sf::Color::Black, SmallSize);
227 TextItem* rp = item.get();
228 item->getSignal(Item::Activated).willAlwaysCall([
this, ctrl, kbm, rp]() {
230 startBindControl(kbm, ctrl);
232 item->getSignal(Item::Selected).willAlwaysCall([
this]() {
233 setHint(
"Select to rebind this control.");
239 controlsKbmMenu.create(
240 engine, engine.renderer().getObserver(), ArrowSelector::create(8.f, sf::Color::Black));
241 Item::Ptr kbmLabel = TextItem::create(
"Keyboard and Mouse", font, LabelColor, LabelSize);
242 kbmLabel->setSelectable(
false);
247 bl::menu::TextItem::Ptr kbmSprintItem = makeCtrl(
Control::Sprint,
true);
249 bl::menu::TextItem::Ptr kbmBackItem = makeCtrl(
Control::Back,
true);
250 bl::menu::TextItem::Ptr kbmPauseItem = makeCtrl(
Control::Pause,
true);
251 Item::Ptr backItem = makeBack();
253 controlsKbmMenu.setRootItem(kbmUpItem);
254 controlsKbmMenu.setPosition(MenuPosition);
255 controlsKbmMenu.setPadding({75.f, 12.f});
257 controlsKbmMenu.addItem(kbmLabel, kbmUpItem.get(), Item::Top);
258 controlsKbmMenu.addItem(kbmRightItem, kbmUpItem.get(), Item::Bottom);
259 controlsKbmMenu.addItem(kbmDownItem, kbmRightItem.get(), Item::Bottom);
260 controlsKbmMenu.addItem(kbmLeftItem, kbmDownItem.get(), Item::Bottom);
261 controlsKbmMenu.addItem(backItem, kbmLeftItem.get(), Item::Bottom);
263 controlsKbmMenu.addItem(kbmSprintItem, kbmUpItem.get(), Item::Right);
264 controlsKbmMenu.addItem(kbmInteractItem, kbmSprintItem.get(), Item::Bottom);
265 controlsKbmMenu.addItem(kbmBackItem, kbmInteractItem.get(), Item::Bottom);
266 controlsKbmMenu.addItem(kbmPauseItem, kbmBackItem.get(), Item::Bottom);
268 controlsKbmMenu.attachExisting(kbmRightItem.get(), kbmInteractItem.get(), Item::Left);
269 controlsKbmMenu.attachExisting(kbmDownItem.get(), kbmBackItem.get(), Item::Left);
270 controlsKbmMenu.attachExisting(kbmLeftItem.get(), kbmPauseItem.get(), Item::Left);
271 controlsKbmMenu.attachExisting(backItem.get(), kbmPauseItem.get(), Item::Bottom,
false);
274 controlsPadMenu.create(
275 engine, engine.renderer().getObserver(), ArrowSelector::create(8.f, sf::Color::Black));
276 Item::Ptr padLabel = TextItem::create(
"Controller", font, LabelColor, LabelSize);
277 padLabel->setSelectable(
false);
282 bl::menu::TextItem::Ptr padSprintItem = makeCtrl(
Control::Sprint,
false);
284 bl::menu::TextItem::Ptr padBackItem = makeCtrl(
Control::Back,
false);
285 bl::menu::TextItem::Ptr padPauseItem = makeCtrl(
Control::Pause,
false);
286 backItem = makeBack();
288 controlsPadMenu.setRootItem(padUpItem);
289 controlsPadMenu.setPosition(MenuPosition);
290 controlsPadMenu.setPadding({75.f, 12.f});
292 controlsPadMenu.addItem(padLabel, padUpItem.get(), Item::Top);
293 controlsPadMenu.addItem(padRightItem, padUpItem.get(), Item::Bottom);
294 controlsPadMenu.addItem(padDownItem, padRightItem.get(), Item::Bottom);
295 controlsPadMenu.addItem(padLeftItem, padDownItem.get(), Item::Bottom);
296 controlsPadMenu.addItem(backItem, padLeftItem.get(), Item::Bottom);
298 controlsPadMenu.addItem(padSprintItem, padUpItem.get(), Item::Right);
299 controlsPadMenu.addItem(padInteractItem, padSprintItem.get(), Item::Bottom);
300 controlsPadMenu.addItem(padBackItem, padInteractItem.get(), Item::Bottom);
301 controlsPadMenu.addItem(padPauseItem, padBackItem.get(), Item::Bottom);
303 controlsPadMenu.attachExisting(padRightItem.get(), padInteractItem.get(), Item::Left);
304 controlsPadMenu.attachExisting(padDownItem.get(), padBackItem.get(), Item::Left);
305 controlsPadMenu.attachExisting(padLeftItem.get(), padPauseItem.get(), Item::Left);
306 controlsPadMenu.attachExisting(backItem.get(), padPauseItem.get(), Item::Bottom,
false);
309 auto overlay = engine.renderer().getObserver().pushScene<bl::rc::Overlay>();
310 background.addToScene(overlay, bl::rc::UpdateSpeed::Static);
311 hint.addToScene(overlay, bl::rc::UpdateSpeed::Static);
312 topMenu.addToOverlay(background.entity());
313 videoMenu.addToOverlay(background.entity());
314 audioMenu.addToOverlay(background.entity());
315 controlsTopMenu.addToOverlay(background.entity());
316 controlsKbmMenu.addToOverlay(background.entity());
317 controlsPadMenu.addToOverlay(background.entity());
319 inputDriver.drive(&topMenu);
320 engine.inputSystem().getActor().addListener(*
this);
321 enterState(MenuState::TopMenu);
325 engine.inputSystem().getActor().removeListener(*
this);
326 engine.renderer().getObserver().popScene();
328 engine.inputSystem().saveToConfig();
333 if (state == MenuState::ControlsBindingControl && ctrlConfigurator.finished()) {
334 ctrlItem->getTextObject().getSection().setString(ctrlString(bindingCtrl, bindingKbm));
336 enterState(MenuState::ControlsKBMMenu);
337 controlsKbmMenu.refreshPositions();
340 enterState(MenuState::ControlsPadMenu);
341 controlsPadMenu.refreshPositions();
346 void SettingsMenu::enterState(MenuState s) {
349 if (state != MenuState::AudioSelectVolume) { volumeEntry.
hide(); }
352 topMenu.setHidden(
true);
353 videoMenu.setHidden(
true);
354 audioMenu.setHidden(
true);
355 controlsTopMenu.setHidden(
true);
356 controlsKbmMenu.setHidden(
true);
357 controlsPadMenu.setHidden(
true);
362 case MenuState::TopMenu:
363 inputDriver.drive(&topMenu);
364 topMenu.setHidden(
false);
367 case MenuState::VideoMenu:
368 inputDriver.drive(&videoMenu);
369 videoMenu.setHidden(
false);
372 case MenuState::VideoSelectMode:
373 videoMenu.setHidden(
false);
375 case MenuState::AudioMenu:
376 inputDriver.drive(&audioMenu);
377 audioMenu.setHidden(
false);
380 case MenuState::AudioSelectVolume:
381 audioMenu.setHidden(
false);
382 volumeEntry.
configure(0, 100,
static_cast<int>(bl::audio::AudioSystem::getVolume()));
383 volumeEntry.
setPosition({volumeItem->getPosition().x + volumeItem->getSize().x +
384 audioMenu.getBounds().left + 20.f,
385 volumeItem->getPosition().y + audioMenu.getBounds().top});
387 case MenuState::ControlsTopMenu:
388 inputDriver.drive(&controlsTopMenu);
389 controlsTopMenu.setHidden(
false);
390 sm = &controlsTopMenu;
392 case MenuState::ControlsKBMMenu:
393 inputDriver.drive(&controlsKbmMenu);
394 controlsKbmMenu.setHidden(
false);
395 sm = &controlsKbmMenu;
397 case MenuState::ControlsPadMenu:
398 inputDriver.drive(&controlsPadMenu);
399 controlsPadMenu.setHidden(
false);
400 sm = &controlsPadMenu;
402 case MenuState::ControlsBindingControl:
403 controlsKbmMenu.setHidden(!bindingKbm);
404 controlsPadMenu.setHidden(bindingKbm);
411 if (sm !=
nullptr) { sm->setSelectedItem(
const_cast<Item*
>(sm->getSelectedItem())); }
416 bool SettingsMenu::observe(
const bl::input::Actor&,
unsigned int activatedControl,
417 bl::input::DispatchType,
bool eventTriggered) {
419 if (eventTriggered) { back(); }
423 case MenuState::AudioSelectVolume:
424 switch (activatedControl) {
426 volumeEntry.
up(1, eventTriggered);
427 bl::audio::AudioSystem::setVolume(
static_cast<float>(volumeEntry.
curQty()));
430 volumeEntry.
up(10, eventTriggered);
431 bl::audio::AudioSystem::setVolume(
static_cast<float>(volumeEntry.
curQty()));
434 volumeEntry.
down(1, eventTriggered);
435 bl::audio::AudioSystem::setVolume(
static_cast<float>(volumeEntry.
curQty()));
438 volumeEntry.
down(10, eventTriggered);
439 bl::audio::AudioSystem::setVolume(
static_cast<float>(volumeEntry.
curQty()));
442 if (eventTriggered) { back(); }
448 case MenuState::ControlsBindingControl:
452 inputDriver.sendControl(activatedControl, eventTriggered);
460 void SettingsMenu::back() {
462 case MenuState::TopMenu:
465 case MenuState::VideoMenu:
466 case MenuState::AudioMenu:
467 case MenuState::ControlsTopMenu:
468 enterState(MenuState::TopMenu);
470 case MenuState::VideoSelectMode:
471 windowModeDropdownItem->closeMenu();
472 enterState(MenuState::VideoMenu);
474 case MenuState::AudioSelectVolume:
475 volumeItem->getTextObject().getSection().setString(volumeString());
476 enterState(MenuState::AudioMenu);
478 case MenuState::ControlsKBMMenu:
479 case MenuState::ControlsPadMenu:
480 enterState(MenuState::ControlsTopMenu);
487 void SettingsMenu::startBindControl(
bool kbm,
unsigned int ctrl) {
492 ctrlConfigurator.start(a.getKBMTriggerControl(ctrl));
493 setHint(
"Press any key or mouse button to rebind the control.");
496 ctrlConfigurator.start(a.getJoystickTriggerControl(ctrl));
497 setHint(
"Move a joystick or press a button on your controller to rebind the control.");
499 enterState(MenuState::ControlsBindingControl);
502 std::string SettingsMenu::ctrlString(
unsigned int ctrl,
bool kbm)
const {
504 const std::string cs = kbm ? a.getKBMTriggerControl(ctrl).toString() :
505 a.getJoystickTriggerControl(ctrl).toString();
509 return "Move up: " + cs;
511 return "Move right: " + cs;
513 return "Move down: " + cs;
515 return "Move left: " + cs;
517 return "Sprint: " + cs;
519 return "Interact: " + cs;
521 return "Back: " + cs;
523 return "Pause: " + cs;
525 return "ERROR: Unknown control";
529 void SettingsMenu::onWindowModeOpen() {
530 enterState(MenuState::VideoSelectMode);
531 videoMenu.setSelectedItem(isFullscreen() ? fullscreenItem.get() : windowedItem.get());
534 bool SettingsMenu::isFullscreen()
const {
535 return (
systems.
engine().settings().windowParameters().style() & sf::Style::Fullscreen) != 0;
538 std::string SettingsMenu::windowModeString()
const {
539 return std::string(
"Window mode: ") + (isFullscreen() ?
"Fullscreen" :
"Windowed");
542 void SettingsMenu::onWindowModeChange(
bool fs) {
543 windowModeDropdownItem->closeMenu();
544 if (fs != isFullscreen()) {
545 bl::engine::Settings::WindowParameters params =
548 params.withVideoMode(bestFullscreenMode());
549 params.withStyle(params.style() | sf::Style::Fullscreen);
552 params.withVideoMode(defaultVideoMode());
553 params.withStyle(params.style() & (~sf::Style::Fullscreen));
557 windowModeTextItem->getTextObject().getSection().setString(windowModeString());
558 videoMenu.refreshPositions();
560 enterState(MenuState::VideoMenu);
563 void SettingsMenu::onVsyncUpdate() {
564 bl::engine::Settings::WindowParameters params =
systems.
engine().settings().windowParameters();
565 params.withVSyncEnabled(vsyncItem->isChecked());
569 void SettingsMenu::setHint(
const std::string& m) { hint.getSection().setString(m); }
Peoplemon specific input functionality around the BLIB input system.
Parent namespace for all functionality unique to the game.
static void save()
Saves the config to the data file.
static const sf::VulkanFont & MenuFont()
static const std::string & MenuImagePath()
static int WindowHeight()
void configure(int minQty, int maxQty, int qty)
Configures the entry with the given parameters and adds to the overlay.
void setPosition(const sf::Vector2f &position)
Sets the position of the selector.
void up(int q, bool ignoreDebounce)
Increments the qty up.
void down(int q, bool ignoreDebounce)
Decrements the qty down.
void hide()
Removes the quantity entry from the overlay.
int curQty() const
Returns the currently selected qty.
Owns all primary systems and a reference to the engine.
const bl::engine::Engine & engine() const
Const accessor for the Engine.
static bl::engine::State::Ptr create(core::system::Systems &systems)
Creates the settings menu state.
virtual const char * name() const override
Returns "SettingsMenu".
virtual void activate(bl::engine::Engine &engine) override
Activates the state.
virtual void update(bl::engine::Engine &engine, float dt, float) override
Updates the state and menus and whatnot.
virtual void deactivate(bl::engine::Engine &engine) override
Deactivates the state.
Parent to all game states. Provides some commonly required data like core game systems.
core::system::Systems & systems