Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,821 changes: 1,821 additions & 0 deletions plugins/RemoteControl/webroot/gamepad.css

Large diffs are not rendered by default.

240 changes: 237 additions & 3 deletions plugins/RemoteControl/webroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<title><?= tr("Stellarium remote control plugin")?></title>
<link type="text/css" rel="stylesheet" href="/external/jquery-ui.css"/>
<link type="text/css" rel="stylesheet" href="style.css" />
<link type="text/css" rel="stylesheet" href="gamepad.css" />
<!-- Stellarium uses require.js to provide a controlled module environment -->
<script data-main="js/main" src="js/require.js" type="text/javascript"></script>
</head>
Expand Down Expand Up @@ -39,6 +40,7 @@ <h1><?= tr("Stellarium remote control")?></h1>
<li><a href="#tab_location"><?= tr("Location")?></a></li>
<li><a href="#tab_projection"><?= tr("Projection")?></a></li>
<li class="stelplugin" data-plugin="Scenery3d"><a href="#tab_scenery3d"><?= tr("Scenery3d")?></a></li>
<li><a href="#tab_gamepad"><?= tr("Gamepad Controller")?></a></li>
</ul>
<div id="tab_main">
<div class="flex">
Expand Down Expand Up @@ -855,8 +857,240 @@ <h3><?= tr("Scene selection")?></h3>
</div>
<iframe id="s3d_info" class="flex-expand">No scene selected</iframe>
</div>
</div>
</div>
</div>
<!-- Gamepad Controller Section - Built on W3C Gamepad API/gpcontroller.js module v2.6.0 -->
<div id="tab_gamepad">
<section id="gp-controller" class="block">
<header>
<h2><?= tr("Gamepad Controller")?></h2>
<div id="gp-status" class="controller-status inline-block" style="margin-left: 20px;">
<span id="gp-connection-status">🔴 <?= tr("Disconnected")?></span>
<button id="gp-connect" class="jquerybutton" style="margin-left: 10px;"><?= tr("Scan")?></button>
</div>
</header>

<div class="flex flex-wrap" style="gap: 20px;">
<!-- Controller Settings Panel -->
<div class="controller-settings-panel">
<h3><?= tr("Controller Settings")?></h3>
<!-- Device Selector -->
<div class="device-selector-row">
<label><?= tr("Active Device:")?></label>
<select id="gp-device-selector">
<option value=""><?= tr("No devices connected")?></option>
</select>
</div>
<!-- Device Information -->
<div class="device-info-card">
<div class="device-info-row">
<span class="device-info-label"><?= tr("Device:")?></span>
<span class="device-info-value" id="gp-device-name">-</span>
</div>
<div class="device-info-row">
<span class="device-info-label"><?= tr("Model:")?></span>
<span class="device-info-value" id="gp-device-model">-</span>
</div>
<div class="device-info-row">
<span class="device-info-label"><?= tr("Vendor:")?></span>
<span class="device-info-value" id="gp-device-vendor">-</span>
</div>
<div class="device-info-row">
<span class="device-info-label"><?= tr("Device ID:")?></span>
<span class="device-info-value" id="gp-device-id" style="font-size: 10px;">-</span>
</div>
<div class="device-info-row">
<span class="device-info-label"><?= tr("Buttons/Axes:")?></span>
<span class="device-info-value" id="gp-button-axis-count" style="font-size: 10px;">- / -</span>
</div>
</div>
<!-- Joystick Preview -->
<div class="joystick-preview-wrapper">
<h4><?= tr("Joystick Preview")?></h4>
<div class="joystick-preview-container">
<!-- Left Stick Section -->
<div class="joystick-preview">
<canvas id="gp-left-stick-canvas" width="100" height="100"></canvas>
<div class="stick-label">
<strong><?= tr("Left Stick")?></strong>
<small><?= tr("View movement")?></small>
</div>
<div class="joystick-info">
<span class="info-label"><?= tr("Direction:")?></span>
<span class="info-value" id="gp-current-direction"><?= tr("centered")?></span>
</div>
<div class="stick-options">
<label class="option-item">
<input type="checkbox" id="gp-invert-x" />
<span><?= tr("Invert X")?></span>
</label>
<label class="option-item">
<input type="checkbox" id="gp-invert-y" />
<span><?= tr("Invert Y")?></span>
</label>
</div>
</div>

