mirror of https://github.com/OpenTTD/OpenTTD
Compare commits
6 Commits
ed638721f1
...
49c022a8f5
Author | SHA1 | Date |
---|---|---|
|
49c022a8f5 | |
|
a8650c6b06 | |
|
e6b45731c0 | |
|
24f173e7a8 | |
|
ccfb40944b | |
|
5b0e9f82d5 |
|
@ -449,6 +449,7 @@ if(WIN32)
|
||||||
psapi
|
psapi
|
||||||
winhttp
|
winhttp
|
||||||
bcrypt
|
bcrypt
|
||||||
|
xinput
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -454,6 +454,7 @@ add_files(
|
||||||
spritecache.cpp
|
spritecache.cpp
|
||||||
spritecache.h
|
spritecache.h
|
||||||
spritecache_internal.h
|
spritecache_internal.h
|
||||||
|
spritecache_type.h
|
||||||
station.cpp
|
station.cpp
|
||||||
station_base.h
|
station_base.h
|
||||||
station_cmd.cpp
|
station_cmd.cpp
|
||||||
|
|
|
@ -75,6 +75,7 @@ void HandleTextInput(std::string_view str, bool marked = false,
|
||||||
std::optional<size_t> insert_location = std::nullopt, std::optional<size_t> replacement_end = std::nullopt);
|
std::optional<size_t> insert_location = std::nullopt, std::optional<size_t> replacement_end = std::nullopt);
|
||||||
void HandleCtrlChanged();
|
void HandleCtrlChanged();
|
||||||
void HandleMouseEvents();
|
void HandleMouseEvents();
|
||||||
|
void HandleGamepadScrolling(int stick_x, int stick_y, int max_axis_value);
|
||||||
void UpdateWindows();
|
void UpdateWindows();
|
||||||
void ChangeGameSpeed(bool enable_fast_forward);
|
void ChangeGameSpeed(bool enable_fast_forward);
|
||||||
|
|
||||||
|
|
|
@ -1698,6 +1698,25 @@ STR_CONFIG_SETTING_SCROLLWHEEL_ZOOM :Zoom map
|
||||||
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :Scroll map
|
STR_CONFIG_SETTING_SCROLLWHEEL_SCROLL :Scroll map
|
||||||
STR_CONFIG_SETTING_SCROLLWHEEL_OFF :Off
|
STR_CONFIG_SETTING_SCROLLWHEEL_OFF :Off
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION :Gamepad stick for scrolling: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION_HELPTEXT :Select which analog stick to use for map scrolling
|
||||||
|
###length 3
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_DISABLED :Disabled
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_LEFT :Left stick
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_STICK_RIGHT :Right stick
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_DEADZONE :Gamepad deadzone: {STRING2}%
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_DEADZONE_HELPTEXT :Minimum stick movement required before scrolling starts (0-100%)
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY :Gamepad sensitivity: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY_HELPTEXT :Control the sensitivity of gamepad analog stick scrolling
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_X :Invert gamepad X-axis: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_X_HELPTEXT :Invert the horizontal axis movement of the gamepad analog stick
|
||||||
|
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_Y :Invert gamepad Y-axis: {STRING2}
|
||||||
|
STR_CONFIG_SETTING_GAMEPAD_INVERT_Y_HELPTEXT :Invert the vertical axis movement of the gamepad analog stick
|
||||||
|
|
||||||
STR_CONFIG_SETTING_OSK_ACTIVATION :On screen keyboard: {STRING2}
|
STR_CONFIG_SETTING_OSK_ACTIVATION :On screen keyboard: {STRING2}
|
||||||
STR_CONFIG_SETTING_OSK_ACTIVATION_HELPTEXT :Select the method to open the on screen keyboard for entering text into editboxes only using the pointing device. This is meant for small devices without actual keyboard
|
STR_CONFIG_SETTING_OSK_ACTIVATION_HELPTEXT :Select the method to open the on screen keyboard for entering text into editboxes only using the pointing device. This is meant for small devices without actual keyboard
|
||||||
###length 4
|
###length 4
|
||||||
|
|
|
@ -681,6 +681,11 @@ SettingsContainer &GetSettingsTree()
|
||||||
* Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
|
* Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
|
||||||
viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
|
viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
|
||||||
viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
|
viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_stick_selection"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_deadzone"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_sensitivity"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_invert_x"));
|
||||||
|
viewports->Add(new SettingEntry("gui.gamepad_invert_y"));
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
/* We might need to emulate a right mouse button on mac */
|
/* We might need to emulate a right mouse button on mac */
|
||||||
viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
|
viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
|
||||||
|
|
|
@ -140,6 +140,13 @@ enum ScrollWheelScrollingSetting : uint8_t {
|
||||||
SWS_OFF = 2 ///< Scroll wheel has no effect.
|
SWS_OFF = 2 ///< Scroll wheel has no effect.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Settings related to gamepad stick selection. */
|
||||||
|
enum GamepadStickSelection : uint8_t {
|
||||||
|
GSS_DISABLED = 0, ///< Gamepad scrolling disabled.
|
||||||
|
GSS_LEFT_STICK = 1, ///< Use left analog stick for scrolling.
|
||||||
|
GSS_RIGHT_STICK = 2, ///< Use right analog stick for scrolling.
|
||||||
|
};
|
||||||
|
|
||||||
/** Settings related to the GUI and other stuff that is not saved in the savegame. */
|
/** Settings related to the GUI and other stuff that is not saved in the savegame. */
|
||||||
struct GUISettings {
|
struct GUISettings {
|
||||||
bool sg_full_load_any; ///< new full load calculation, any cargo must be full read from pre v93 savegames
|
bool sg_full_load_any; ///< new full load calculation, any cargo must be full read from pre v93 savegames
|
||||||
|
@ -183,6 +190,11 @@ struct GUISettings {
|
||||||
uint8_t right_mouse_btn_emulation; ///< should we emulate right mouse clicking?
|
uint8_t right_mouse_btn_emulation; ///< should we emulate right mouse clicking?
|
||||||
uint8_t scrollwheel_scrolling; ///< scrolling using the scroll wheel?
|
uint8_t scrollwheel_scrolling; ///< scrolling using the scroll wheel?
|
||||||
uint8_t scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
|
uint8_t scrollwheel_multiplier; ///< how much 'wheel' per incoming event from the OS?
|
||||||
|
uint8_t gamepad_deadzone; ///< deadzone for gamepad analog sticks (0-100)
|
||||||
|
uint8_t gamepad_sensitivity; ///< sensitivity multiplier for gamepad scrolling
|
||||||
|
bool gamepad_invert_x; ///< invert X axis for gamepad scrolling?
|
||||||
|
bool gamepad_invert_y; ///< invert Y axis for gamepad scrolling?
|
||||||
|
uint8_t gamepad_stick_selection; ///< which stick to use for scrolling (left/right/disabled)
|
||||||
bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables
|
bool timetable_arrival_departure; ///< show arrivals and departures in vehicle timetables
|
||||||
RightClickClose right_click_wnd_close; ///< close window with right click
|
RightClickClose right_click_wnd_close; ///< close window with right click
|
||||||
bool pause_on_newgame; ///< whether to start new games paused or not
|
bool pause_on_newgame; ///< whether to start new games paused or not
|
||||||
|
|
|
@ -535,7 +535,7 @@ static void *ReadSprite(const SpriteCache *sc, SpriteID id, SpriteType sprite_ty
|
||||||
|
|
||||||
struct GrfSpriteOffset {
|
struct GrfSpriteOffset {
|
||||||
size_t file_pos;
|
size_t file_pos;
|
||||||
uint8_t control_flags;
|
SpriteCacheCtrlFlags control_flags{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Map from sprite numbers to position in the GRF file. */
|
/** Map from sprite numbers to position in the GRF file. */
|
||||||
|
@ -565,7 +565,7 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
|
||||||
size_t old_pos = file.GetPos();
|
size_t old_pos = file.GetPos();
|
||||||
file.SeekTo(data_offset, SEEK_CUR);
|
file.SeekTo(data_offset, SEEK_CUR);
|
||||||
|
|
||||||
GrfSpriteOffset offset = { 0, 0 };
|
GrfSpriteOffset offset{0};
|
||||||
|
|
||||||
/* Loop over all sprite section entries and store the file
|
/* Loop over all sprite section entries and store the file
|
||||||
* offset for each newly encountered ID. */
|
* offset for each newly encountered ID. */
|
||||||
|
@ -574,7 +574,6 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
|
||||||
if (id != prev_id) {
|
if (id != prev_id) {
|
||||||
_grf_sprite_offsets[prev_id] = offset;
|
_grf_sprite_offsets[prev_id] = offset;
|
||||||
offset.file_pos = file.GetPos() - 4;
|
offset.file_pos = file.GetPos() - 4;
|
||||||
offset.control_flags = 0;
|
|
||||||
}
|
}
|
||||||
prev_id = id;
|
prev_id = id;
|
||||||
uint length = file.ReadDword();
|
uint length = file.ReadDword();
|
||||||
|
@ -585,11 +584,11 @@ void ReadGRFSpriteOffsets(SpriteFile &file)
|
||||||
uint8_t zoom = file.ReadByte();
|
uint8_t zoom = file.ReadByte();
|
||||||
length--;
|
length--;
|
||||||
if (colour.Any() && zoom == 0) { // ZoomLevel::Normal (normal zoom)
|
if (colour.Any() && zoom == 0) { // ZoomLevel::Normal (normal zoom)
|
||||||
SetBit(offset.control_flags, (colour != SpriteComponent::Palette) ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL);
|
offset.control_flags.Set((colour != SpriteComponent::Palette) ? SpriteCacheCtrlFlag::AllowZoomMin1x32bpp : SpriteCacheCtrlFlag::AllowZoomMin1xPal);
|
||||||
SetBit(offset.control_flags, (colour != SpriteComponent::Palette) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL);
|
offset.control_flags.Set((colour != SpriteComponent::Palette) ? SpriteCacheCtrlFlag::AllowZoomMin2x32bpp : SpriteCacheCtrlFlag::AllowZoomMin2xPal);
|
||||||
}
|
}
|
||||||
if (colour.Any() && zoom == 2) { // ZoomLevel::In2x (2x zoomed in)
|
if (colour.Any() && zoom == 2) { // ZoomLevel::In2x (2x zoomed in)
|
||||||
SetBit(offset.control_flags, (colour != SpriteComponent::Palette) ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL);
|
offset.control_flags.Set((colour != SpriteComponent::Palette) ? SpriteCacheCtrlFlag::AllowZoomMin2x32bpp : SpriteCacheCtrlFlag::AllowZoomMin2xPal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,7 +620,7 @@ bool LoadNextSprite(SpriteID load_index, SpriteFile &file, uint file_sprite_id)
|
||||||
uint8_t grf_type = file.ReadByte();
|
uint8_t grf_type = file.ReadByte();
|
||||||
|
|
||||||
SpriteType type;
|
SpriteType type;
|
||||||
uint8_t control_flags = 0;
|
SpriteCacheCtrlFlags control_flags;
|
||||||
if (grf_type == 0xFF) {
|
if (grf_type == 0xFF) {
|
||||||
/* Some NewGRF files have "empty" pseudo-sprites which are 1
|
/* Some NewGRF files have "empty" pseudo-sprites which are 1
|
||||||
* byte long. Catch these so the sprites won't be displayed. */
|
* byte long. Catch these so the sprites won't be displayed. */
|
||||||
|
|
|
@ -11,24 +11,9 @@
|
||||||
#define SPRITECACHE_H
|
#define SPRITECACHE_H
|
||||||
|
|
||||||
#include "gfx_type.h"
|
#include "gfx_type.h"
|
||||||
|
#include "spritecache_type.h"
|
||||||
#include "spriteloader/spriteloader.hpp"
|
#include "spriteloader/spriteloader.hpp"
|
||||||
|
|
||||||
/** Data structure describing a sprite. */
|
|
||||||
struct Sprite {
|
|
||||||
uint16_t height; ///< Height of the sprite.
|
|
||||||
uint16_t width; ///< Width of the sprite.
|
|
||||||
int16_t x_offs; ///< Number of pixels to shift the sprite to the right.
|
|
||||||
int16_t y_offs; ///< Number of pixels to shift the sprite downwards.
|
|
||||||
std::byte data[]; ///< Sprite data.
|
|
||||||
};
|
|
||||||
|
|
||||||
enum SpriteCacheCtrlFlags : uint8_t {
|
|
||||||
SCCF_ALLOW_ZOOM_MIN_1X_PAL = 0, ///< Allow use of sprite min zoom setting at 1x in palette mode.
|
|
||||||
SCCF_ALLOW_ZOOM_MIN_1X_32BPP = 1, ///< Allow use of sprite min zoom setting at 1x in 32bpp mode.
|
|
||||||
SCCF_ALLOW_ZOOM_MIN_2X_PAL = 2, ///< Allow use of sprite min zoom setting at 2x in palette mode.
|
|
||||||
SCCF_ALLOW_ZOOM_MIN_2X_32BPP = 3, ///< Allow use of sprite min zoom setting at 2x in 32bpp mode.
|
|
||||||
};
|
|
||||||
|
|
||||||
extern uint _sprite_cache_size;
|
extern uint _sprite_cache_size;
|
||||||
|
|
||||||
/** SpriteAllocator that allocates memory via a unique_ptr array. */
|
/** SpriteAllocator that allocates memory via a unique_ptr array. */
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "core/math_func.hpp"
|
#include "core/math_func.hpp"
|
||||||
#include "gfx_type.h"
|
#include "gfx_type.h"
|
||||||
|
#include "spritecache_type.h"
|
||||||
#include "spriteloader/spriteloader.hpp"
|
#include "spriteloader/spriteloader.hpp"
|
||||||
|
|
||||||
#include "table/sprites.h"
|
#include "table/sprites.h"
|
||||||
|
@ -27,7 +28,7 @@ struct SpriteCache {
|
||||||
uint32_t lru = 0;
|
uint32_t lru = 0;
|
||||||
SpriteType type = SpriteType::Invalid; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble.
|
SpriteType type = SpriteType::Invalid; ///< In some cases a single sprite is misused by two NewGRFs. Once as real sprite and once as recolour sprite. If the recolour sprite gets into the cache it might be drawn as real sprite which causes enormous trouble.
|
||||||
bool warned = false; ///< True iff the user has been warned about incorrect use of this sprite
|
bool warned = false; ///< True iff the user has been warned about incorrect use of this sprite
|
||||||
uint8_t control_flags = 0; ///< Control flags, see SpriteCacheCtrlFlags
|
SpriteCacheCtrlFlags control_flags{}; ///< Control flags, see SpriteCacheCtrlFlags
|
||||||
|
|
||||||
void ClearSpriteData();
|
void ClearSpriteData();
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* This file is part of OpenTTD.
|
||||||
|
* OpenTTD 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, version 2.
|
||||||
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file spritecache_type.h Types related to the sprite cache. */
|
||||||
|
|
||||||
|
#ifndef SPRITECACHE_TYPE_H
|
||||||
|
#define SPRITECACHE_TYPE_H
|
||||||
|
|
||||||
|
#include "core/enum_type.hpp"
|
||||||
|
|
||||||
|
/** Data structure describing a sprite. */
|
||||||
|
struct Sprite {
|
||||||
|
uint16_t height; ///< Height of the sprite.
|
||||||
|
uint16_t width; ///< Width of the sprite.
|
||||||
|
int16_t x_offs; ///< Number of pixels to shift the sprite to the right.
|
||||||
|
int16_t y_offs; ///< Number of pixels to shift the sprite downwards.
|
||||||
|
std::byte data[]; ///< Sprite data.
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SpriteCacheCtrlFlag : uint8_t {
|
||||||
|
AllowZoomMin1xPal, ///< Allow use of sprite min zoom setting at 1x in palette mode.
|
||||||
|
AllowZoomMin1x32bpp, ///< Allow use of sprite min zoom setting at 1x in 32bpp mode.
|
||||||
|
AllowZoomMin2xPal, ///< Allow use of sprite min zoom setting at 2x in palette mode.
|
||||||
|
AllowZoomMin2x32bpp, ///< Allow use of sprite min zoom setting at 2x in 32bpp mode.
|
||||||
|
};
|
||||||
|
|
||||||
|
using SpriteCacheCtrlFlags = EnumBitSet<SpriteCacheCtrlFlag, uint8_t>;
|
||||||
|
|
||||||
|
#endif /* SPRITECACHE_TYPE_H */
|
|
@ -256,7 +256,7 @@ static ZoomLevels LoadSpriteV1(SpriteLoader::SpriteCollection &sprite, SpriteFil
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
|
static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
|
||||||
{
|
{
|
||||||
static const ZoomLevel zoom_lvl_map[6] = {ZoomLevel::Normal, ZoomLevel::In4x, ZoomLevel::In2x, ZoomLevel::Out2x, ZoomLevel::Out4x, ZoomLevel::Out8x};
|
static const ZoomLevel zoom_lvl_map[6] = {ZoomLevel::Normal, ZoomLevel::In4x, ZoomLevel::In2x, ZoomLevel::Out2x, ZoomLevel::Out4x, ZoomLevel::Out8x};
|
||||||
|
|
||||||
|
@ -295,11 +295,11 @@ static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFil
|
||||||
is_wanted_zoom_lvl = true;
|
is_wanted_zoom_lvl = true;
|
||||||
ZoomLevel zoom_min = sprite_type == SpriteType::Font ? ZoomLevel::Min : _settings_client.gui.sprite_zoom_min;
|
ZoomLevel zoom_min = sprite_type == SpriteType::Font ? ZoomLevel::Min : _settings_client.gui.sprite_zoom_min;
|
||||||
if (zoom_min >= ZoomLevel::In2x &&
|
if (zoom_min >= ZoomLevel::In2x &&
|
||||||
HasBit(control_flags, load_32bpp ? SCCF_ALLOW_ZOOM_MIN_2X_32BPP : SCCF_ALLOW_ZOOM_MIN_2X_PAL) && zoom_lvl < ZoomLevel::In2x) {
|
control_flags.Test(load_32bpp ? SpriteCacheCtrlFlag::AllowZoomMin2x32bpp : SpriteCacheCtrlFlag::AllowZoomMin2xPal) && zoom_lvl < ZoomLevel::In2x) {
|
||||||
is_wanted_zoom_lvl = false;
|
is_wanted_zoom_lvl = false;
|
||||||
}
|
}
|
||||||
if (zoom_min >= ZoomLevel::Normal &&
|
if (zoom_min >= ZoomLevel::Normal &&
|
||||||
HasBit(control_flags, load_32bpp ? SCCF_ALLOW_ZOOM_MIN_1X_32BPP : SCCF_ALLOW_ZOOM_MIN_1X_PAL) && zoom_lvl < ZoomLevel::Normal) {
|
control_flags.Test(load_32bpp ? SpriteCacheCtrlFlag::AllowZoomMin1x32bpp : SpriteCacheCtrlFlag::AllowZoomMin1xPal) && zoom_lvl < ZoomLevel::Normal) {
|
||||||
is_wanted_zoom_lvl = false;
|
is_wanted_zoom_lvl = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -359,7 +359,7 @@ static ZoomLevels LoadSpriteV2(SpriteLoader::SpriteCollection &sprite, SpriteFil
|
||||||
return loaded_sprites;
|
return loaded_sprites;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZoomLevels SpriteLoaderGrf::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
|
ZoomLevels SpriteLoaderGrf::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
|
||||||
{
|
{
|
||||||
if (this->container_ver >= 2) {
|
if (this->container_ver >= 2) {
|
||||||
return LoadSpriteV2(sprite, file, file_pos, sprite_type, load_32bpp, control_flags, avail_8bpp, avail_32bpp);
|
return LoadSpriteV2(sprite, file, file_pos, sprite_type, load_32bpp, control_flags, avail_8bpp, avail_32bpp);
|
||||||
|
|
|
@ -17,7 +17,7 @@ class SpriteLoaderGrf : public SpriteLoader {
|
||||||
uint8_t container_ver;
|
uint8_t container_ver;
|
||||||
public:
|
public:
|
||||||
SpriteLoaderGrf(uint8_t container_ver) : container_ver(container_ver) {}
|
SpriteLoaderGrf(uint8_t container_ver) : container_ver(container_ver) {}
|
||||||
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
|
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SPRITELOADER_GRF_HPP */
|
#endif /* SPRITELOADER_GRF_HPP */
|
||||||
|
|
|
@ -48,7 +48,7 @@ static void Convert32bppTo8bpp(SpriteLoader::Sprite &sprite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZoomLevels SpriteLoaderMakeIndexed::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
|
ZoomLevels SpriteLoaderMakeIndexed::LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp)
|
||||||
{
|
{
|
||||||
ZoomLevels avail = this->baseloader.LoadSprite(sprite, file, file_pos, sprite_type, true, control_flags, avail_8bpp, avail_32bpp);
|
ZoomLevels avail = this->baseloader.LoadSprite(sprite, file, file_pos, sprite_type, true, control_flags, avail_8bpp, avail_32bpp);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ class SpriteLoaderMakeIndexed : public SpriteLoader {
|
||||||
SpriteLoader &baseloader;
|
SpriteLoader &baseloader;
|
||||||
public:
|
public:
|
||||||
SpriteLoaderMakeIndexed(SpriteLoader &baseloader) : baseloader(baseloader) {}
|
SpriteLoaderMakeIndexed(SpriteLoader &baseloader) : baseloader(baseloader) {}
|
||||||
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
|
ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SPRITELOADER_MAKEINDEXED_H */
|
#endif /* SPRITELOADER_MAKEINDEXED_H */
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "../core/alloc_type.hpp"
|
#include "../core/alloc_type.hpp"
|
||||||
#include "../core/enum_type.hpp"
|
#include "../core/enum_type.hpp"
|
||||||
#include "../gfx_type.h"
|
#include "../gfx_type.h"
|
||||||
|
#include "../spritecache_type.h"
|
||||||
#include "sprite_file_type.hpp"
|
#include "sprite_file_type.hpp"
|
||||||
|
|
||||||
struct Sprite;
|
struct Sprite;
|
||||||
|
@ -94,7 +95,7 @@ public:
|
||||||
* @param[out] avail_32bpp Available 32bpp sprites.
|
* @param[out] avail_32bpp Available 32bpp sprites.
|
||||||
* @return Available sprites matching \a load_32bpp.
|
* @return Available sprites matching \a load_32bpp.
|
||||||
*/
|
*/
|
||||||
virtual ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, uint8_t control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) = 0;
|
virtual ZoomLevels LoadSprite(SpriteLoader::SpriteCollection &sprite, SpriteFile &file, size_t file_pos, SpriteType sprite_type, bool load_32bpp, SpriteCacheCtrlFlags control_flags, ZoomLevels &avail_8bpp, ZoomLevels &avail_32bpp) = 0;
|
||||||
|
|
||||||
virtual ~SpriteLoader() = default;
|
virtual ~SpriteLoader() = default;
|
||||||
};
|
};
|
||||||
|
|
|
@ -912,3 +912,62 @@ post_cb = [](auto) { SetupWidgetDimensions(); ReInitAllWindows(true); }
|
||||||
cat = SC_BASIC
|
cat = SC_BASIC
|
||||||
startup = true
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_VAR]
|
||||||
|
var = gui.gamepad_deadzone
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = 10
|
||||||
|
min = 0
|
||||||
|
max = 100
|
||||||
|
interval = 5
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_DEADZONE
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_DEADZONE_HELPTEXT
|
||||||
|
strval = STR_JUST_COMMA
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_VAR]
|
||||||
|
var = gui.gamepad_sensitivity
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = 10
|
||||||
|
min = 1
|
||||||
|
max = 100
|
||||||
|
interval = 5
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_SENSITIVITY_HELPTEXT
|
||||||
|
strval = STR_JUST_COMMA
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_BOOL]
|
||||||
|
var = gui.gamepad_invert_x
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = false
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_INVERT_X
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_INVERT_X_HELPTEXT
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_BOOL]
|
||||||
|
var = gui.gamepad_invert_y
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync
|
||||||
|
def = false
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_INVERT_Y
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_INVERT_Y_HELPTEXT
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
[SDTC_VAR]
|
||||||
|
var = gui.gamepad_stick_selection
|
||||||
|
type = SLE_UINT8
|
||||||
|
flags = SettingFlag::NotInSave, SettingFlag::NoNetworkSync, SettingFlag::GuiDropdown
|
||||||
|
def = GSS_LEFT_STICK
|
||||||
|
min = GSS_DISABLED
|
||||||
|
max = GSS_RIGHT_STICK
|
||||||
|
str = STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION
|
||||||
|
strhelp = STR_CONFIG_SETTING_GAMEPAD_STICK_SELECTION_HELPTEXT
|
||||||
|
strval = STR_CONFIG_SETTING_GAMEPAD_STICK_DISABLED
|
||||||
|
cat = SC_BASIC
|
||||||
|
startup = true
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ static bool MockLoadNextSprite(SpriteID load_index)
|
||||||
sc->id = 0;
|
sc->id = 0;
|
||||||
sc->type = is_mapgen ? SpriteType::MapGen : SpriteType::Normal;
|
sc->type = is_mapgen ? SpriteType::MapGen : SpriteType::Normal;
|
||||||
sc->warned = false;
|
sc->warned = false;
|
||||||
sc->control_flags = 0;
|
sc->control_flags = {};
|
||||||
|
|
||||||
/* Fill with empty sprites up until the default sprite count. */
|
/* Fill with empty sprites up until the default sprite count. */
|
||||||
return load_index < SPR_OPENTTD_BASE + OPENTTD_SPRITE_COUNT;
|
return load_index < SPR_OPENTTD_BASE + OPENTTD_SPRITE_COUNT;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "../fileio_func.h"
|
#include "../fileio_func.h"
|
||||||
#include "../framerate_type.h"
|
#include "../framerate_type.h"
|
||||||
#include "../window_func.h"
|
#include "../window_func.h"
|
||||||
|
#include "../zoom_func.h"
|
||||||
#include "sdl2_v.h"
|
#include "sdl2_v.h"
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
|
@ -524,6 +525,27 @@ bool VideoDriver_SDL_Base::PollEvent()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEADDED: {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad device added, index: {}", ev.cdevice.which);
|
||||||
|
/* Try to open the newly connected gamepad */
|
||||||
|
if (this->gamepad == nullptr) {
|
||||||
|
this->OpenGamepad();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED: {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad device removed, instance ID: {}", ev.cdevice.which);
|
||||||
|
/* Close gamepad if it was removed */
|
||||||
|
if (this->gamepad != nullptr && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(this->gamepad))) {
|
||||||
|
Debug(driver, 1, "SDL2: Current gamepad was removed, closing and reopening");
|
||||||
|
this->CloseGamepad();
|
||||||
|
/* Try to open another gamepad if available */
|
||||||
|
this->OpenGamepad();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -539,6 +561,12 @@ static std::optional<std::string_view> InitializeSDL()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) return SDL_GetError();
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) return SDL_GetError();
|
||||||
|
|
||||||
|
/* Initialize gamepad subsystem for gamepad scrolling support */
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
|
Debug(driver, 1, "SDL2: Failed to initialize gamepad subsystem: {}", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,6 +618,11 @@ std::optional<std::string_view> VideoDriver_SDL_Base::Start(const StringList &pa
|
||||||
SDL_StopTextInput();
|
SDL_StopTextInput();
|
||||||
this->edit_box_focused = false;
|
this->edit_box_focused = false;
|
||||||
|
|
||||||
|
/* Initialize gamepad for scrolling */
|
||||||
|
Debug(driver, 1, "SDL2: Attempting to initialize gamepad support");
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection);
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
this->is_game_threaded = false;
|
this->is_game_threaded = false;
|
||||||
#else
|
#else
|
||||||
|
@ -601,6 +634,8 @@ std::optional<std::string_view> VideoDriver_SDL_Base::Start(const StringList &pa
|
||||||
|
|
||||||
void VideoDriver_SDL_Base::Stop()
|
void VideoDriver_SDL_Base::Stop()
|
||||||
{
|
{
|
||||||
|
this->CloseGamepad();
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
|
if (SDL_WasInit(SDL_INIT_EVERYTHING) == 0) {
|
||||||
SDL_Quit(); // If there's nothing left, quit SDL
|
SDL_Quit(); // If there's nothing left, quit SDL
|
||||||
|
@ -629,6 +664,9 @@ void VideoDriver_SDL_Base::InputLoop()
|
||||||
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
|
(keys[SDL_SCANCODE_DOWN] ? 8 : 0);
|
||||||
|
|
||||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||||
|
|
||||||
|
/* Process gamepad input for scrolling */
|
||||||
|
this->ProcessGamepadInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoDriver_SDL_Base::LoopOnce()
|
void VideoDriver_SDL_Base::LoopOnce()
|
||||||
|
@ -755,3 +793,84 @@ void VideoDriver_SDL_Base::UnlockVideoBuffer()
|
||||||
|
|
||||||
this->buffer_locked = false;
|
this->buffer_locked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoDriver_SDL_Base::OpenGamepad()
|
||||||
|
{
|
||||||
|
/* Don't open gamepad if already open or if gamepad scrolling is disabled */
|
||||||
|
if (this->gamepad != nullptr) {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad already open, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad scrolling disabled, not opening gamepad");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if any gamepads are available */
|
||||||
|
int num_gamepads = SDL_NumJoysticks();
|
||||||
|
Debug(driver, 1, "SDL2: Found {} joystick(s)", num_gamepads);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_gamepads; i++) {
|
||||||
|
if (SDL_IsGameController(i)) {
|
||||||
|
Debug(driver, 1, "SDL2: Joystick {} is a gamepad, attempting to open", i);
|
||||||
|
this->gamepad = SDL_GameControllerOpen(i);
|
||||||
|
if (this->gamepad != nullptr) {
|
||||||
|
Debug(driver, 1, "SDL2: Opened gamepad: {}", SDL_GameControllerName(this->gamepad));
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
Debug(driver, 1, "SDL2: Failed to open gamepad {}: {}", i, SDL_GetError());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(driver, 1, "SDL2: Joystick {} is not a gamepad", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->gamepad == nullptr) {
|
||||||
|
Debug(driver, 1, "SDL2: No gamepad opened");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_SDL_Base::CloseGamepad()
|
||||||
|
{
|
||||||
|
if (this->gamepad != nullptr) {
|
||||||
|
SDL_GameControllerClose(this->gamepad);
|
||||||
|
this->gamepad = nullptr;
|
||||||
|
Debug(driver, 1, "SDL2: Closed gamepad");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_SDL_Base::ProcessGamepadInput()
|
||||||
|
{
|
||||||
|
/* Skip if gamepad is not available */
|
||||||
|
if (this->gamepad == nullptr) {
|
||||||
|
static bool logged_no_gamepad = false;
|
||||||
|
if (!logged_no_gamepad) {
|
||||||
|
Debug(driver, 2, "SDL2: No gamepad available for input processing");
|
||||||
|
logged_no_gamepad = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if gamepad is still connected */
|
||||||
|
if (!SDL_GameControllerGetAttached(this->gamepad)) {
|
||||||
|
Debug(driver, 1, "SDL2: Gamepad disconnected, closing and reopening");
|
||||||
|
this->CloseGamepad();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get analog stick values based on stick selection */
|
||||||
|
Sint16 stick_x = 0, stick_y = 0;
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_LEFT_STICK) {
|
||||||
|
stick_x = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
|
stick_y = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
|
Debug(driver, 3, "SDL2: Left stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
} else if (_settings_client.gui.gamepad_stick_selection == GSS_RIGHT_STICK) {
|
||||||
|
stick_x = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||||
|
stick_y = SDL_GameControllerGetAxis(this->gamepad, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||||
|
Debug(driver, 3, "SDL2: Right stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use the common gamepad handling function */
|
||||||
|
HandleGamepadScrolling(stick_x, stick_y, 32767);
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
|
|
||||||
#include "video_driver.hpp"
|
#include "video_driver.hpp"
|
||||||
|
|
||||||
|
/* Forward declaration of SDL_GameController */
|
||||||
|
struct _SDL_GameController;
|
||||||
|
typedef struct _SDL_GameController SDL_GameController;
|
||||||
|
|
||||||
/** The SDL video driver. */
|
/** The SDL video driver. */
|
||||||
class VideoDriver_SDL_Base : public VideoDriver {
|
class VideoDriver_SDL_Base : public VideoDriver {
|
||||||
public:
|
public:
|
||||||
|
@ -69,6 +73,13 @@ protected:
|
||||||
/** Create the main window. */
|
/** Create the main window. */
|
||||||
virtual bool CreateMainWindow(uint w, uint h, uint flags = 0);
|
virtual bool CreateMainWindow(uint w, uint h, uint flags = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Gamepad support for map scrolling */
|
||||||
|
SDL_GameController *gamepad = nullptr; ///< Currently opened gamepad.
|
||||||
|
void OpenGamepad();
|
||||||
|
void CloseGamepad();
|
||||||
|
void ProcessGamepadInput();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void LoopOnce();
|
void LoopOnce();
|
||||||
void MainLoopCleanup();
|
void MainLoopCleanup();
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <imm.h>
|
#include <imm.h>
|
||||||
#include <versionhelpers.h>
|
#include <versionhelpers.h>
|
||||||
|
#include <xinput.h>
|
||||||
#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
|
#if defined(_MSC_VER) && defined(NTDDI_WIN10_RS4)
|
||||||
#include <winrt/Windows.UI.ViewManagement.h>
|
#include <winrt/Windows.UI.ViewManagement.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -984,6 +985,9 @@ void VideoDriver_Win32Base::InputLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
|
||||||
|
|
||||||
|
/* Process gamepad input for scrolling */
|
||||||
|
this->ProcessGamepadInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoDriver_Win32Base::PollEvent()
|
bool VideoDriver_Win32Base::PollEvent()
|
||||||
|
@ -1143,6 +1147,106 @@ void VideoDriver_Win32Base::UnlockVideoBuffer()
|
||||||
this->buffer_locked = false;
|
this->buffer_locked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VideoDriver_Win32Base::OpenGamepad()
|
||||||
|
{
|
||||||
|
/* Don't open gamepad if already open or if gamepad scrolling is disabled */
|
||||||
|
if (this->gamepad_user_index != XUSER_MAX_COUNT) {
|
||||||
|
Debug(driver, 1, "Win32: Gamepad already open, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
Debug(driver, 1, "Win32: Gamepad scrolling disabled, not opening gamepad");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for any connected gamepads */
|
||||||
|
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
|
||||||
|
XINPUT_STATE state = {};
|
||||||
|
|
||||||
|
if (XInputGetState(i, &state) == ERROR_SUCCESS) {
|
||||||
|
this->gamepad_user_index = i;
|
||||||
|
Debug(driver, 1, "Win32: Opened gamepad at index {}", i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_Win32Base::CloseGamepad()
|
||||||
|
{
|
||||||
|
if (this->gamepad_user_index != XUSER_MAX_COUNT) {
|
||||||
|
this->gamepad_user_index = XUSER_MAX_COUNT;
|
||||||
|
Debug(driver, 1, "Win32: Closed gamepad");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDriver_Win32Base::ProcessGamepadInput()
|
||||||
|
{
|
||||||
|
/* Skip if gamepad scrolling is disabled */
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If no gamepad is currently open, try to reconnect periodically */
|
||||||
|
if (this->gamepad_user_index == XUSER_MAX_COUNT) {
|
||||||
|
static bool logged_no_gamepad = false;
|
||||||
|
|
||||||
|
/* Only try to reconnect every 60 frames (~1 second at 60 FPS) to avoid spam */
|
||||||
|
if (this->gamepad_reconnect_timer > 0) {
|
||||||
|
this->gamepad_reconnect_timer--;
|
||||||
|
if (!logged_no_gamepad) {
|
||||||
|
Debug(driver, 2, "Win32: No gamepad available for input processing");
|
||||||
|
logged_no_gamepad = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to open gamepad */
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
|
/* If still no gamepad, set timer for next retry */
|
||||||
|
if (this->gamepad_user_index == XUSER_MAX_COUNT) {
|
||||||
|
this->gamepad_reconnect_timer = 60; /* Retry in ~1 second */
|
||||||
|
if (!logged_no_gamepad) {
|
||||||
|
Debug(driver, 2, "Win32: No gamepad available for input processing");
|
||||||
|
logged_no_gamepad = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
/* Successfully reconnected */
|
||||||
|
logged_no_gamepad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get gamepad state */
|
||||||
|
XINPUT_STATE state = {};
|
||||||
|
|
||||||
|
if (XInputGetState(this->gamepad_user_index, &state) != ERROR_SUCCESS) {
|
||||||
|
Debug(driver, 1, "Win32: Gamepad disconnected, closing and will retry connection");
|
||||||
|
this->CloseGamepad();
|
||||||
|
this->gamepad_reconnect_timer = 60; /* Start retry timer */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get analog stick values based on stick selection
|
||||||
|
* Note: XInput uses SHORT values for stick positions, but we have to extend to INT
|
||||||
|
* to avoid overflow when inverting the Y-axis value */
|
||||||
|
INT stick_x = 0, stick_y = 0;
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_LEFT_STICK) {
|
||||||
|
stick_x = state.Gamepad.sThumbLX;
|
||||||
|
stick_y = state.Gamepad.sThumbLY;
|
||||||
|
Debug(driver, 3, "Win32: Left stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
} else if (_settings_client.gui.gamepad_stick_selection == GSS_RIGHT_STICK) {
|
||||||
|
stick_x = state.Gamepad.sThumbRX;
|
||||||
|
stick_y = state.Gamepad.sThumbRY;
|
||||||
|
Debug(driver, 3, "Win32: Right stick raw values: x={}, y={}", stick_x, stick_y);
|
||||||
|
}
|
||||||
|
stick_y = -stick_y; // Xinput Y-axis is inverted from other libraries
|
||||||
|
|
||||||
|
/* Use the common gamepad handling function */
|
||||||
|
HandleGamepadScrolling(stick_x, stick_y, 32767);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI;
|
static FVideoDriver_Win32GDI iFVideoDriver_Win32GDI;
|
||||||
|
|
||||||
|
@ -1158,6 +1262,11 @@ std::optional<std::string_view> VideoDriver_Win32GDI::Start(const StringList &pa
|
||||||
|
|
||||||
MarkWholeScreenDirty();
|
MarkWholeScreenDirty();
|
||||||
|
|
||||||
|
/* Initialize gamepad for scrolling */
|
||||||
|
Debug(driver, 1, "Win32: Attempting to initialize gamepad support");
|
||||||
|
Debug(driver, 1, "Win32: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection);
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -1165,6 +1274,7 @@ std::optional<std::string_view> VideoDriver_Win32GDI::Start(const StringList &pa
|
||||||
|
|
||||||
void VideoDriver_Win32GDI::Stop()
|
void VideoDriver_Win32GDI::Stop()
|
||||||
{
|
{
|
||||||
|
this->CloseGamepad();
|
||||||
DeleteObject(this->gdi_palette);
|
DeleteObject(this->gdi_palette);
|
||||||
DeleteObject(this->dib_sect);
|
DeleteObject(this->dib_sect);
|
||||||
|
|
||||||
|
@ -1472,6 +1582,11 @@ std::optional<std::string_view> VideoDriver_Win32OpenGL::Start(const StringList
|
||||||
|
|
||||||
MarkWholeScreenDirty();
|
MarkWholeScreenDirty();
|
||||||
|
|
||||||
|
/* Initialize gamepad for scrolling */
|
||||||
|
Debug(driver, 1, "Win32: Attempting to initialize gamepad support");
|
||||||
|
Debug(driver, 1, "Win32: Gamepad stick selection setting: {}", _settings_client.gui.gamepad_stick_selection);
|
||||||
|
this->OpenGamepad();
|
||||||
|
|
||||||
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
this->is_game_threaded = !GetDriverParamBool(param, "no_threads") && !GetDriverParamBool(param, "no_thread");
|
||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -1479,6 +1594,7 @@ std::optional<std::string_view> VideoDriver_Win32OpenGL::Start(const StringList
|
||||||
|
|
||||||
void VideoDriver_Win32OpenGL::Stop()
|
void VideoDriver_Win32OpenGL::Stop()
|
||||||
{
|
{
|
||||||
|
this->CloseGamepad();
|
||||||
this->DestroyContext();
|
this->DestroyContext();
|
||||||
this->VideoDriver_Win32Base::Stop();
|
this->VideoDriver_Win32Base::Stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <xinput.h>
|
||||||
|
|
||||||
/** Base class for Windows video drivers. */
|
/** Base class for Windows video drivers. */
|
||||||
class VideoDriver_Win32Base : public VideoDriver {
|
class VideoDriver_Win32Base : public VideoDriver {
|
||||||
|
@ -48,6 +49,13 @@ protected:
|
||||||
|
|
||||||
bool buffer_locked; ///< Video buffer was locked by the main thread.
|
bool buffer_locked; ///< Video buffer was locked by the main thread.
|
||||||
|
|
||||||
|
/** Gamepad support for map scrolling */
|
||||||
|
DWORD gamepad_user_index = XUSER_MAX_COUNT; ///< Index of currently opened gamepad (XUSER_MAX_COUNT = no gamepad).
|
||||||
|
uint32_t gamepad_reconnect_timer = 0; ///< Timer for retrying gamepad connection after disconnect.
|
||||||
|
void OpenGamepad();
|
||||||
|
void CloseGamepad();
|
||||||
|
void ProcessGamepadInput();
|
||||||
|
|
||||||
Dimension GetScreenSize() const override;
|
Dimension GetScreenSize() const override;
|
||||||
float GetDPIScale() override;
|
float GetDPIScale() override;
|
||||||
void InputLoop() override;
|
void InputLoop() override;
|
||||||
|
|
|
@ -3572,3 +3572,81 @@ void PickerWindowBase::Close([[maybe_unused]] int data)
|
||||||
ResetObjectToPlace();
|
ResetObjectToPlace();
|
||||||
this->Window::Close();
|
this->Window::Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process gamepad analog stick input for viewport scrolling.
|
||||||
|
* This is a common function that can be used by any video driver that supports gamepads.
|
||||||
|
* @param stick_x Raw analog stick X value (typically -32768 to 32767 range)
|
||||||
|
* @param stick_y Raw analog stick Y value (typically -32768 to 32767 range)
|
||||||
|
* @param max_axis_value Maximum value for the analog stick axes (e.g., 32767 for SDL2)
|
||||||
|
*/
|
||||||
|
void HandleGamepadScrolling(int stick_x, int stick_y, int max_axis_value)
|
||||||
|
{
|
||||||
|
/* Skip if gamepad stick selection is disabled */
|
||||||
|
if (_settings_client.gui.gamepad_stick_selection == GSS_DISABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply deadzone (convert percentage to axis range) */
|
||||||
|
const int deadzone = (_settings_client.gui.gamepad_deadzone * max_axis_value) / 100;
|
||||||
|
|
||||||
|
if (abs(stick_x) < deadzone) stick_x = 0;
|
||||||
|
if (abs(stick_y) < deadzone) stick_y = 0;
|
||||||
|
|
||||||
|
/* Skip if no movement after deadzone */
|
||||||
|
if (stick_x == 0 && stick_y == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate scroll delta with sensitivity */
|
||||||
|
float sensitivity = _settings_client.gui.gamepad_sensitivity / 10.0f;
|
||||||
|
int delta_x = (int)(stick_x * sensitivity / (max_axis_value / 16)); // Scale down from axis range
|
||||||
|
int delta_y = (int)(stick_y * sensitivity / (max_axis_value / 16));
|
||||||
|
|
||||||
|
/* Apply axis inversion */
|
||||||
|
if (_settings_client.gui.gamepad_invert_x) delta_x = -delta_x;
|
||||||
|
if (_settings_client.gui.gamepad_invert_y) delta_y = -delta_y;
|
||||||
|
|
||||||
|
/* Skip if deltas are too small */
|
||||||
|
if (abs(delta_x) < 1 && abs(delta_y) < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply scrolling based on cursor position */
|
||||||
|
if (_game_mode != GM_MENU && _game_mode != GM_BOOTSTRAP) {
|
||||||
|
Window *target_window = nullptr;
|
||||||
|
|
||||||
|
/* Check if cursor is over a window with a viewport */
|
||||||
|
Window *w = FindWindowFromPt(_cursor.pos.x, _cursor.pos.y);
|
||||||
|
if (w != nullptr && w->viewport != nullptr) {
|
||||||
|
/* Check if cursor is actually over the viewport area within the window */
|
||||||
|
Point pt = { _cursor.pos.x - w->left, _cursor.pos.y - w->top };
|
||||||
|
if (pt.x >= w->viewport->left - w->left &&
|
||||||
|
pt.x < w->viewport->left - w->left + w->viewport->width &&
|
||||||
|
pt.y >= w->viewport->top - w->top &&
|
||||||
|
pt.y < w->viewport->top - w->top + w->viewport->height) {
|
||||||
|
target_window = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If no viewport under cursor, use main window */
|
||||||
|
if (target_window == nullptr) {
|
||||||
|
target_window = GetMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply scrolling to the target viewport */
|
||||||
|
if (target_window != nullptr && target_window->viewport != nullptr) {
|
||||||
|
/* Check if the viewport is following a vehicle (similar to mouse scroll behavior) */
|
||||||
|
if (target_window == GetMainWindow() && target_window->viewport->follow_vehicle != VehicleID::Invalid()) {
|
||||||
|
/* If following a vehicle, center on it and stop following (like mouse scroll) */
|
||||||
|
const Vehicle *veh = Vehicle::Get(target_window->viewport->follow_vehicle);
|
||||||
|
ScrollMainWindowTo(veh->x_pos, veh->y_pos, veh->z_pos, true); // This also resets follow_vehicle
|
||||||
|
return; // Don't apply gamepad scroll, just like mouse scroll returns ES_NOT_HANDLED
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply the scroll using the same method as keyboard scrolling */
|
||||||
|
target_window->viewport->dest_scrollpos_x += ScaleByZoom(delta_x, target_window->viewport->zoom);
|
||||||
|
target_window->viewport->dest_scrollpos_y += ScaleByZoom(delta_y, target_window->viewport->zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue