Enable UI scaling in the scenario and implement Linux menus

Patch from @x-qq
Addresses #195

Note: UI scaling for the scenario editor still needs some work,
but it's basically functional.
This commit is contained in:
2020-01-28 20:13:24 -05:00
parent c2cdeb4990
commit 1d61d48ec3
14 changed files with 495 additions and 87 deletions

View File

@@ -110,6 +110,13 @@ bool OpenBoEMenu::handle_event(const sf::Event& event) {
// Returns true if event was consumed
bool OpenBoEMenu::handle_keypressed_event(const sf::Event& event) {
// NOTE: menu items get dynamically enabled/disabled based
// on gamestate, but these keyboard shortcuts do not. So
// this may not be the best way to implement them.
// NOTE: since we are manually adding keyboard shortcut descriptions
// to the menu items, they become parts of menu hierarchies
bool event_was_consumed { false };
if(this->is_control_key_pressed()) {
@@ -133,7 +140,7 @@ bool OpenBoEMenu::handle_keypressed_event(const sf::Event& event) {
return event_was_consumed;
}
bool OpenBoEMenu::is_control_key_pressed() {
bool OpenBoEMenu::is_control_key_pressed() const {
// NOTE: Control is not cross-platform (apple)
@@ -161,7 +168,7 @@ void OpenBoEMenu::update_for_game_state(eGameMode overall_mode, bool party_in_me
menubar->setMenuEnabled("Cast Mage", false);
menubar->setMenuEnabled("Cast Priest", false);
menubar->setMenuItemEnabled({ "File", "Save Game" }, false);
menubar->setMenuItemEnabled({ "File", "Save Game Ctrl-S" }, false);
if(party_in_memory) {
menubar->setMenuItemEnabled({ "File", "Save As..." }, true);
} else {
@@ -174,7 +181,7 @@ void OpenBoEMenu::update_for_game_state(eGameMode overall_mode, bool party_in_me
menubar->setMenuEnabled("Cast Mage", true);
menubar->setMenuEnabled("Cast Priest", true);
menubar->setMenuItemEnabled({ "File", "Save Game" }, true);
menubar->setMenuItemEnabled({ "File", "Save Game Ctrl-S" }, true);
menubar->setMenuItemEnabled({ "File", "Save As..." }, true);
}
}
@@ -195,7 +202,7 @@ void OpenBoEMenu::purge_spell_menus(tgui::MenuBar::Ptr& menubar) {
void OpenBoEMenu::update_mage_spells_menu(tgui::MenuBar::Ptr& menubar) {
// Add "About" menu item and store connection id
const OpenBoEMenu::MenuHierarchy about_hierarchy {{ "Cast Mage", "About this menu" }};
const OpenBoEMenu::MenuHierarchy about_hierarchy { "Cast Mage", "About this menu" };
menubar->addMenuItem(about_hierarchy);
this->spell_menus_connection_ids.push_back(
menubar->connectMenuItem(about_hierarchy, handle_menu_choice, eMenu::ABOUT_MAGE)
@@ -205,7 +212,7 @@ void OpenBoEMenu::update_mage_spells_menu(tgui::MenuBar::Ptr& menubar) {
for(int spell_id = 0; spell_id < NUM_MAGE_SPELLS; ++spell_id) {
eSpell spell = cSpell::fromNum(eSkill::MAGE_SPELLS, spell_id);
if(pc_can_cast_spell(this->univ.current_pc(), spell)) {
const OpenBoEMenu::MenuHierarchy spell_hierarchy = this->menu_hierarchy_from_spell(*spell);
const auto spell_hierarchy = this->menu_hierarchy_from_spell(*spell);
menubar->addMenuItem(spell_hierarchy);
// Connect and store connection id
this->spell_menus_connection_ids.push_back(
@@ -218,7 +225,7 @@ void OpenBoEMenu::update_mage_spells_menu(tgui::MenuBar::Ptr& menubar) {
void OpenBoEMenu::update_priest_spells_menu(tgui::MenuBar::Ptr& menubar) {
// Add "About" menu item and store connection id
const OpenBoEMenu::MenuHierarchy about_hierarchy { { "Cast Priest", "About this menu" } };
const OpenBoEMenu::MenuHierarchy about_hierarchy { "Cast Priest", "About this menu" };
menubar->addMenuItem(about_hierarchy);
this->spell_menus_connection_ids.push_back(
menubar->connectMenuItem(about_hierarchy, handle_menu_choice, eMenu::ABOUT_PRIEST)
@@ -228,7 +235,7 @@ void OpenBoEMenu::update_priest_spells_menu(tgui::MenuBar::Ptr& menubar) {
for (int spell_id = 0; spell_id < NUM_PRIEST_SPELLS; ++spell_id) {
eSpell spell = cSpell::fromNum(eSkill::PRIEST_SPELLS, spell_id);
if(pc_can_cast_spell(this->univ.current_pc(), spell)) {
const OpenBoEMenu::MenuHierarchy spell_hierarchy = this->menu_hierarchy_from_spell(*spell);
const auto spell_hierarchy = this->menu_hierarchy_from_spell(*spell);
menubar->addMenuItem(spell_hierarchy);
// Connect and store connection id
this->spell_menus_connection_ids.push_back(
@@ -238,7 +245,7 @@ void OpenBoEMenu::update_priest_spells_menu(tgui::MenuBar::Ptr& menubar) {
}
}
// Create a menu hierarcy from cSpell
// Create a menu hierarchy from cSpell
OpenBoEMenu::MenuHierarchy OpenBoEMenu::menu_hierarchy_from_spell(const cSpell& spell) const {
OpenBoEMenu::MenuHierarchy hier;

View File

@@ -22,7 +22,7 @@ public:
void update_spell_menus();
void update_monsters_menu();
private:
private:
using MenuHierarchy = std::vector<sf::String>;
@@ -34,7 +34,7 @@ public:
tgui::MenuBar::Ptr build_menubar() const;
bool handle_keypressed_event(const sf::Event&);
bool is_control_key_pressed();
bool is_control_key_pressed() const;
void add_menu_placeholders(tgui::MenuBar::Ptr&) const;
void add_persistent_menu_items(tgui::MenuBar::Ptr&) const;
tgui::MenuBar::Ptr get_menubar_ptr() const;

View File

@@ -27,6 +27,7 @@ elif str(platform) == "win32":
elif str(platform) == "posix":
scened_sources.extend(Split("""
scen.menus.linux.cpp
scen.menu.cpp
"""))
scened = env.Program("#build/bin/BoE Scenario Editor", scened_sources + common_sources)

View File

@@ -1776,13 +1776,13 @@ void handle_keystroke(sf::Event event) {
mouse_button_held = false;
}
void handle_scroll(sf::Event& event) {
void handle_scroll(const sf::Event& event) {
rectangle pal_rect = terrain_buttons_rect, right_area_rect = {0,0,RIGHT_AREA_HEIGHT,RIGHT_AREA_WIDTH};
right_area_rect.offset(RIGHT_AREA_UL_X, RIGHT_AREA_UL_Y);
pal_rect.offset(RIGHT_AREA_UL_X,RIGHT_AREA_UL_Y);
pal_rect.height() = 16 * 17 + 2;
fill_rect(mainPtr, right_area_rect, sf::Color::Magenta);
location pos(event.mouseWheel.x, event.mouseWheel.y);
location pos { translate_mouse_coordinates({event.mouseMove.x,event.mouseMove.y}) };
int amount = event.mouseWheel.delta;
if(right_sbar->isVisible() && pos.in(right_area_rect)) {
right_sbar->setPosition(right_sbar->getPosition() - amount);
@@ -2288,7 +2288,7 @@ void set_up_start_screen() {
set_lb(NLS - 2,LB_TEXT,LB_NO_ACTION,"Copyright 1997, All rights reserved.");
set_lb(NLS - 1,LB_TEXT,LB_NO_ACTION,version());
change_made = false;
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
}
void set_up_main_screen() {
@@ -2328,7 +2328,7 @@ void set_up_main_screen() {
shut_down_menus(4);
shut_down_menus(3);
redraw_screen();
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
}
void start_town_edit() {
@@ -2358,7 +2358,7 @@ void start_town_edit() {
current_ground = 0;
else if(town->terrain(i,j) == 2)
current_ground = 2;
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
}
void start_out_edit() {
@@ -2389,7 +2389,7 @@ void start_out_edit() {
current_ground = 0;
else if(current_terrain->terrain[i][j] == 2)
current_ground = 2;
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2402,7 +2402,7 @@ void start_terrain_editing() {
place_location();
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete/clear",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
}
void start_monster_editing(bool just_redo_text) {
@@ -2426,7 +2426,7 @@ void start_monster_editing(bool just_redo_text) {
set_rb(i - 1,RB_MONST, i, title);
}
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2456,7 +2456,7 @@ void start_item_editing(bool just_redo_text) {
set_rb(i,RB_ITEM, i, title);
}
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2483,7 +2483,7 @@ void start_special_item_editing(bool just_redo_text) {
set_rb(i,RB_SPEC_ITEM, i, title);
}
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2508,7 +2508,7 @@ void start_quest_editing(bool just_redo_text) {
set_rb(i, RB_QUEST, i, title);
}
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2533,7 +2533,7 @@ void start_shops_editing(bool just_redo_text) {
set_rb(i, RB_SHOP, i, title);
}
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2606,7 +2606,7 @@ void start_string_editing(eStrMode mode,short just_redo_text) {
pos = right_sbar->getPosition();
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2655,7 +2655,7 @@ void start_special_editing(short mode,short just_redo_text) {
case 2: set_rb(num_specs, RB_TOWN_SPEC, num_specs, make_new); break;
}
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}
@@ -2691,7 +2691,7 @@ void start_dialogue_editing(short restoring) {
}
set_rb(10 + n_nodes, RB_DIALOGUE, n_nodes, "Create New Node");
set_lb(NLS - 3,LB_TEXT,LB_NO_ACTION,"Alt-click node to delete",true);
update_mouse_spot(sf::Mouse::getPosition(mainPtr));
update_mouse_spot(translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr)));
redraw_screen();
}

View File

@@ -8,7 +8,7 @@ void flash_rect(rectangle to_flash);
void swap_terrain();
void set_new_terrain(ter_num_t selected_terrain);
void handle_keystroke(sf::Event event);
void handle_scroll(sf::Event& event);
void handle_scroll(const sf::Event& event);
void get_wandering_monst();
void get_town_info();
void get_sign_resource();

View File

@@ -19,6 +19,7 @@
#include "dialog.hpp"
#include "scen.core.hpp"
#include "scen.menus.hpp"
#include "scen.townout.hpp"
#include "scrollbar.hpp"
#include "res_image.hpp"
@@ -36,6 +37,7 @@ void sort_specials();
extern cOutdoors* current_terrain;
extern sf::RenderWindow mainPtr;
extern sf::View mainView;
extern cTown* current_town;
extern short cen_x, cen_y,current_terrain_type,cur_town;
extern cTown* town;
@@ -385,10 +387,23 @@ void load_graphics(){
void redraw_screen() {
rectangle windRect(mainPtr);
// Switch back to the default view while drawing the background tiles
// so that they are not upscaled
mainPtr.setView(mainPtr.getDefaultView());
tileImage(mainPtr,windRect,bg[20]);
mainPtr.setView(mainView);
draw_main_screen();
if(overall_mode < MODE_MAIN_SCREEN)
draw_terrain();
// DIRTY FIX to a problem that exist somewhere else. But where?
undo_clip(mainPtr);
drawMenuBar();
mainPtr.display();
}
@@ -961,7 +976,7 @@ void draw_terrain(){
yMin = cen_y + 5 - (324 / size);
yMax = cen_y + 5;
} else yMax = std::min(yMax, 324 / size);
std::cout << "Drawing map for x = " << xMin << "..." << xMax << " and y = " << yMin << "..." << yMax << std::endl;
// std::cout << "Drawing map for x = " << xMin << "..." << xMax << " and y = " << yMin << "..." << yMax << std::endl;
for(short q = xMin; q < xMax; q++)
for(short r = yMin; r < yMax; r++) {
if(q - xMin < 0 || q - xMin >= max_dim || r - yMin < 0 || r - yMin >= max_dim)
@@ -1298,13 +1313,13 @@ void place_location() {
short picture_wanted;
tileImage(mainPtr, terrain_buttons_rect, bg[17]);
frame_rect(mainPtr, terrain_buttons_rect, sf::Color::Black);
location mouse = sf::Mouse::getPosition(mainPtr);
location mouse = translate_mouse_coordinates(sf::Mouse::getPosition(mainPtr));
location moveTo(5, terrain_rects[255].top + 18);
draw_rect = text_rect;
draw_rect.offset(moveTo);
if(overall_mode < MODE_MAIN_SCREEN) {
std::cout << "Mouse: " << mouse << " Buttons: " << terrain_buttons_rect << " Terrain: " << terrain_rect << std::endl;
// std::cout << "Mouse: " << mouse << " Buttons: " << terrain_buttons_rect << " Terrain: " << terrain_rect << std::endl;
if(mouse.in(terrain_buttons_rect)) {
location rel_mouse = mouse;
rel_mouse.x -= RIGHT_AREA_UL_X;
@@ -1613,3 +1628,8 @@ bool container_there(location l) {
}
void record_display_strings(){}
// Translate mouse event coordinates based on the global view and viewport
sf::Vector2f translate_mouse_coordinates(sf::Vector2i const point) {
return mainPtr.mapPixelToCoords(point, mainView);
}

View File

@@ -1,4 +1,5 @@
#include <SFML/Graphics/RenderWindow.hpp>
#include "fields.hpp"
#include "location.hpp"
@@ -36,3 +37,4 @@ short string_length(char *str);
rectangle get_custom_rect (short which_rect);
void init_dialogs();
void record_display_strings();
sf::Vector2f translate_mouse_coordinates(sf::Vector2i const point);

View File

@@ -29,8 +29,8 @@
/* Globals */
bool All_Done = false;
sf::Event event;
sf::RenderWindow mainPtr;
sf::View mainView;
cTown* town = nullptr;
bool mouse_button_held = false,editing_town = false;
short cur_viewing_mode = 0;
@@ -49,12 +49,15 @@ location cur_out;
/* Prototypes */
static void init_scened(int, char*[]);
void Handle_One_Event();
void handle_events();
void handle_one_event(const sf::Event&);
void Handle_Activate();
void Handle_Update();
void Mouse_Pressed();
void redraw_everything();
void Mouse_Pressed(const sf::Event&);
void close_program();
void ding();
void init_main_window(sf::RenderWindow&, sf::View&);
sf::FloatRect compute_viewport(sf::RenderWindow&, float ui_scale);
cScenario scenario;
rectangle right_sbar_rect;
@@ -75,8 +78,7 @@ int main(int argc, char* argv[]) {
set_up_start_screen();
}
while(!All_Done)
Handle_One_Event();
handle_events();
close_program();
return 0;
@@ -99,29 +101,71 @@ static void init_sbar(std::shared_ptr<cScrollbar>& sbar, rectangle rect, int pgS
sbar->hide();
}
void init_scened(int argc, char* argv[]) {
init_directories(argv[0]);
init_menubar();
sync_prefs();
init_shaders();
init_tiling();
init_snd_tool();
sf::FloatRect compute_viewport(sf::RenderWindow& mainPtr, float ui_scale) {
// See compute_viewport() in boe.graphics.cpp
int const os_specific_y_offset =
#if defined(SFML_SYSTEM_WINDOWS) || defined(SFML_SYSTEM_MAC)
0;
#else
getMenubarHeight();
#endif
sf::FloatRect viewport;
sf::VideoMode mode = sf::VideoMode::getDesktopMode();
rectangle windRect;
windRect.width() = mode.width;
windRect.height() = mode.height;
int height = 420 + getMenubarHeight();
viewport.top = float(os_specific_y_offset) / mainPtr.getSize().y;
viewport.left = 0;
viewport.width = ui_scale;
viewport.height = ui_scale;
windRect.inset((windRect.right - 584) / 2,(windRect.bottom - height) / 2);
windRect.offset(0,18);
mainPtr.create(sf::VideoMode(windRect.width(), windRect.height()), "Blades of Exile Scenario Editor", sf::Style::Titlebar | sf::Style::Close);
mainPtr.setPosition(windRect.topLeft());
#ifndef __APPLE__ // This overrides Dock icon on OSX, which isn't what we want at all
return viewport;
}
void init_main_window (sf::RenderWindow & mainPtr, sf::View & mainView) {
// TODO: things might still be broken when upscaled.
// translate_mouse_coordinates has been applied in some places but more work might be needed.
// In particular, the white area on the right side of the main menu needs fixing.
float ui_scale = get_float_pref("UIScale", 1.0);
int const width = ui_scale * 584;
int const height = ui_scale * 420
#ifndef SFML_SYSTEM_WINDOWS
+ getMenubarHeight()
#endif
;
mainPtr.create(sf::VideoMode(width, height), "Blades of Exile Scenario Editor", sf::Style::Titlebar | sf::Style::Close);
mainPtr.setPosition({0,0});
// Initialize the view
mainView.setSize(width, height);
mainView.setCenter(width / 2, height / 2);
// Apply the viewport to the view
sf::FloatRect mainPort = compute_viewport(mainPtr, ui_scale);
mainView.setViewport(mainPort);
// Apply view to the main window
mainPtr.setView(mainView);
#ifndef SFML_SYSTEM_MAC // This overrides Dock icon on OSX, which isn't what we want at all
const ImageRsrc& icon = ResMgr::graphics.get("icon", true);
mainPtr.setIcon(icon->getSize().x, icon->getSize().y, icon->copyToImage().getPixelsPtr());
#endif
}
void init_scened(int argc, char* argv[]) {
init_directories(argv[0]);
sync_prefs();
init_main_window(mainPtr, mainView);
init_menubar();
init_shaders();
init_tiling();
init_snd_tool();
#ifdef SFML_SYSTEM_MAC
init_menubar(); // This is called twice because Windows and Mac have different ordering requirements
#endif
mainPtr.clear(sf::Color::Black);
mainPtr.display();
@@ -161,11 +205,30 @@ void init_scened(int argc, char* argv[]) {
redraw_screen();
}
void Handle_One_Event() {
ae_loading = false;
Handle_Update();
void handle_events() {
sf::Event currentEvent;
sf::Clock framerate_clock;
const sf::Int64 desired_microseconds_per_frame { 1000000 / 60 }; // us / FPS
while(!All_Done) {
while(mainPtr.pollEvent(currentEvent)) handle_one_event(currentEvent);
// Why do we have to set this to false after handling every event?
ae_loading = false;
redraw_everything();
// Prevent the loop from executing too fast.
const sf::Int64 remaining_time_budget = desired_microseconds_per_frame - framerate_clock.getElapsedTime().asMicroseconds();
if(remaining_time_budget > 0) sf::sleep(sf::microseconds(remaining_time_budget));
framerate_clock.restart();
}
}
void handle_one_event(sf::Event const & event) {
if(!mainPtr.waitEvent(event)) return;
// Check if the menubar wants to handle this event.
if(menuBarProcessEvent(event)) return;
switch(event.type) {
case sf::Event::KeyPressed:
@@ -174,13 +237,13 @@ void Handle_One_Event() {
break;
case sf::Event::MouseButtonPressed:
Mouse_Pressed();
Mouse_Pressed(event);
break;
case sf::Event::MouseMoved:
if(mouse_button_held)
handle_action(loc(event.mouseMove.x,event.mouseMove.y),event);
update_mouse_spot(loc(event.mouseMove.x,event.mouseMove.y));
handle_action(location { translate_mouse_coordinates({event.mouseMove.x,event.mouseMove.y})},event);
update_mouse_spot(location { translate_mouse_coordinates({event.mouseMove.x,event.mouseMove.y})});
break;
case sf::Event::MouseWheelMoved:
@@ -202,7 +265,7 @@ void Handle_One_Event() {
}
}
void Handle_Update() {
void redraw_everything() {
redraw_screen();
restore_cursor();
}
@@ -559,9 +622,11 @@ static void handleUpdateWhileScrolling(volatile bool& doneScrolling) {
mainPtr.setActive(false);
}
void Mouse_Pressed() {
void Mouse_Pressed(sf::Event const & event) {
// Translate coordinates
location mousePos { translate_mouse_coordinates({event.mouseButton.x, event.mouseButton.y}) };
location mousePos(event.mouseButton.x, event.mouseButton.y);
volatile bool doneScrolling = false;
if(right_sbar->isVisible() && mousePos.in(right_sbar->getBounds())) {
mainPtr.setActive(false);
@@ -578,7 +643,7 @@ void Mouse_Pressed() {
updater.join();
redraw_screen(/*REFRESH_RIGHT_BAR*/);
set_up_terrain_buttons(false);
} else handle_action(loc(event.mouseButton.x,event.mouseButton.y),event);
} else handle_action(mousePos,event);
}
void close_program() {

263
src/scenedit/scen.menu.cpp Normal file
View File

@@ -0,0 +1,263 @@
// Author: xq, Tuesday 2020-01-28
#include "scen.menu.hpp"
#include "scen.menus.hpp"
#include <stdexcept>
OpenBoESceneditMenu::OpenBoESceneditMenu(sf::RenderWindow& window)
: tgui { window }
, mainPtr { window } {
// Build a menubar and store it in tgui with a known name
this->tgui.add(this->build_menubar(), this->internal_menubar_widget_name);
}
tgui::MenuBar::Ptr OpenBoESceneditMenu::build_menubar() const {
auto menubar = tgui::MenuBar::create();
// XXX can we get this constant magic number from somewhere?
menubar->setSize(this->mainPtr.getSize().x, 20);
this->add_menu_placeholders(menubar);
this->add_persistent_menu_items(menubar);
return menubar;
}
// This method ensures that the menus on the menubar are in specific order
void OpenBoESceneditMenu::add_menu_placeholders(tgui::MenuBar::Ptr& menubar) const {
menubar->addMenu("File");
menubar->addMenu("Edit");
// With Advanced as a topmost item to have more vertical space for sub-items there
menubar->addMenuItem({"Scenario", "Advanced"});
menubar->addMenuItem({"Town", "Advanced"});
menubar->addMenuItem({"Outdoors", "Advanced"});
menubar->addMenu("Help");
}
// This method fills the menu with items that never change.
void OpenBoESceneditMenu::add_persistent_menu_items(tgui::MenuBar::Ptr& menubar) const {
const std::vector<std::pair <OpenBoESceneditMenu::MenuHierarchy, eMenu>> persistent_menu_items {
{ { "File", "New Scenario" }, eMenu::FILE_NEW },
{ { "File", "Open Scenario Ctrl-O" }, eMenu::FILE_OPEN },
{ { "File", "Close Scenario" }, eMenu::FILE_CLOSE },
{ { "File", "Save Scenario" }, eMenu::FILE_SAVE },
{ { "File", "Save As..." }, eMenu::FILE_SAVE_AS },
{ { "File", "Revert to Saved" }, eMenu::FILE_REVERT },
{ { "File", "Quit Ctrl-Q" }, eMenu::QUIT },
{ { "Edit", "Undo" }, eMenu::EDIT_UNDO },
{ { "Edit", "Redo" }, eMenu::EDIT_REDO },
{ { "Edit", "Cut" }, eMenu::EDIT_CUT },
{ { "Edit", "Copy" }, eMenu::EDIT_COPY },
{ { "Edit", "Paste" }, eMenu::EDIT_PASTE },
{ { "Edit", "Delete" }, eMenu::EDIT_DELETE },
{ { "Edit", "Select All" }, eMenu::EDIT_SELECT_ALL },
{ { "Scenario", "Advanced", "Edit Special Nodes" }, eMenu::SCEN_SPECIALS },
{ { "Scenario", "Advanced", "Edit Scenario Text" }, eMenu::SCEN_TEXT },
{ { "Scenario", "Advanced", "Edit Journal Entries" }, eMenu::SCEN_JOURNALS },
{ { "Scenario", "Advanced", "Import Town" }, eMenu::TOWN_IMPORT },
{ { "Scenario", "Advanced", "Import Outdoor Sector" }, eMenu::OUT_IMPORT },
{ { "Scenario", "Advanced", "Edit Saved Item Rectangles" }, eMenu::SCEN_SAVE_ITEM_RECTS },
{ { "Scenario", "Advanced", "Set Variable Town Entry" }, eMenu::TOWN_VARYING },
{ { "Scenario", "Advanced", "Set Scenario Event Timers" }, eMenu::SCEN_TIMERS },
{ { "Scenario", "Advanced", "Edit Item Placement Shortcuts" }, eMenu::SCEN_ITEM_SHORTCUTS },
{ { "Scenario", "Advanced", "Delete Last Town" }, eMenu::TOWN_DELETE },
{ { "Scenario", "Advanced", "Write Data To Text File" }, eMenu::SCEN_DATA_DUMP },
{ { "Scenario", "Advanced", "Do Full Text Dump" }, eMenu::SCEN_TEXT_DUMP },
// NOT IMPLEMENTED:
// { { "Scenario", "Advanced", "Scenario Shopping Text Dump" }, eMenu::NONE },
// { { "Scenario", "Advanced", "Scenario Monster Dump" }, eMenu::NONE },
// { { "Scenario", "Advanced", "Scenario Specials Dump" }, eMenu::NONE },
// { { "Scenario", "Advanced", "Scenario Object Data Dump" }, eMenu::NONE },
{ { "Scenario", "Create New Town" }, eMenu::TOWN_CREATE },
{ { "Scenario", "Create New Town" }, eMenu::TOWN_CREATE },
{ { "Scenario", "Resize Outdoors" }, eMenu::OUT_RESIZE },
{ { "Scenario", "Scenario Details" }, eMenu::SCEN_DETAILS },
{ { "Scenario", "Scenario Intro Text" }, eMenu::SCEN_INTRO },
{ { "Scenario", "Edit Custom Graphic Sheets" }, eMenu::SCEN_SHEETS },
{ { "Scenario", "Classify Custom Graphics" }, eMenu::SCEN_PICS },
{ { "Scenario", "Edit Custom Sounds" }, eMenu::SCEN_SNDS },
{ { "Town", "Advanced", "Edit Special Nodes" }, eMenu::TOWN_SPECIALS },
{ { "Town", "Advanced", "Edit Town Text" }, eMenu::TOWN_TEXT },
{ { "Town", "Advanced", "Edit Town Signs" }, eMenu::TOWN_SIGNS },
{ { "Town", "Advanced", "Advanced Town Details" }, eMenu::TOWN_ADVANCED },
{ { "Town", "Advanced", "Set Town Event Timers" }, eMenu::TOWN_TIMERS },
// NOT IMPLEMENTED:
// { { "Town", "Advanced", "Concise Town Report" }, eMenu::NONE },
{ { "Town", "Town Details" }, eMenu::TOWN_DETAILS },
{ { "Town", "Town Wandering Monsters" }, eMenu::TOWN_WANDERING },
{ { "Town", "Set Town Boundaries" }, eMenu::TOWN_BOUNDARIES },
{ { "Town", "Frill Up Terrain" }, eMenu::FRILL },
{ { "Town", "Remove Terrain Frills" }, eMenu::UNFRILL },
{ { "Town", "Edit Area Descriptions" }, eMenu::TOWN_AREAS },
{ { "Town", "Set Starting Location" }, eMenu::TOWN_START },
{ { "Town", "Add Random Items" }, eMenu::TOWN_ITEMS_RANDOM },
{ { "Town", "Set All Items Not Property" }, eMenu::TOWN_ITEMS_NOT_PROPERTY },
{ { "Town", "Clear All Items" }, eMenu::TOWN_ITEMS_CLEAR },
{ { "Outdoors", "Advanced", "Edit Special Nodes" }, eMenu::OUT_SPECIALS },
{ { "Outdoors", "Advanced", "Edit Outdoor Text" }, eMenu::OUT_TEXT },
{ { "Outdoors", "Advanced", "Edit Outdoor Signs" }, eMenu::OUT_SIGNS },
// NOT IMPLEMENTED:
// { { "Outdoors", "Advanced", "Concise Outdoor Report" }, eMenu::NONE },
{ { "Outdoors", "Outdoor Details" }, eMenu::OUT_DETAILS },
{ { "Outdoors", "Outdoor Wandering Monsters" }, eMenu::OUT_WANDERING },
{ { "Outdoors", "Outdoor Special Encounters" }, eMenu::OUT_ENCOUNTERS },
{ { "Outdoors", "Frill Up Terrain" }, eMenu::FRILL },
{ { "Outdoors", "Remove Terrain Frills" }, eMenu::UNFRILL },
{ { "Outdoors", "Edit Area Descriptions" }, eMenu::OUT_AREAS },
{ { "Outdoors", "Set Starting Location" }, eMenu::OUT_START },
{ { "Help", "Index" }, eMenu::HELP_TOC },
{ { "Help", "About Blades Scenario Editor" }, eMenu::ABOUT },
{ { "Help", "Getting Started" }, eMenu::HELP_START },
{ { "Help", "Testing Your Scenario" }, eMenu::HELP_TEST },
{ { "Help", "Distributing Your Scenario" }, eMenu::HELP_DIST },
};
// Note that signal connection ids are discarded.
for(const auto& item : persistent_menu_items) {
menubar->addMenuItem(item.first);
menubar->connectMenuItem(item.first, handle_menu_choice, item.second);
}
}
bool OpenBoESceneditMenu::handle_event(const sf::Event& event) {
if(event.type == sf::Event::KeyPressed && this->handle_keypressed_event(event))
return true;
return this->tgui.handleEvent(event);
}
// Returns true if event was consumed
bool OpenBoESceneditMenu::handle_keypressed_event(const sf::Event& event) {
// NOTE: menu items get dynamically enabled/disabled based
// on gamestate, but these keyboard shortcuts do not. So
// this may not be the best way to implement them.
// NOTE: since we are manually adding keyboard shortcut descriptions
// to the menu items, they become parts of menu hierarchies
bool event_was_consumed { false };
if(this->is_control_key_pressed()) {
switch(event.key.code) {
case sf::Keyboard::O:
handle_menu_choice(eMenu::FILE_OPEN);
event_was_consumed = true;
break;
case sf::Keyboard::Q:
handle_menu_choice(eMenu::QUIT);
event_was_consumed = true;
break;
default: break;
}
}
return event_was_consumed;
}
bool OpenBoESceneditMenu::is_control_key_pressed() const {
// NOTE: Control is not cross-platform (apple)
return (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)
|| sf::Keyboard::isKeyPressed(sf::Keyboard::RControl));
}
void OpenBoESceneditMenu::draw() {
this->tgui.draw();
}
tgui::MenuBar::Ptr OpenBoESceneditMenu::get_menubar_ptr() const {
return this->tgui.get<tgui::MenuBar>(this->internal_menubar_widget_name);
}
// mode 0: no scenario loaded
// mode 1: town menu disabled
// mode 2: outdoors menu disabled
// mode 3: both town and outdoors menus disabled
// mode 4: scenario loaded, everything enabled
void OpenBoESceneditMenu::update_for_mode(short mode) {
switch(mode){
case 0: this->update_for_mode_0(); break;
case 1: this->update_for_mode_1(); break;
case 2: this->update_for_mode_2(); break;
case 3: this->update_for_mode_3(); break;
case 4: this->update_for_mode_4(); break;
default:
throw std::runtime_error { "BUG: update_for_mode called with unknown mode!" };
}
}
void OpenBoESceneditMenu::update_for_mode_0() {
auto menubar = this->get_menubar_ptr();
menubar->setMenuItemEnabled({ "File", "Save Scenario" }, false);
menubar->setMenuItemEnabled({ "File", "Save As..." }, false);
menubar->setMenuEnabled("Scenario", false);
menubar->setMenuEnabled("Town", false);
menubar->setMenuEnabled("Outdoors", false);
}
void OpenBoESceneditMenu::update_for_mode_1() {
auto menubar = this->get_menubar_ptr();
menubar->setMenuEnabled("Town", false);
}
void OpenBoESceneditMenu::update_for_mode_2() {
auto menubar = this->get_menubar_ptr();
menubar->setMenuEnabled("Outdoors", false);
}
void OpenBoESceneditMenu::update_for_mode_3() {
auto menubar = this->get_menubar_ptr();
menubar->setMenuEnabled("Town", false);
menubar->setMenuEnabled("Outdoors", false);
}
void OpenBoESceneditMenu::update_for_mode_4() {
auto menubar = this->get_menubar_ptr();
menubar->setMenuItemEnabled({ "File", "Save Scenario" }, true);
menubar->setMenuItemEnabled({ "File", "Save As..." }, true);
menubar->setMenuEnabled("Scenario", true);
menubar->setMenuEnabled("Town", true);
menubar->setMenuEnabled("Outdoors", true);
}
void OpenBoESceneditMenu::update_edit_menu(cUndoList const & undo_list) {
auto menubar = this->get_menubar_ptr();
OpenBoESceneditMenu::MenuHierarchy const undo_menu_item { "Edit", "Undo" };
OpenBoESceneditMenu::MenuHierarchy const redo_menu_item { "Edit", "Redo" };
if(undo_list.noUndo()) {
menubar->setMenuItemEnabled(undo_menu_item, false);
} else {
menubar->setMenuItemEnabled(undo_menu_item, true);
}
if(undo_list.noRedo()) {
menubar->setMenuItemEnabled(redo_menu_item, false);
} else {
menubar->setMenuItemEnabled(redo_menu_item, true);
}
}

View File

@@ -0,0 +1,42 @@
// Author: xq, Tuesday 2020-01-28
#ifndef SCEN_MENU_HPP
#define SCEN_MENU_HPP
// NOTE: this also includes SFML for us
#include <TGUI/TGUI.hpp>
#include <vector>
#include "undo.hpp"
class OpenBoESceneditMenu {
public:
OpenBoESceneditMenu(sf::RenderWindow &);
bool handle_event(const sf::Event&);
void draw();
void update_for_mode(short mode);
void update_edit_menu(cUndoList const &);
private:
using MenuHierarchy = std::vector<sf::String>;
tgui::Gui tgui;
sf::RenderWindow& mainPtr;
const sf::String internal_menubar_widget_name { "openboe-scenedit-menu" };
tgui::MenuBar::Ptr build_menubar() const;
void add_menu_placeholders(tgui::MenuBar::Ptr&) const;
void add_persistent_menu_items(tgui::MenuBar::Ptr&) const;
tgui::MenuBar::Ptr get_menubar_ptr() const;
bool handle_keypressed_event(const sf::Event&);
bool is_control_key_pressed() const;
void update_for_mode_0();
void update_for_mode_1();
void update_for_mode_2();
void update_for_mode_3();
void update_for_mode_4();
};
#endif

View File

@@ -13,6 +13,10 @@ void init_menubar();
void shut_down_menus(short mode);
void update_edit_menu();
namespace sf { class Event; };
bool menuBarProcessEvent(const sf::Event&);
void drawMenuBar();
enum class eMenu {
NONE, ABOUT, QUIT, FRILL, UNFRILL,
FILE_NEW, FILE_OPEN, FILE_CLOSE, FILE_SAVE, FILE_SAVE_AS, FILE_REVERT,

View File

@@ -1,43 +1,33 @@
#include "scen.menus.hpp"
#include <map>
#include <SFML/Graphics/RenderWindow.hpp>
#include "scenario.hpp"
#include "winutil.hpp"
// This is the index of each menu on the menubar
enum {
FILE_MENU_POS = 0,
EDIT_MENU_POS = 1,
SCEN_MENU_POS = 2,
TOWN_MENU_POS = 3,
OUT_MENU_POS = 4,
HELP_MENU_POS = 6,
};
#include <memory>
#include "scen.menu.hpp"
#include "undo.hpp"
extern sf::RenderWindow mainPtr;
extern cScenario scenario;
std::map<int,eMenu> menuChoices;
extern cUndoList undo_list;
std::shared_ptr <OpenBoESceneditMenu> menu_ptr;
void init_menubar() {
menu_ptr.reset(new OpenBoESceneditMenu(mainPtr));
}
void shut_down_menus(short mode) {
menu_ptr->update_for_mode(mode);
}
void update_edit_menu() {
menu_ptr->update_edit_menu(undo_list);
}
#include "cursors.hpp"
bool menuBarProcessEvent(const sf::Event& event) {
return menu_ptr->handle_event(event);
}
#include "fileio.hpp"
#include "scen.actions.hpp"
void drawMenuBar() {
menu_ptr->draw();
}
extern short cur_town;
extern location cur_out;
extern cTown* town;
extern cOutdoors* current_terrain;
extern bool change_made, ae_loading;
void set_up_apple_events(int argc, char* argv[]) {
}

View File

@@ -164,6 +164,13 @@ void update_edit_menu() {
}
}
bool menuBarProcessEvent(const sf::Event&) {
return false;
}
void drawMenuBar() {
}
@implementation MenuHandler
-(void) menuChoice:(id) sender {
handle_menu_choice(eMenu([[sender representedObject] intValue]));

View File

@@ -253,3 +253,10 @@ void set_up_apple_events(int argc, char* argv[]) {
}
}
}
bool menuBarProcessEvent(const sf::Event&) {
return false;
}
void drawMenuBar() {
}