<!-- Right Stick Section -->
<div class="joystick-preview">
<canvas id="gp-right-stick-canvas" width="100" height="100"></canvas>
<div class="stick-label">
<strong><?= tr("Right Stick")?></strong>
<small><?= tr("Continuous zoom")?></small>
</div>
<div class="joystick-info">
<span class="info-label"><?= tr("Current FOV:")?></span>
<span class="info-value" id="gp-current-fov">60°</span>
</div>
<div class="stick-options">
<label class="option-item">
<input type="checkbox" id="zoom-invert-y" />
<span><?= tr("Invert Y")?></span>
</label>
</div>
</div>
</div>
</div>
<!-- Calibration Section -->
<div class="calibration-section">
<div class="calibration-header">
<h4><?= tr("Joystick Calibration")?></h4>
<div class="calibration-buttons">
<button id="gp-calibrate" class="calibration-btn"><?= tr("Calibrate")?></button>
<button id="gp-reset-calibration" class="calibration-btn"><?= tr("Reset")?></button>
</div>
</div>
<div id="gp-calibration-values" class="calibration-status"></div>
<p class="note" style="margin-top: 5px; font-size: 10px; color: #aaa;">
<?= tr("Move joysticks in circles to calibrate center position. Press any button to finish.")?>
</p>
</div>
<!-- Control Settings -->
<div class="control-slider-group">
<!-- Checkbox Group -->
<div class="checkbox-group">
<!-- Manual Test Button -->
<div class="checkbox-item" style="margin-top: 5px; margin-bottom: 15px;">
<button id="gp-test-vibration" class="test-vibration-btn"><?= tr("Test Vibration")?></button>
<span class="help-text" style="margin-left: 10px;"><?= tr("Manual test (uses full intensity)")?></span>
</div>

<div class="checkbox-item">
<input type="checkbox" id="gp-vibration-feedback" />
<label for="gp-vibration-feedback"><?= tr("Vibration Feedback")?></label>
<span class="help-text">(<?= tr("works in Chrome/Edge")?>)</span>
</div>
</div>

<!-- Vibration Intensity Slider (visible only when feedback enabled) -->
<div class="slider-row" id="gp-vibration-intensity-row" style="display: none;">
<span class="slider-label"><?= tr("Vibration Intensity:")?></span>
<div class="slider-container">
<div id="gp-vibration-intensity" class="slider"></div>
</div>
<span id="gp-vibration-intensity-value" class="slider-value">0.5</span>
</div>

<!-- Sensitivity Slider -->
<div class="slider-row">
<span class="slider-label"><?= tr("Sensitivity:")?></span>
<div class="slider-container">
<div id="gp-sensitivity" class="slider"></div>
</div>
<span id="gp-sensitivity-value" class="slider-value">1.0</span>
</div>

<!-- Deadzone Slider -->
<div class="slider-row">
<span class="slider-label"><?= tr("Deadzone:")?></span>
<div class="slider-container">
<div id="gp-deadzone" class="slider"></div>
</div>
<span id="gp-deadzone-value" class="slider-value">0.15</span>
</div>

<!-- Zoom Speed Slider -->
<div class="slider-row">
<span class="slider-label"><?= tr("Zoom Speed:")?></span>
<div class="slider-container">
<div id="gp-zoom-speed" class="slider"></div>
</div>
<span id="gp-zoom-speed-value" class="slider-value">0.05</span>
</div>

<!-- Movement Speed Slider -->
<div class="slider-row">
<span class="slider-label"><?= tr("Movement Speed:")?></span>
<div class="slider-container">
<div id="gp-movement-speed" class="slider"></div>
</div>
<span id="gp-movement-speed-value" class="slider-value">5.0</span>
</div>
</div>
</div>
<!-- Button Customization Panel -->
<div class="button-customization-panel">
<h3><?= tr("Button Customization")?></h3>

<div id="gp-button-customization" class="button-customization-container">
<!-- Populated by JavaScript -->
</div>

<div class="customization-note">
<?= tr("Each connected controller has its own independent profile. Select a device from 'Active Device' to customize.")?>
</div>

