|
4 | 4 |
|
5 | 5 | #include "df/world_generatorst.h" |
6 | 6 | #include "modules/Gui.h" |
| 7 | +#include "modules/DFSDL.h" |
7 | 8 |
|
8 | 9 | #include "df/enabler.h" |
9 | 10 | #include "df/gamest.h" |
@@ -52,6 +53,62 @@ DFhackCExport command_result plugin_shutdown([[maybe_unused]] color_ostream &out |
52 | 53 | return CR_OK; |
53 | 54 | } |
54 | 55 |
|
| 56 | +static std::atomic_bool request_queued = false; |
| 57 | + |
| 58 | +struct scroll_state { |
| 59 | + int8_t xdiff; |
| 60 | + int8_t ydiff; |
| 61 | +}; |
| 62 | + |
| 63 | +static const scroll_state state_default(0, 0); |
| 64 | + |
| 65 | +static scroll_state state = state_default; |
| 66 | +static scroll_state queued = state_default; |
| 67 | + |
| 68 | +static void render_thread_cb([[maybe_unused]] void* _) { |
| 69 | + queued = state_default; |
| 70 | + // Ignore the mouse if outside the window |
| 71 | + if (!enabler->mouse_focus) { |
| 72 | + request_queued.store(false); |
| 73 | + return; |
| 74 | + } |
| 75 | + |
| 76 | + // Determine window border location in window coordinates |
| 77 | + auto* renderer = virtual_cast<df::renderer_2d>(enabler->renderer); |
| 78 | + int origin_x, origin_y = 0; |
| 79 | + int end_x, end_y; |
| 80 | + DFSDL::DFSDL_RenderLogicalToWindow((SDL_Renderer*)renderer->sdl_renderer, renderer->origin_x, renderer->origin_y, &origin_x, &origin_y); |
| 81 | + DFSDL::DFSDL_RenderLogicalToWindow((SDL_Renderer*)renderer->sdl_renderer, renderer->cur_w - renderer->origin_x, renderer->cur_h - renderer->origin_y, &end_x, &end_y); |
| 82 | + |
| 83 | + int mx, my; |
| 84 | + DFSDL::DFSDL_GetMouseState(&mx, &my); |
| 85 | + |
| 86 | + if (mx <= origin_x + border_range) { |
| 87 | + queued.xdiff--; |
| 88 | + } else if (mx >= end_x - border_range) { |
| 89 | + queued.xdiff++; |
| 90 | + } |
| 91 | + if (my <= origin_y + border_range) { |
| 92 | + queued.ydiff--; |
| 93 | + } else if (my >= end_y - border_range) { |
| 94 | + queued.ydiff++; |
| 95 | + } |
| 96 | + |
| 97 | + request_queued.store(false); |
| 98 | +} |
| 99 | + |
| 100 | +static bool update_mouse_pos() { |
| 101 | + if (request_queued.load()) |
| 102 | + return false; // No new inputs, and a request for more is already placed |
| 103 | + |
| 104 | + state = queued; |
| 105 | + queued = state_default; |
| 106 | + DFHack::runOnRenderThread(render_thread_cb, nullptr); |
| 107 | + request_queued.store(true); |
| 108 | + return true; |
| 109 | +} |
| 110 | + |
| 111 | +// Scrolling behavior |
55 | 112 | template<typename T> |
56 | 113 | static void apply_scroll(T* out, T diff, T min, T max) { |
57 | 114 | *out = std::min(std::max(*out + diff, min), max); |
@@ -134,61 +191,30 @@ static void scroll_world(world_map screen, int xdiff, int ydiff) { |
134 | 191 | } |
135 | 192 |
|
136 | 193 | DFhackCExport command_result plugin_onupdate(color_ostream &out) { |
137 | | - |
138 | | - // Ensure either a map viewscreen or the main viewport are visible |
139 | | - auto worldmap = get_map(); |
140 | | - if (!worldmap.has_value() && (!gps->main_viewport || !gps->main_viewport->flag.bits.active)) |
141 | | - return CR_OK; |
142 | | - |
143 | | - // FIXME: Once dfhooks_sdl_loop is hooked up in Core, use SDL_GetMouseState |
144 | | - // to determine the correct mouse position without forcing the screen to |
145 | | - // render slightly un-centered. |
146 | | - // origin_x/y are already zero if not fitting the interface to the grid |
147 | | - |
148 | | - // Force the origin_x/y values to zero to workaround df marking any |
149 | | - // mouse position within the margin register as invalid |
150 | | - auto renderer = virtual_cast<df::renderer_2d>(enabler->renderer); |
151 | | - if (renderer && (renderer->origin_x != 0 || renderer->origin_y != 0)) { |
152 | | - renderer->origin_x = 0; |
153 | | - renderer->origin_y = 0; |
154 | | - } |
155 | | - |
156 | | - auto dim_x = gps->screen_pixel_x; |
157 | | - auto dim_y = gps->screen_pixel_y; |
158 | | - auto x = gps->precise_mouse_x; |
159 | | - auto y = gps->precise_mouse_y; |
160 | | - if (x == -1 || y == -1) |
161 | | - return CR_OK; // Invalid mouse position |
162 | | - |
163 | 194 | // Apply a cooldown to any potential edgescrolls |
164 | 195 | auto& core = Core::getInstance(); |
165 | 196 | static uint32_t last_action = 0; |
166 | 197 | uint32_t now = core.p->getTickCount(); |
167 | 198 | if (now < last_action + cooldown_ms) |
168 | 199 | return CR_OK; |
169 | 200 |
|
| 201 | + // Update mouse_x/y to values from the render thread |
| 202 | + if (!update_mouse_pos()) |
| 203 | + return CR_OK; |
170 | 204 |
|
171 | | - int xdiff = 0; |
172 | | - int ydiff = 0; |
173 | | - if (x <= border_range) { |
174 | | - xdiff--; |
175 | | - } else if (x >= dim_x - border_range) { |
176 | | - xdiff++; |
177 | | - } |
178 | | - if (y <= border_range) { |
179 | | - ydiff--; |
180 | | - } else if (y >= dim_y - border_range) { |
181 | | - ydiff++; |
182 | | - } |
| 205 | + // Ensure either a map viewscreen or the main viewport are visible |
| 206 | + auto worldmap = get_map(); |
| 207 | + if (!worldmap.has_value() && (!gps->main_viewport || !gps->main_viewport->flag.bits.active)) |
| 208 | + return CR_OK; |
183 | 209 |
|
184 | | - if (xdiff == 0 && ydiff == 0) |
| 210 | + if (state.xdiff == 0 && state.ydiff == 0) |
185 | 211 | return CR_OK; // No work to do |
186 | 212 |
|
187 | 213 | // Dispatch scrolling to active scrollables |
188 | 214 | if (worldmap.has_value()) |
189 | | - scroll_world(worldmap.value(), xdiff, ydiff); |
| 215 | + scroll_world(worldmap.value(), state.xdiff, state.ydiff); |
190 | 216 | else if (gps->main_viewport->flag.bits.active) |
191 | | - scroll_dwarfmode(xdiff, ydiff); |
| 217 | + scroll_dwarfmode(state.xdiff, state.ydiff); |
192 | 218 |
|
193 | 219 | // Update cooldown |
194 | 220 | last_action = now; |
|
0 commit comments