From 83dc4ab30478e70dab6e08cd9b30c3434d45eca6 Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Sat, 28 Dec 2024 14:09:42 +0100 Subject: [PATCH 1/7] Implement units cycling Since the new Units Report was introduced in 3.1, there was no longer a way to cycle all units of a given unit type on the map. With this commit scrolling the mouse wheel on the selected unit icon in the "Unit Controls" widget, cycles through idle or sentried units of the same unit type. --- client/hudwidget.cpp | 106 +++++++++++++++++++++++++++- client/hudwidget.h | 7 +- docs/Manuals/Game/unit-controls.rst | 3 + 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/client/hudwidget.cpp b/client/hudwidget.cpp index 83825dc920..ba1ee79539 100644 --- a/client/hudwidget.cpp +++ b/client/hudwidget.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 1996-2023 Freeciv21 and Freeciv contributors. This file is + Copyright (c) 1996-2024 Freeciv21 and Freeciv contributors. This file is part of Freeciv21. Freeciv21 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the @@ -528,6 +528,9 @@ hud_units::hud_units(QWidget *parent) setLayout(main_layout); mw = new move_widget(this); setFocusPolicy(Qt::ClickFocus); + + connect(&unit_label, &click_label::wheel_scrolled, this, + &hud_units::cycle_units); } /** @@ -770,6 +773,89 @@ void hud_units::update_actions() show(); } +/** + Cycle idle or sentried units for the unittype of the currently + selected unit. + + The sign of direction determines in which direction the units will + be cycled. + */ +void hud_units::cycle_units(const int direction) +{ + if (get_num_units_in_focus() != 1) { + return; + } + + struct unit *current_unit = head_of_units_in_focus(); + + // Get the count of relevant units and determine the index of the + // current unit among these units. If the current unit is not among + // the relevant units, then index 0 is assumed by default. + int unit_count = 0; + int current_unit_index = 0; + unit_list_iterate(client_player()->units, punit) + { + if (punit->utype != current_unit->utype) { + continue; + } + + if (ACTIVITY_IDLE != punit->activity + && ACTIVITY_SENTRY != punit->activity) { + continue; + } + + if (!can_unit_do_activity(punit, ACTIVITY_IDLE)) { + continue; + } + + if (current_unit->id == punit->id) { + current_unit_index = unit_count; + } + + unit_count++; + } + unit_list_iterate_end; + + // Determine the index of the unit to be selected next. + int cycle_unit_index = current_unit_index; + if (direction > 0) { + cycle_unit_index++; + } else if (direction < 0) { + cycle_unit_index--; + } + + if (cycle_unit_index < 0) { + cycle_unit_index = unit_count - 1; + } else if (cycle_unit_index >= unit_count) { + cycle_unit_index = 0; + } + + // Select the next unit. + unit_count = 0; + unit_list_iterate(client_player()->units, punit) + { + if (punit->utype != current_unit->utype) { + continue; + } + + if (ACTIVITY_IDLE != punit->activity + && ACTIVITY_SENTRY != punit->activity) { + continue; + } + + if (!can_unit_do_activity(punit, ACTIVITY_IDLE)) { + continue; + } + + if (unit_count == cycle_unit_index) { + unit_focus_set_and_select(punit); + } + + unit_count++; + } + unit_list_iterate_end; +} + /** Custom label with extra mouse events */ @@ -789,6 +875,24 @@ void click_label::mousePressEvent(QMouseEvent *e) } } +/** + Wheel event for click_label + */ +void click_label::wheelEvent(QWheelEvent *e) +{ + static int accumulatedDelta = 0; + accumulatedDelta += e->angleDelta().y(); + + if (abs(accumulatedDelta) < 120) { + return; + } + + int steps = accumulatedDelta / 120; + accumulatedDelta = accumulatedDelta - steps * 120; + + emit wheel_scrolled(steps); +} + /** Centers on current unit */ diff --git a/client/hudwidget.h b/client/hudwidget.h index 37f8eea88a..c18de70f09 100644 --- a/client/hudwidget.h +++ b/client/hudwidget.h @@ -1,5 +1,5 @@ /************************************************************************** - Copyright (c) 1996-2020 Freeciv21 and Freeciv contributors. This file is + Copyright (c) 1996-2024 Freeciv21 and Freeciv contributors. This file is part of Freeciv21. Freeciv21 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the @@ -144,11 +144,13 @@ class click_label : public QLabel { click_label(); signals: void left_clicked(); + void wheel_scrolled(const int delta); private slots: void mouse_clicked(); protected: void mousePressEvent(QMouseEvent *e) override; + void wheelEvent(QWheelEvent *e) override; }; /**************************************************************************** @@ -221,6 +223,9 @@ class hud_units : public QFrame { move_widget *mw; unit_list *ul_units; tile *current_tile; + +private slots: + void cycle_units(const int delta); }; /**************************************************************************** diff --git a/docs/Manuals/Game/unit-controls.rst b/docs/Manuals/Game/unit-controls.rst index 3b10531298..bc4ec038d0 100644 --- a/docs/Manuals/Game/unit-controls.rst +++ b/docs/Manuals/Game/unit-controls.rst @@ -42,3 +42,6 @@ appear in quotes after the Unit ID value. If the unit selected is not in your field of vision on the map, then you can click on the icon for the unit on the left side and the game map will center on the unit for you. As with other widgets in Freeciv21, you can click+drag the widget to move it by using the plus symbol in the upper left corner. + +If you scroll your mouse wheel on icon for the unit on the left side, than the game will cycle through idle or +sentried units of the same unit type. From 59f2c0d7aaeb98fff7b17df1466889b14b11bc0d Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Wed, 1 Jan 2025 14:38:37 +0100 Subject: [PATCH 2/7] Factor out unit cycling into a family of functions The new function names share the prefix `cycle_units`. This is an effort to reduce complexity and pet CodeFactor. --- client/hudwidget.cpp | 211 ++++++++++++++++++++++++++++--------------- client/hudwidget.h | 1 + 2 files changed, 140 insertions(+), 72 deletions(-) diff --git a/client/hudwidget.cpp b/client/hudwidget.cpp index ba1ee79539..923bb4d5f7 100644 --- a/client/hudwidget.cpp +++ b/client/hudwidget.cpp @@ -44,6 +44,12 @@ #include "widgets/decorations.h" static QString popup_terrain_info(struct tile *ptile); +static bool cycle_units_predicate(const struct unit *current_unit, + const struct unit *candidate); +static std::tuple +cycle_units_get_count_and_index(const struct unit *current_unit); +static void cycle_units_select_index(const struct unit *current_unit, + int index); /** Returns true if player has any unit of unit_type @@ -782,78 +788,7 @@ void hud_units::update_actions() */ void hud_units::cycle_units(const int direction) { - if (get_num_units_in_focus() != 1) { - return; - } - - struct unit *current_unit = head_of_units_in_focus(); - - // Get the count of relevant units and determine the index of the - // current unit among these units. If the current unit is not among - // the relevant units, then index 0 is assumed by default. - int unit_count = 0; - int current_unit_index = 0; - unit_list_iterate(client_player()->units, punit) - { - if (punit->utype != current_unit->utype) { - continue; - } - - if (ACTIVITY_IDLE != punit->activity - && ACTIVITY_SENTRY != punit->activity) { - continue; - } - - if (!can_unit_do_activity(punit, ACTIVITY_IDLE)) { - continue; - } - - if (current_unit->id == punit->id) { - current_unit_index = unit_count; - } - - unit_count++; - } - unit_list_iterate_end; - - // Determine the index of the unit to be selected next. - int cycle_unit_index = current_unit_index; - if (direction > 0) { - cycle_unit_index++; - } else if (direction < 0) { - cycle_unit_index--; - } - - if (cycle_unit_index < 0) { - cycle_unit_index = unit_count - 1; - } else if (cycle_unit_index >= unit_count) { - cycle_unit_index = 0; - } - - // Select the next unit. - unit_count = 0; - unit_list_iterate(client_player()->units, punit) - { - if (punit->utype != current_unit->utype) { - continue; - } - - if (ACTIVITY_IDLE != punit->activity - && ACTIVITY_SENTRY != punit->activity) { - continue; - } - - if (!can_unit_do_activity(punit, ACTIVITY_IDLE)) { - continue; - } - - if (unit_count == cycle_unit_index) { - unit_focus_set_and_select(punit); - } - - unit_count++; - } - unit_list_iterate_end; + ::cycle_units(direction); } /** @@ -1835,3 +1770,135 @@ void hud_battle_log::showEvent(QShowEvent *event) m_timer.restart(); setVisible(true); } + +/** + This predicate is called by cycle_units and friends to filter + relevant units from the iterated units. + + current_unit is the currently selected unit. + candidate is the candidate checked for relevance. + */ +static bool cycle_units_predicate(const struct unit *current_unit, + const struct unit *candidate) +{ + fc_assert_ret_val(current_unit != nullptr && candidate != nullptr, false); + + if (candidate->utype != current_unit->utype) { + return false; + } + + if (ACTIVITY_IDLE != candidate->activity + && ACTIVITY_SENTRY != candidate->activity) { + return false; + } + + if (!can_unit_do_activity(candidate, ACTIVITY_IDLE)) { + return false; + } + + return true; +} + +/** + Gets the count of relevant units and determines the index of the + current unit among these units. + + If current_unit is not among the relevant units, then index 0 is + assumed by default. + + current_unit is the currently selected unit. + */ +static std::tuple +cycle_units_get_count_and_index(const struct unit *current_unit) +{ + fc_assert_ret_val(current_unit != nullptr, std::make_tuple(0, 0)); + + int unit_count = 0; + int current_unit_index = 0; + + unit_list_iterate(client_player()->units, punit) + { + if (!cycle_units_predicate(current_unit, punit)) { + continue; + } + + if (current_unit->id == punit->id) { + current_unit_index = unit_count; + } + + unit_count++; + } + unit_list_iterate_end; + + return {unit_count, current_unit_index}; +} + +/** + Selects the next unit when units are cycled. + + current_unit is the currently selected unit. + index is the index of the unit to be selected next among the + relevant units. + */ +static void cycle_units_select_index(const struct unit *current_unit, + int index) +{ + fc_assert_ret(current_unit != nullptr); + + // Select the next unit. + int unit_count = 0; + unit_list_iterate(client_player()->units, punit) + { + if (!cycle_units_predicate(current_unit, punit)) { + continue; + } + + if (unit_count == index) { + unit_focus_set_and_select(punit); + } + + unit_count++; + } + unit_list_iterate_end; +} + +/** + Cycle idle or sentried units for the unittype of the currently + selected unit. + + The sign of direction determines in which direction the units will + be cycled. + */ +void cycle_units(const int direction) +{ + if (get_num_units_in_focus() != 1) { + return; + } + + struct unit *current_unit = head_of_units_in_focus(); + + int unit_count = 0; + int current_unit_index = 0; + std::tie(unit_count, current_unit_index) = + cycle_units_get_count_and_index(current_unit); + + if (unit_count == 0) { + return; + } + + // Determine the index of the unit to be selected next. + int cycle_unit_index = current_unit_index; + if (direction > 0) { + cycle_unit_index++; + } else if (direction < 0) { + cycle_unit_index--; + } + + if (cycle_unit_index < 0) { + cycle_unit_index = unit_count - 1; + } else if (cycle_unit_index >= unit_count) { + cycle_unit_index = 0; + } + + cycle_units_select_index(current_unit, cycle_unit_index); +} diff --git a/client/hudwidget.h b/client/hudwidget.h index c18de70f09..b12b708c9a 100644 --- a/client/hudwidget.h +++ b/client/hudwidget.h @@ -43,6 +43,7 @@ struct tile; struct unit; struct unit_list; +void cycle_units(const int direction); void show_new_turn_info(); bool has_player_unit_type(Unit_type_id utype); From 70365a270b451d80c05d083ae64abdb0ae8b2ff2 Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Wed, 1 Jan 2025 21:35:22 +0100 Subject: [PATCH 3/7] Rephrase documentation --- docs/Manuals/Game/unit-controls.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Manuals/Game/unit-controls.rst b/docs/Manuals/Game/unit-controls.rst index bc4ec038d0..20ab54bc52 100644 --- a/docs/Manuals/Game/unit-controls.rst +++ b/docs/Manuals/Game/unit-controls.rst @@ -43,5 +43,5 @@ If the unit selected is not in your field of vision on the map, then you can cli the left side and the game map will center on the unit for you. As with other widgets in Freeciv21, you can click+drag the widget to move it by using the plus symbol in the upper left corner. -If you scroll your mouse wheel on icon for the unit on the left side, than the game will cycle through idle or -sentried units of the same unit type. +If you scroll your mouse wheel over the unit icon on the left side of the widget, the game will cycle through +idle or units of the same unit type. From ff622c4b9615172f795bb36cc21969a312718eda Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Fri, 3 Jan 2025 21:48:45 +0100 Subject: [PATCH 4/7] Use structured binding instead of std::tie As requested by code review. No functional changes. --- client/hudwidget.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/hudwidget.cpp b/client/hudwidget.cpp index 923bb4d5f7..28d8301dde 100644 --- a/client/hudwidget.cpp +++ b/client/hudwidget.cpp @@ -1877,9 +1877,7 @@ void cycle_units(const int direction) struct unit *current_unit = head_of_units_in_focus(); - int unit_count = 0; - int current_unit_index = 0; - std::tie(unit_count, current_unit_index) = + auto [unit_count, current_unit_index] = cycle_units_get_count_and_index(current_unit); if (unit_count == 0) { From f8af8bf65e72b7d807f6b59ca44f6921a6f25596 Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Fri, 3 Jan 2025 21:50:16 +0100 Subject: [PATCH 5/7] Fix error in documentation. As requested by code review. No functional changes. --- docs/Manuals/Game/unit-controls.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Manuals/Game/unit-controls.rst b/docs/Manuals/Game/unit-controls.rst index 20ab54bc52..54f506919c 100644 --- a/docs/Manuals/Game/unit-controls.rst +++ b/docs/Manuals/Game/unit-controls.rst @@ -44,4 +44,4 @@ the left side and the game map will center on the unit for you. As with other wi click+drag the widget to move it by using the plus symbol in the upper left corner. If you scroll your mouse wheel over the unit icon on the left side of the widget, the game will cycle through -idle or units of the same unit type. +idle or sentried units of the same unit type. From 26d8fabb6d0f36bfda4422ab9839c1d025946434 Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Sat, 4 Jan 2025 16:09:21 +0100 Subject: [PATCH 6/7] Remove unused includes Reported by clangd. --- client/control.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/control.cpp b/client/control.cpp index 2b33acacf0..3ae3e63445 100644 --- a/client/control.cpp +++ b/client/control.cpp @@ -37,12 +37,10 @@ // client #include "audio/audio.h" -#include "chatline.h" #include "client_main.h" #include "climap.h" #include "climisc.h" #include "control.h" -#include "editor.h" #include "goto.h" #include "governor.h" #include "options.h" From 34f3cc3e0249d3fe467008cfa10769fd361982c8 Mon Sep 17 00:00:00 2001 From: Tobias Rehbein Date: Sat, 4 Jan 2025 18:07:13 +0100 Subject: [PATCH 7/7] Factor out `unit_is_on_stand_by` and reduce code duplication This check is used in the logic to find the nearest unit in `view_units.cpp` and the code to cycle units in `hudwidget.cpp`. --- client/control.cpp | 22 +++++++++++++++++++++- client/control.h | 1 + client/hudwidget.cpp | 11 +---------- client/views/view_units.cpp | 7 ++----- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/client/control.cpp b/client/control.cpp index 3ae3e63445..a2996a51fd 100644 --- a/client/control.cpp +++ b/client/control.cpp @@ -357,7 +357,7 @@ static void ask_server_for_actions(struct unit *punit) } /** - Return TRUE iff this unit is in focus. + Return TRUE if this unit is in focus. */ bool unit_is_in_focus(const struct unit *punit) { @@ -365,6 +365,26 @@ bool unit_is_in_focus(const struct unit *punit) return std::find(focus.begin(), focus.end(), punit) != focus.end(); } +/** + Returns TRUE if this unit is on stand by. + + A unit is on stand by, if it is idle or sentried and not busy + building anything. +*/ +bool unit_is_on_stand_by(const struct unit *punit) +{ + if (ACTIVITY_IDLE != punit->activity + && ACTIVITY_SENTRY != punit->activity) { + return false; + } + + if (!can_unit_do_activity(punit, ACTIVITY_IDLE)) { + return false; + } + + return true; +} + /** Return TRUE iff a unit on this tile is in focus. */ diff --git a/client/control.h b/client/control.h index 8e61eb9dd0..004d78ac11 100644 --- a/client/control.h +++ b/client/control.h @@ -141,6 +141,7 @@ void wakeup_sentried_units(struct tile *ptile); void clear_unit_orders(struct unit *punit); bool unit_is_in_focus(const struct unit *punit); +bool unit_is_on_stand_by(const struct unit *punit); struct unit *get_focus_unit_on_tile(const struct tile *ptile); struct unit *head_of_units_in_focus(); std::vector &get_units_in_focus(); diff --git a/client/hudwidget.cpp b/client/hudwidget.cpp index 28d8301dde..623b0ff4b5 100644 --- a/client/hudwidget.cpp +++ b/client/hudwidget.cpp @@ -1787,16 +1787,7 @@ static bool cycle_units_predicate(const struct unit *current_unit, return false; } - if (ACTIVITY_IDLE != candidate->activity - && ACTIVITY_SENTRY != candidate->activity) { - return false; - } - - if (!can_unit_do_activity(candidate, ACTIVITY_IDLE)) { - return false; - } - - return true; + return unit_is_on_stand_by(candidate); } /** diff --git a/client/views/view_units.cpp b/client/views/view_units.cpp index c9fa048c76..fa51ecfa7a 100644 --- a/client/views/view_units.cpp +++ b/client/views/view_units.cpp @@ -550,11 +550,8 @@ void units_view::find_nearest() if ((punit = find_nearest_unit(utype, ptile))) { queen()->mapview_wdg->center_on_tile(punit->tile); - if (ACTIVITY_IDLE == punit->activity - || ACTIVITY_SENTRY == punit->activity) { - if (can_unit_do_activity(punit, ACTIVITY_IDLE)) { - unit_focus_set_and_select(punit); - } + if (unit_is_on_stand_by(punit)) { + unit_focus_set_and_select(punit); } }