mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 13:05:54 +02:00
Launcher: controller starts on PLAY and the open tab; rows and side moves in lists
- The focus starts on PLAY when the launcher opens with a pad connected, and on the open tab (GAME, MY MODS) when the pad opens Settings or the Mod manager. - List rows act as one item: up and down go from row to row in one press, and a row's buttons take the focus only from the row (D-pad right). - D-pad left and right between the rail and a list: ImGui looks to the side only along a thin band through the focus and missed items level with the gap between two rows, so a side move that finds nothing looks again across the focus's full height. - The left stick scrolls the panel under the pointer directly instead of through wheel events, which queued behind the pointer's moves. - On a Steam Deck (SteamDeck=1) the right stick gets a trackpad's feel: small swipes move the pointer, and the glide after the thumb lifts is cut.
This commit is contained in:
1 parent
21d5a0ba4b
commit
7b093429a2
8 files changed
+255
-28
No files matched your search
@@ -22,6 +22,7 @@ void check(bool condition, const char* what) {
|
||||
++failures;
|
||||
}
|
||||
|
||||
constexpr std::uint16_t dpad_up = 0x0001, dpad_left = 0x0004;
|
||||
constexpr std::uint16_t dpad_down = 0x0002, dpad_right = 0x0008, right_thumb = 0x0080, button_a = 0x1000, button_b = 0x2000;
|
||||
|
||||
struct Seen {
|
||||
@@ -39,6 +40,7 @@ Seen frame(PadFeed& feed, const PadState& pad, bool open_menu = false, bool pane
|
||||
io.DeltaTime = 1.0f / 60.0f;
|
||||
feed.update(io, pad, 1.0f);
|
||||
ImGui::NewFrame();
|
||||
feed.after_new_frame();
|
||||
Seen seen;
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(800, 600));
|
||||
@@ -99,6 +101,56 @@ std::string press(PadFeed& feed, std::uint16_t button) {
|
||||
return pressed.empty() ? released : pressed;
|
||||
}
|
||||
|
||||
// The Mod manager's layout: a rail of tabs beside a list whose rows carry
|
||||
// buttons, the rail's tiles level with the gaps between rows.
|
||||
std::string mods_page(PadFeed& feed, const PadState& state) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2(900, 700);
|
||||
io.DeltaTime = 1.0f / 60.0f;
|
||||
feed.update(io, state, 1.0f);
|
||||
ImGui::NewFrame();
|
||||
feed.after_new_frame();
|
||||
std::string pressed;
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImVec2(900, 700));
|
||||
ImGui::Begin("##page", nullptr, ImGuiWindowFlags_NoDecoration);
|
||||
ImGui::SetCursorPos(ImVec2(20, 100));
|
||||
ImGui::BeginChild("##rail", ImVec2(200, 400), ImGuiChildFlags_NavFlattened);
|
||||
if (ImGui::Button("MY MODS", ImVec2(150, 40))) pressed = "MY MODS";
|
||||
dingosdk::launcher_gui::default_focus();
|
||||
if (ImGui::Button("GET MODS", ImVec2(150, 40))) pressed = "GET MODS";
|
||||
ImGui::EndChild();
|
||||
ImGui::SetCursorPos(ImVec2(260, 100));
|
||||
ImGui::BeginChild("##content", ImVec2(600, 500), ImGuiChildFlags_NavFlattened);
|
||||
for (int row = 0; row < 12; ++row) {
|
||||
ImGui::PushID(row);
|
||||
dingosdk::launcher_gui::begin_row();
|
||||
const ImVec2 start = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::Selectable("##row", false, ImGuiSelectableFlags_AllowOverlap, ImVec2(560, 60)))
|
||||
pressed = "row " + std::to_string(row);
|
||||
const ImVec2 next = ImGui::GetCursorScreenPos();
|
||||
dingosdk::launcher_gui::row_buttons();
|
||||
ImGui::SetCursorScreenPos(ImVec2(start.x + 380, start.y + 14));
|
||||
if (ImGui::Button("INSTALL", ImVec2(100, 32))) pressed = "install " + std::to_string(row);
|
||||
dingosdk::launcher_gui::end_row();
|
||||
ImGui::SetCursorScreenPos(next);
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::End();
|
||||
ImGui::Render();
|
||||
return pressed;
|
||||
}
|
||||
|
||||
// Taps a D-pad direction on the Mod manager, then A: what A pressed is where the focus went.
|
||||
std::string mods_tap(PadFeed& feed, std::uint16_t button) {
|
||||
mods_page(feed, pad(button));
|
||||
for (int i = 0; i < 3; ++i) mods_page(feed, pad());
|
||||
std::string pressed = mods_page(feed, pad(button_a));
|
||||
pressed += mods_page(feed, pad());
|
||||
return pressed;
|
||||
}
|
||||
|
||||
void fresh() {
|
||||
if (ImGui::GetCurrentContext()) ImGui::DestroyContext();
|
||||
ImGui::CreateContext();
|
||||
@@ -138,6 +190,33 @@ int main() {
|
||||
check(!(ImGui::GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad), "An unplugged pad is no gamepad");
|
||||
}
|
||||
|
||||
// ------------------------------------------------ the Mod manager
|
||||
{
|
||||
fresh();
|
||||
PadFeed feed;
|
||||
mods_page(feed, pad());
|
||||
mods_page(feed, pad());
|
||||
check(GImGui->NavCursorVisible && mods_tap(feed, 0) == "MY MODS",
|
||||
"With a pad connected the page opens with the focus on its tab");
|
||||
check(mods_tap(feed, dpad_down) == "GET MODS", "Down goes to the next tab");
|
||||
const auto row = mods_tap(feed, dpad_right);
|
||||
check(row.rfind("row ", 0) == 0, "Right goes from a tab level with a gap between rows into the list");
|
||||
check(mods_tap(feed, dpad_left) == "GET MODS", "Left goes from the list back to the tabs");
|
||||
mods_tap(feed, dpad_right);
|
||||
const int first = std::stoi(mods_tap(feed, dpad_down).substr(4));
|
||||
check(mods_tap(feed, dpad_down) == "row " + std::to_string(first + 1), "Down goes to the next row in one press");
|
||||
check(mods_tap(feed, dpad_up) == "row " + std::to_string(first), "Up goes to the row above in one press");
|
||||
check(mods_tap(feed, dpad_right) == "install " + std::to_string(first), "Right goes to the row's own button");
|
||||
check(mods_tap(feed, dpad_up) == "row " + std::to_string(first - 1), "Up from a row's button goes to the row above");
|
||||
}
|
||||
{
|
||||
fresh();
|
||||
PadFeed feed;
|
||||
mods_page(feed, PadState{});
|
||||
mods_page(feed, PadState{});
|
||||
check(!GImGui->NavCursorVisible, "Without a pad nothing shows a focus");
|
||||
}
|
||||
|
||||
// ------------------------------------------------ back
|
||||
{
|
||||
fresh();
|
||||
@@ -227,6 +306,39 @@ int main() {
|
||||
check(scroll > 100, "The left stick scrolls the panel under the mouse");
|
||||
}
|
||||
|
||||
// ------------------------------------------------ the Steam Deck's trackpad
|
||||
{
|
||||
fresh();
|
||||
PadFeed stick;
|
||||
PadFeed trackpad(true);
|
||||
ImGui::GetIO().AddMousePosEvent(400, 300);
|
||||
frame(trackpad, pad());
|
||||
for (int i = 0; i < 10; ++i) frame(trackpad, pad(0, 3300, 0)); // a slow swipe
|
||||
frame(trackpad, pad());
|
||||
check(ImGui::GetIO().MousePos.x > 405, "A slow swipe on the trackpad moves the pointer");
|
||||
fresh();
|
||||
ImGui::GetIO().AddMousePosEvent(400, 300);
|
||||
frame(stick, pad());
|
||||
for (int i = 0; i < 10; ++i) frame(stick, pad(0, 3300, 0));
|
||||
frame(stick, pad());
|
||||
check(!stick.pointing() && ImGui::GetIO().MousePos.x == 400, "The same small tilt of a thumbstick does not");
|
||||
|
||||
fresh();
|
||||
PadFeed glide(true);
|
||||
ImGui::GetIO().AddMousePosEvent(100, 300);
|
||||
frame(glide, pad());
|
||||
for (int i = 0; i < 5; ++i) frame(glide, pad(0, 20000, 0)); // the swipe
|
||||
float tilt = 20000;
|
||||
float stopped = 0;
|
||||
for (int i = 0; i < 30; ++i) { // the thumb lifted: the tilt only fades
|
||||
tilt *= 0.9f;
|
||||
frame(glide, pad(0, static_cast<std::int16_t>(tilt), 0));
|
||||
if (i == 6) stopped = ImGui::GetIO().MousePos.x;
|
||||
}
|
||||
frame(glide, pad());
|
||||
check(stopped > 100 && ImGui::GetIO().MousePos.x == stopped, "The trackpad's glide after the thumb lifts does not carry the pointer on");
|
||||
}
|
||||
|
||||
// ------------------------------------------------ trackpad pointer
|
||||
{
|
||||
fresh();
|
||||
|
||||
+97
-25
@@ -24,15 +24,25 @@ constexpr std::uint16_t focus_bits = dpad_up | dpad_down | dpad_left | dpad_righ
|
||||
|
||||
// XInput's left stick dead zone.
|
||||
constexpr float left_dead_zone = 7849.0f / 32767.0f;
|
||||
// Larger on the right: a thumb resting on the Deck's trackpad is never quite
|
||||
// centred, and the pointer must not creep under it.
|
||||
constexpr float right_dead_zone = 0.2f;
|
||||
// Pointer speed at full tilt, in design pixels a second.
|
||||
constexpr float pointer_speed = 1400.0f;
|
||||
// Scrolling at full tilt: design pixels a second for the focused panel, and
|
||||
// mouse wheel notches a second (ImGui scrolls five lines a notch) under a pointer.
|
||||
// Scrolling at full tilt, in design pixels a second.
|
||||
constexpr float scroll_speed = 1200.0f;
|
||||
constexpr float wheel_speed = 16.0f;
|
||||
|
||||
// How the right stick drives the pointer.
|
||||
struct PointerFeel {
|
||||
float dead_zone;
|
||||
float speed; // design pixels a second at full tilt
|
||||
bool squared; // a small tilt is slow, for placing the pointer precisely
|
||||
bool cut_glide; // stop once the tilt only fades
|
||||
};
|
||||
// A thumbstick sets a speed. A resting thumb is never quite centred, so the
|
||||
// dead zone is large.
|
||||
constexpr PointerFeel stick_feel{0.2f, 1400.0f, true, false};
|
||||
// The Steam Deck's trackpad, in Steam's gamepad layouts, tilts the stick by how
|
||||
// fast the thumb swipes and keeps gliding after it lifts, fading out. Small
|
||||
// swipes must move the pointer, and the glide must not carry it on.
|
||||
constexpr PointerFeel trackpad_feel{0.06f, 2600.0f, false, true};
|
||||
// Frames of a steadily fading tilt that make it a glide rather than the thumb.
|
||||
constexpr int glide_frames = 4;
|
||||
// A mouse move this close to the last one is the same place reported again.
|
||||
constexpr float pointer_slack = 2.0f;
|
||||
|
||||
@@ -41,20 +51,17 @@ float axis(std::int16_t value) { return std::clamp(static_cast<float>(value) / 3
|
||||
// The left stick scrolls whatever the user is looking at: the panel holding
|
||||
// the pad's focus while the focus is shown, else the one under the pointer.
|
||||
// ImGui's own stick scrolling only ever moves the focused window, which under a
|
||||
// pointer is usually the page itself, with nothing to scroll.
|
||||
void scroll(ImGuiIO& io, float tilt, float scale) {
|
||||
// pointer is usually the page itself, with nothing to scroll. It scrolls the
|
||||
// panel directly: wheel events would queue behind the pointer's moves and lag.
|
||||
void scroll(const ImGuiIO& io, float tilt, float scale) {
|
||||
const float amount = std::copysign(std::clamp((std::abs(tilt) - left_dead_zone) / (1 - left_dead_zone), 0.0f, 1.0f), tilt);
|
||||
if (amount == 0.0f) return;
|
||||
const ImGuiContext& g = *GImGui;
|
||||
if (g.NavCursorVisible && g.NavHighlightItemUnderNav) {
|
||||
// The nearest panel around the focus that can scroll.
|
||||
ImGuiWindow* window = g.NavWindow;
|
||||
while (window && window->ScrollMax.y <= 0 && (window->Flags & ImGuiWindowFlags_ChildWindow)) window = window->ParentWindow;
|
||||
if (window && window->ScrollMax.y > 0)
|
||||
ImGui::SetScrollY(window, window->Scroll.y - amount * scroll_speed * scale * io.DeltaTime);
|
||||
return;
|
||||
}
|
||||
io.AddMouseWheelEvent(0, amount * wheel_speed * io.DeltaTime);
|
||||
ImGuiWindow* window = g.NavCursorVisible && g.NavHighlightItemUnderNav ? g.NavWindow : g.HoveredWindow;
|
||||
// The nearest panel around it that can scroll.
|
||||
while (window && window->ScrollMax.y <= 0 && (window->Flags & ImGuiWindowFlags_ChildWindow)) window = window->ParentWindow;
|
||||
if (window && window->ScrollMax.y > 0)
|
||||
ImGui::SetScrollY(window, window->Scroll.y - amount * scroll_speed * scale * io.DeltaTime);
|
||||
}
|
||||
|
||||
// B goes to ImGui's own cancel while ImGui has something to cancel: a combo or
|
||||
@@ -99,22 +106,28 @@ void PadFeed::update(ImGuiIO& io, const PadState& input, float scale) {
|
||||
// a pointer moves, so the pointer goes away instead. Scrolling keeps both.
|
||||
if (pressed & focus_bits) pointing_ = false;
|
||||
|
||||
const PointerFeel& feel = trackpad_ ? trackpad_feel : stick_feel;
|
||||
const float x = axis(pad.right_x), y = -axis(pad.right_y); // screen Y grows downward
|
||||
const float tilt = std::min(1.0f, std::sqrt(x * x + y * y));
|
||||
if (feel.cut_glide) {
|
||||
// A glide only ever fades; the thumb speeds up again or stops.
|
||||
if (tilt <= feel.dead_zone || tilt > last_tilt_ + 0.01f) fading_ = 0;
|
||||
else if (tilt < last_tilt_) ++fading_;
|
||||
last_tilt_ = tilt;
|
||||
}
|
||||
const bool steering = tilt > feel.dead_zone && fading_ < glide_frames;
|
||||
// The first R3 only shows the pointer: a stray click must not press
|
||||
// whatever the mouse last rested on.
|
||||
const bool click = pointing_ && (pressed & right_thumb);
|
||||
if (!pointing_ && (tilt > right_dead_zone || (pressed & right_thumb))) {
|
||||
if (!pointing_ && (steering || (pressed & right_thumb))) {
|
||||
pointing_ = true;
|
||||
// From wherever the mouse left it, which may be outside the window.
|
||||
pointer_ = ImGui::IsMousePosValid(&io.MousePos) ? io.MousePos
|
||||
: ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||
}
|
||||
if (pointing_ && tilt > right_dead_zone) {
|
||||
// Squared, so a small tilt places the pointer precisely and a full one
|
||||
// crosses the window in about a second.
|
||||
const float speed = (tilt - right_dead_zone) / (1 - right_dead_zone);
|
||||
const float step = speed * speed * pointer_speed * scale * io.DeltaTime / tilt;
|
||||
if (pointing_ && steering) {
|
||||
const float speed = (tilt - feel.dead_zone) / (1 - feel.dead_zone);
|
||||
const float step = (feel.squared ? speed * speed : speed) * feel.speed * scale * io.DeltaTime / tilt;
|
||||
pointer_.x += x * step;
|
||||
pointer_.y += y * step;
|
||||
}
|
||||
@@ -134,6 +147,65 @@ void PadFeed::update(ImGuiIO& io, const PadState& input, float scale) {
|
||||
io.MouseDrawCursor = pointing_;
|
||||
}
|
||||
|
||||
void PadFeed::after_new_frame() {
|
||||
ImGuiContext& g = *GImGui;
|
||||
// ImGui looks to the side only along a thin band through the middle of the
|
||||
// focus, so it misses an item level with the gap between two list rows: the
|
||||
// rail's tiles beside a list. A side move that found nothing looks again
|
||||
// across the focus's full height.
|
||||
if (side_dir_ != ImGuiDir_None && g.NavId == side_from_ && g.NavWindow && !g.NavMoveSubmitted) {
|
||||
const ImGuiDir dir = side_dir_;
|
||||
side_dir_ = ImGuiDir_None;
|
||||
ImGui::NavMoveRequestSubmit(dir, dir, ImGuiNavMoveFlags_None,
|
||||
ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY);
|
||||
g.NavScoringRect = ImGui::WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[g.NavLayer]);
|
||||
last_nav_id_ = g.NavId;
|
||||
return;
|
||||
}
|
||||
side_dir_ = ImGuiDir_None;
|
||||
if (g.NavMoveSubmitted && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)) {
|
||||
side_dir_ = g.NavMoveDir;
|
||||
// ImGui clears the focus for the move when the item is wider than its
|
||||
// panel; the move then lands on that same item, which is no move either.
|
||||
side_from_ = g.NavId ? g.NavId : last_nav_id_;
|
||||
}
|
||||
last_nav_id_ = g.NavId;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool g_row_focused{};
|
||||
} // namespace
|
||||
|
||||
void default_focus() {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
ImGuiContext& g = *GImGui;
|
||||
// With a pad there, the focus starts on it straight away as the window
|
||||
// appears: when the launcher starts, and when the pad opened this page.
|
||||
// (Not whenever nothing has the focus: ImGui clears it for a frame while
|
||||
// the focus moves off a row wider than its panel.)
|
||||
const bool pad = (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
||||
const bool pad_led = g.NavCursorVisible && g.NavHighlightItemUnderNav;
|
||||
if (!pad || !ImGui::IsWindowAppearing() || !(pad_led || g.FrameCount <= 2)) return;
|
||||
ImGui::SetFocusID(g.LastItemData.ID, g.CurrentWindow);
|
||||
g.NavCursorVisible = true;
|
||||
g.NavHighlightItemUnderNav = true;
|
||||
}
|
||||
|
||||
void begin_row() {
|
||||
const ImGuiID scope = ImGui::GetID("##row_focus");
|
||||
g_row_focused = GImGui->NavFocusScopeId == scope;
|
||||
ImGui::PushFocusScope(scope);
|
||||
}
|
||||
|
||||
void row_buttons() {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_NoNav, !g_row_focused);
|
||||
}
|
||||
|
||||
void end_row() {
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::PopFocusScope();
|
||||
}
|
||||
|
||||
void PadFeed::mouse_moved(ImVec2 position) {
|
||||
// Windows repeats a mouse move where the mouse already was when a window
|
||||
// changes under it; only a move to a new place is the mouse or a finger.
|
||||
|
||||
@@ -28,9 +28,14 @@ struct PadState {
|
||||
// click is R3, so this is what makes the trackpad a mouse in the launcher.
|
||||
class PadFeed {
|
||||
public:
|
||||
// `trackpad`: the right stick is a Steam Deck's trackpad, not a thumbstick.
|
||||
explicit PadFeed(bool trackpad = false) : trackpad_(trackpad) {}
|
||||
// Call once a frame after the backends' NewFrame and before
|
||||
// ImGui::NewFrame(), which takes the events in.
|
||||
void update(ImGuiIO& io, const PadState& pad, float scale);
|
||||
// Call right after ImGui::NewFrame(): looks again for a D-pad left or right
|
||||
// that ImGui found nothing for.
|
||||
void after_new_frame();
|
||||
// A mouse move or a touch the window received (WM_MOUSEMOVE, client area
|
||||
// pixels). A move to a new place takes the pointer back from the pad.
|
||||
void mouse_moved(ImVec2 position);
|
||||
@@ -40,7 +45,13 @@ public:
|
||||
bool pointing() const { return pointing_; }
|
||||
|
||||
private:
|
||||
bool trackpad_{};
|
||||
std::uint16_t buttons_{};
|
||||
float last_tilt_{};
|
||||
int fading_{}; // frames the right stick's tilt only faded
|
||||
ImGuiDir side_dir_{ImGuiDir_None}; // a D-pad left or right ImGui looked for last frame
|
||||
ImGuiID side_from_{};
|
||||
ImGuiID last_nav_id_{};
|
||||
ImGuiKey back_key_{ImGuiKey_Escape};
|
||||
bool pointing_{};
|
||||
bool clicking_{}; // R3 holds the left button
|
||||
@@ -48,4 +59,17 @@ private:
|
||||
ImVec2 mouse_{-1, -1}; // where the last mouse move was
|
||||
};
|
||||
|
||||
// Makes the item just submitted the one a controller starts on in its window.
|
||||
// With a pad connected, the focus moves there at once when nothing has it yet
|
||||
// or when the pad opened the page.
|
||||
void default_focus();
|
||||
|
||||
// A list row a controller treats as one item: up and down go from row to row
|
||||
// in one press, and the buttons on a row take the focus only from the row
|
||||
// itself (D-pad right). begin_row() before the row's list_row, row_buttons()
|
||||
// after it, end_row() after the row's last button.
|
||||
void begin_row();
|
||||
void row_buttons();
|
||||
void end_row();
|
||||
|
||||
} // namespace dingosdk::launcher_gui
|
||||
+5
-1
@@ -435,7 +435,10 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& a
|
||||
const auto ui_storage = std::make_unique<Ui>();
|
||||
auto& ui = *ui_storage;
|
||||
ModsPanel mods_panel;
|
||||
PadFeed pad_feed;
|
||||
// The Steam Deck's right trackpad reaches the launcher as the right stick
|
||||
// (Steam sets SteamDeck=1 for what it starts there), which wants a
|
||||
// trackpad's feel rather than a thumbstick's.
|
||||
PadFeed pad_feed(GetEnvironmentVariableW(L"SteamDeck", nullptr, 0) > 0);
|
||||
g_pad_feed = &pad_feed;
|
||||
bool running = true;
|
||||
HANDLE game{};
|
||||
@@ -490,6 +493,7 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& a
|
||||
const bool foreground = GetForegroundWindow() == window;
|
||||
pad_feed.update(ImGui::GetIO(), foreground ? read_pad() : PadState{}, g_scale);
|
||||
ImGui::NewFrame();
|
||||
pad_feed.after_new_frame();
|
||||
frame(launcher, fonts, window, ui, mods_panel);
|
||||
// While the pad moves the focus, the mouse cursor (the Deck's trackpad in its
|
||||
// desktop layout) hides; the next mouse move hides the focus and brings it back.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "gui_internal.h"
|
||||
|
||||
#include "mod_manager.h"␍
|
||||
#include "mod_manager.h"
|
||||
|
||||
#include "problem.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -463,6 +464,8 @@ void frame(Launcher& launcher, const Fonts& fonts, HWND window, Ui& ui, ModsPane
|
||||
? (ui.mods_pending == 1 ? std::string("1 MOD UPDATE") : std::format("{} MOD UPDATES", ui.mods_pending))
|
||||
: std::string();
|
||||
if (action_tile(draw, fonts, primary, primary_size, label, detail, enabled && !modal, secondary, play_mark)) act();
|
||||
// A controller starts on PLAY.
|
||||
if (!modal) default_focus();
|
||||
if (settings_tile(draw, fonts, settings, ImVec2(column, S(84)), !modal)) ui.settings = true;
|
||||
if (time - ui.mods_checked > 5 && !ui.mods) {
|
||||
std::size_t enabled_mods = 0, installed = 0;
|
||||
|
||||
@@ -129,10 +129,13 @@ bool rail(const Fonts& fonts, ModsPanel& panel, HWND window, float width,
|
||||
: pending.size() == 1 ? std::string("1 update on Thunderstore")
|
||||
: std::format("{} updates on Thunderstore", pending.size())))
|
||||
panel.tab = 0;
|
||||
// A controller starts on the open tab's tile.
|
||||
if (panel.tab == 0) default_focus();
|
||||
if (nav_tile(fonts, width, "GET MODS", panel.tab == 1,
|
||||
store.loaded ? std::to_string(store.packages.size()) : std::string("..."), false,
|
||||
store.loaded ? std::string() : std::string("Loading the Thunderstore listing")))
|
||||
panel.tab = 1;
|
||||
if (panel.tab == 1) default_focus();
|
||||
|
||||
ImGui::Dummy(ImVec2(0, S(8)));
|
||||
if (!pending.empty()) {
|
||||
@@ -316,8 +319,10 @@ void installed_row(const Fonts& fonts, ModsPanel& panel, const thunderstore::Ins
|
||||
const ImVec2 start = ImGui::GetCursorScreenPos();
|
||||
const float width = ImGui::GetContentRegionAvail().x;
|
||||
const bool ticked = panel.marked.contains(mod.name);
|
||||
begin_row();
|
||||
const bool pressed = list_row("##row", width, tall, ticked);
|
||||
bool menu = ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
||||
row_buttons();
|
||||
if (pressed) {
|
||||
// Ctrl and Shift pick, as they do in a file list; a plain click opens the mod.
|
||||
if (ImGui::GetIO().KeyShift) mark(panel, view, position, true, true);
|
||||
@@ -399,6 +404,7 @@ void installed_row(const Fonts& fonts, ModsPanel& panel, const thunderstore::Ins
|
||||
if (const auto detail = summary(panel, mod); !detail.empty())
|
||||
draw->AddText(fonts.body, fonts.body->FontSize, ImVec2(text_x, start.y + S(37)), color::muted, detail.c_str(),
|
||||
nullptr, 0, &clip);
|
||||
end_row();
|
||||
|
||||
if (menu) ImGui::OpenPopup("##menu");
|
||||
if (ImGui::BeginPopup("##menu")) {
|
||||
|
||||
@@ -518,10 +518,12 @@ void browse_page(Launcher& launcher, const Fonts& fonts, ModsPanel& panel, float
|
||||
const ImVec2 start = ImGui::GetCursorScreenPos();
|
||||
const float width = ImGui::GetContentRegionAvail().x;
|
||||
// The row itself opens the overview; the widgets on it keep their clicks.
|
||||
begin_row();
|
||||
if (list_row("##row", width, tall, ticked)) {
|
||||
store.selected = package.full_name;
|
||||
store.overview = false;
|
||||
}
|
||||
row_buttons();
|
||||
const float right = start.x + width;
|
||||
const float text_x = start.x + S(82);
|
||||
mod_icon(panel, &package, ImVec2(start.x + S(14), start.y + S(11)), S(56));
|
||||
@@ -572,6 +574,7 @@ void browse_page(Launcher& launcher, const Fonts& fonts, ModsPanel& panel, float
|
||||
std::format("v{}{} / {} downloads / {}", version.number,
|
||||
version.file_size ? " / " + size_text(version.file_size) : std::string(),
|
||||
count_text(package.downloads), date_text(package.date_updated)).c_str());
|
||||
end_row();
|
||||
ImGui::PopID();
|
||||
});
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -127,9 +127,12 @@ void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui
|
||||
ImGui::BeginChild("##settings_rail", ImVec2(rail_width, bottom - rail_top), ImGuiChildFlags_NavFlattened);
|
||||
static constexpr std::array<const char*, 4> names{"GAME", "DISPLAY", "KEYS", "ADVANCED"};
|
||||
ImGui::BeginDisabled(binding);
|
||||
for (int i = 0; i < static_cast<int>(names.size()); ++i)
|
||||
for (int i = 0; i < static_cast<int>(names.size()); ++i) {
|
||||
if (nav_tile(fonts, rail_width, names[static_cast<std::size_t>(i)], ui.settings_tab == i))
|
||||
ui.settings_tab = i;
|
||||
// A controller starts on the open tab's tile.
|
||||
if (ui.settings_tab == i) default_focus();
|
||||
}
|
||||
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - S(38));
|
||||
push_primary_button();
|
||||
if (ImGui::Button("\xe2\x86\x90 BACK", ImVec2(-1, S(34)))) open = false;
|
||||
|
||||
Reference in new issue
Block a user