From 1d61d48ec3e0cdb0155a434072c1dfdab9502686 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Tue, 28 Jan 2020 20:13:24 -0500 Subject: [PATCH] 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. --- src/game/boe.menu.cpp | 23 ++- src/game/boe.menu.hpp | 4 +- src/scenedit/SConscript | 1 + src/scenedit/scen.actions.cpp | 30 ++-- src/scenedit/scen.actions.hpp | 2 +- src/scenedit/scen.graphics.cpp | 26 ++- src/scenedit/scen.graphics.hpp | 2 + src/scenedit/scen.main.cpp | 133 +++++++++++---- src/scenedit/scen.menu.cpp | 263 ++++++++++++++++++++++++++++++ src/scenedit/scen.menu.hpp | 42 +++++ src/scenedit/scen.menus.hpp | 4 + src/scenedit/scen.menus.linux.cpp | 38 ++--- src/scenedit/scen.menus.mac.mm | 7 + src/scenedit/scen.menus.win.cpp | 7 + 14 files changed, 495 insertions(+), 87 deletions(-) create mode 100644 src/scenedit/scen.menu.cpp create mode 100644 src/scenedit/scen.menu.hpp diff --git a/src/game/boe.menu.cpp b/src/game/boe.menu.cpp index 5ad15911..6ee7bab6 100644 --- a/src/game/boe.menu.cpp +++ b/src/game/boe.menu.cpp @@ -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; diff --git a/src/game/boe.menu.hpp b/src/game/boe.menu.hpp index 0295773f..c2718c30 100644 --- a/src/game/boe.menu.hpp +++ b/src/game/boe.menu.hpp @@ -22,7 +22,7 @@ public: void update_spell_menus(); void update_monsters_menu(); - private: +private: using MenuHierarchy = std::vector; @@ -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; diff --git a/src/scenedit/SConscript b/src/scenedit/SConscript index 853237fe..dd798ef3 100644 --- a/src/scenedit/SConscript +++ b/src/scenedit/SConscript @@ -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) diff --git a/src/scenedit/scen.actions.cpp b/src/scenedit/scen.actions.cpp index 83142f13..e73f3ea9 100644 --- a/src/scenedit/scen.actions.cpp +++ b/src/scenedit/scen.actions.cpp @@ -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(); } diff --git a/src/scenedit/scen.actions.hpp b/src/scenedit/scen.actions.hpp index a6a19ac7..ede570f3 100644 --- a/src/scenedit/scen.actions.hpp +++ b/src/scenedit/scen.actions.hpp @@ -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(); diff --git a/src/scenedit/scen.graphics.cpp b/src/scenedit/scen.graphics.cpp index ba435eaa..aca41eae 100644 --- a/src/scenedit/scen.graphics.cpp +++ b/src/scenedit/scen.graphics.cpp @@ -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); +} diff --git a/src/scenedit/scen.graphics.hpp b/src/scenedit/scen.graphics.hpp index b1666006..b5345480 100644 --- a/src/scenedit/scen.graphics.hpp +++ b/src/scenedit/scen.graphics.hpp @@ -1,4 +1,5 @@ +#include #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); diff --git a/src/scenedit/scen.main.cpp b/src/scenedit/scen.main.cpp index 3dcae2ba..40bf7a71 100644 --- a/src/scenedit/scen.main.cpp +++ b/src/scenedit/scen.main.cpp @@ -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& 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() { diff --git a/src/scenedit/scen.menu.cpp b/src/scenedit/scen.menu.cpp new file mode 100644 index 00000000..ca0e08b8 --- /dev/null +++ b/src/scenedit/scen.menu.cpp @@ -0,0 +1,263 @@ +// Author: xq, Tuesday 2020-01-28 + +#include "scen.menu.hpp" +#include "scen.menus.hpp" + +#include + +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> 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(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); + } +} + diff --git a/src/scenedit/scen.menu.hpp b/src/scenedit/scen.menu.hpp new file mode 100644 index 00000000..8ea9dfc9 --- /dev/null +++ b/src/scenedit/scen.menu.hpp @@ -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 +#include +#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; + + 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 diff --git a/src/scenedit/scen.menus.hpp b/src/scenedit/scen.menus.hpp index f686fdfc..862787d7 100644 --- a/src/scenedit/scen.menus.hpp +++ b/src/scenedit/scen.menus.hpp @@ -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, diff --git a/src/scenedit/scen.menus.linux.cpp b/src/scenedit/scen.menus.linux.cpp index 86aca2b7..42bfcf5e 100644 --- a/src/scenedit/scen.menus.linux.cpp +++ b/src/scenedit/scen.menus.linux.cpp @@ -1,43 +1,33 @@ - #include "scen.menus.hpp" -#include #include -#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 +#include "scen.menu.hpp" +#include "undo.hpp" extern sf::RenderWindow mainPtr; -extern cScenario scenario; -std::map menuChoices; +extern cUndoList undo_list; +std::shared_ptr 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[]) { } diff --git a/src/scenedit/scen.menus.mac.mm b/src/scenedit/scen.menus.mac.mm index c3e1b08a..26d88688 100644 --- a/src/scenedit/scen.menus.mac.mm +++ b/src/scenedit/scen.menus.mac.mm @@ -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])); diff --git a/src/scenedit/scen.menus.win.cpp b/src/scenedit/scen.menus.win.cpp index 692ed16b..486f74c7 100644 --- a/src/scenedit/scen.menus.win.cpp +++ b/src/scenedit/scen.menus.win.cpp @@ -253,3 +253,10 @@ void set_up_apple_events(int argc, char* argv[]) { } } } + +bool menuBarProcessEvent(const sf::Event&) { + return false; +} + +void drawMenuBar() { +}