<div class="action-buttons">
<button id="gp-reset-mappings" class="action-btn reset"><?= tr("Reset Mappings")?></button>
<button id="gp-save-mappings" class="action-btn save"><?= tr("Save Mappings")?></button>
<button id="gp-export-profile" class="action-btn export"><?= tr("Export Profile")?></button>
<button id="gp-import-profile" class="action-btn import"><?= tr("Import Profile")?></button>
</div>
<p class="note" style="margin-top: 10px; font-size: 10px; color: #aaa; text-align: center;">
<?= tr("Profiles are saved per device and can be exported/imported for backup or sharing")?>
</p>
</div>
</div>
<!-- Live Input Panel - Updated Structure -->
<div id="gp-live-input" class="live-input-panel">
<div class="live-input-header">
<h4><?= tr("Live Input Monitor")?></h4>
<p><?= tr("Real-time raw values from the browser's Gamepad API. Buttons show press state, Axes show visual meter. Use this panel to verify button and axis mappings.")?></p>
</div>
<div class="live-input-content">
<div class="live-input-two-columns">
<!-- Buttons Column - 4 column grid -->
<div class="live-input-column">
<h5><?= tr("Buttons")?> <span style="font-size:8px;">(4-column grid)</span></h5>
<div id="gp-buttons-container" class="live-input-grid-buttons">
<div class="loading-placeholder"><?= tr("Waiting for controller input...")?></div>
</div>
</div>
<!-- Axes Column - with visual meter -->
<div class="live-input-column">
<h5><?= tr("Axes")?> <span style="font-size:8px;">(visual meter)</span></h5>
<div id="gp-axes-container" class="live-input-grid-axes">
<div class="loading-placeholder"><?= tr("Waiting for controller input...")?></div>
</div>
</div>
</div>
<!-- Device Information Section -->
<div class="live-info-section">
<h5><?= tr("Device Information")?></h5>
<div id="gp-device-info-live">
<div class="loading-placeholder"><?= tr("Waiting for controller input...")?></div>
</div>
<div class="info-note-live">
<h4><?= tr("Understanding Raw Values")?></h4>
<?= tr("Buttons: Range 0.0 (not pressed) to 1.0 (green = fully pressed).")?><br>
<?= tr("Axes: -1.0 to 1.0 with visual meter Center position is 0.0. Triggers (L2/R2) show gradual values.")?><br>
<?= tr("Note: Different browsers may report the same physical button on different indices. Use this panel to identify the correct mapping for your setup.")?>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<ul class="ui-corner-all ui-widget-content button32list margin-vertical">
<li title="<?= tr("Constellation lines")?>"><button class="stelaction icon32 btConstellationLines" name="actionShow_Constellation_Lines"></button></li>
<li title="<?= tr("Constellation boundaries")?>"><button class="stelaction icon32 btConstellationBoundaries" name="actionShow_Constellation_Boundaries"></button></li>
Expand All @@ -882,4 +1116,4 @@ <h3><?= tr("Scene selection")?></h3>
</div>
<div id="loadindicator"></div>
</body>
</html>
</html>
6 changes: 3 additions & 3 deletions plugins/RemoteControl/webroot/js/api/viewcontrol.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ define(["jquery", "./remotecontrol"], function($, rc) {
if (x !== 0 || y !== 0) {
updateTimeout = setTimeout(function() {
move(x, y);
}, 250); //repeat each 250ms to avoid Stellarium thinking the interface crashed
}, 100); //repeat each 250ms to avoid Stellarium thinking the interface crashed
}
}
});
Expand All @@ -57,7 +57,7 @@ define(["jquery", "./remotecontrol"], function($, rc) {

if (queuedFov === lastServerFov) {
//dont do another request just yet, nothing changed for now
fovTimeout = setTimeout(fovServerUpdate, 250);
fovTimeout = setTimeout(fovServerUpdate, 50);
} else {
var fov = queuedFov;
fovXHR = $.ajax({
Expand All @@ -71,7 +71,7 @@ define(["jquery", "./remotecontrol"], function($, rc) {

if (lastServerFov !== queuedFov) {
//we have not yet reached the queued value, queue another update after some time
fovTimeout = setTimeout(fovServerUpdate, 250);
fovTimeout = setTimeout(fovServerUpdate, 50);
} else {
fovXHR = undefined;
fovTimeout = undefined;
Expand Down
8 changes: 7 additions & 1 deletion plugins/RemoteControl/webroot/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ requirejs.config({
}
});

require(["ui/mainui"]);
// Load main UI module and Gamepad controller
require(["ui/mainui", "ui/gpcontroller"], function(mainui, gpcontroller) {
"use strict";

// Initialize gamepad controller after main UI is ready
// The actual initialization will happen in mainui.js when UI is ready
});
Loading
Loading