| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Desktop Mode — Games: hub window + desktop icon registration. | |
| 3 | + * OpenStation — Games: hub window + desktop icon registration. | |
| 4 | 4 | * |
| 5 | 5 | * Native window with id `desktop-mode-games` — the "Games folder" |
| 6 | 6 | * fixture on the wallpaper. The template body is a static skeleton |
| 7 | 7 | * (a game grid + a detail panel, Steam-library style) that the JS |
| @@ -9,32 +9,82 @@ | ||
| 9 | 9 | * registry; selecting a game reveals its description, Play + |
| 10 | 10 | * Challenge actions, its scoreboard, and its challenges below. |
| 11 | 11 | * |
| 12 | 12 | * Both registrations are filterable via |
| 13 | - * `desktop_mode_games_window_args` / `desktop_mode_games_icon_args`, | |
| 13 | + * `openstation_games_window_args` / `openstation_games_icon_args`, | |
| 14 | 14 | * mirroring the Recycle Bin. |
| 15 | 15 | * |
| 16 | - * @package WPDesktopMode | |
| 17 | - * @since 0.9.6 | |
| 16 | + * @package OpenStation | |
| 18 | 17 | */ |
| 19 | 18 | |
| 20 | 19 | defined( 'ABSPATH' ) || exit; |
| 21 | 20 | |
| 22 | 21 | /** |
| 22 | + * The stylesheet handles a game window needs, hub or no hub. | |
| 23 | + * | |
| 24 | + * Set once while the Games window is registered — after the | |
| 25 | + * `openstation_games_window_args` filter, so it includes the built-in | |
| 26 | + * games' own sheets and any a plugin appended — and read back when the | |
| 27 | + * shell config is assembled. | |
| 28 | + * | |
| 29 | + * These ride the hub window as companion styles, which is right when | |
| 30 | + * the hub is what opened. It is not the only way in: the challenge | |
| 31 | + * toast's "Accept & Play" is built to work with the hub closed, solo | |
| 32 | + * mode boots straight to `?openstation_solo=os-game-<id>`, and | |
| 33 | + * `wp.os.games.launch()` is documented for plugin authors. Each of | |
| 34 | + * those reaches `launchGame()` in the shell bundle without the hub | |
| 35 | + * window ever existing, and the game then paints unstyled. | |
| 36 | + * | |
| 37 | + * @param string[]|null $set Handles to store, when registering. | |
| 38 | + * @return string[] The stored handles. | |
| 39 | + */ | |
| 40 | +function openstation_games_style_handles( $set = null ) { | |
| 41 | + static $handles = array(); | |
| 42 | + if ( null !== $set ) { | |
| 43 | + $handles = array_values( array_unique( array_filter( array_map( 'strval', (array) $set ) ) ) ); | |
| 44 | + } | |
| 45 | + return $handles; | |
| 46 | +} | |
| 47 | + | |
| 48 | +/** | |
| 23 | 49 | * The shared gamepad SVG used by the window icon and the desktop |
| 24 | 50 | * icon. |
| 25 | 51 | * |
| 26 | - * @since 0.9.6 | |
| 52 | + * Drawn in `currentColor`, which is what makes it a silhouette: | |
| 53 | + * `isSilhouetteSvg()` in `src/icon.ts` looks for exactly that token | |
| 54 | + * and, finding it, paints the art as a CSS mask instead of a | |
| 55 | + * background-image, so it takes whatever colour the surface is | |
| 56 | + * already using for text. | |
| 27 | 57 | * |
| 58 | + * This icon used to be the only fixed-colour one in the shell, in | |
| 59 | + * four hues (`#6c5ce7`, white, `#ffd166`, `#06d6a0`) that belong to | |
| 60 | + * no palette the plugin ships. Two things followed from that. It | |
| 61 | + * could not invert on a light title bar or a light desktop theme, | |
| 62 | + * because a background-image has no colour to inherit. And under a | |
| 63 | + * desktop theme's icon tint it collapsed outright: that path runs | |
| 64 | + * `applyIconMask()`, which keeps only the artwork's alpha, and every | |
| 65 | + * pixel of the old gamepad was opaque, so the d-pad and the buttons | |
| 66 | + * vanished into a featureless blob. | |
| 67 | + * | |
| 68 | + * Redrawn as an outlined body with the controls knocked into it, the | |
| 69 | + * detail is carried by negative space rather than by colour, so it | |
| 70 | + * survives both. Held to five elements because it renders as small as | |
| 71 | + * 20px in the dock: body, two d-pad bars, two buttons. | |
| 72 | + * | |
| 28 | 73 | * @return string Raw `<svg>` markup. |
| 29 | 74 | */ |
| 30 | -function desktop_mode_games_icon_svg() { | |
| 75 | +function openstation_games_icon_svg() { | |
| 31 | 76 | return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">' |
| 32 | - . '<path fill="#6c5ce7" d="M18 16h28a16 16 0 0 1 16 16v10a10 10 0 0 1-18.3 5.6L39.9 42H24.1l-3.8 5.6A10 10 0 0 1 2 42V32a16 16 0 0 1 16-16z"/>' | |
| 33 | - . '<rect x="12" y="28" width="14" height="5" rx="2.5" fill="#ffffff"/>' | |
| 34 | - . '<rect x="16.5" y="23.5" width="5" height="14" rx="2.5" fill="#ffffff"/>' | |
| 35 | - . '<circle cx="43" cy="27" r="3.4" fill="#ffd166"/>' | |
| 36 | - . '<circle cx="50" cy="34" r="3.4" fill="#06d6a0"/>' | |
| 77 | + // The body: outlined, so what sits inside it reads as detail | |
| 78 | + // rather than as more fill. | |
| 79 | + . '<path d="M21 21h22a13 13 0 0 1 12.6 9.8l2.8 11.2a7.5 7.5 0 0 1-13.6 5.8L40.5 41h-17l-4.3 6.8A7.5 7.5 0 0 1 5.6 42l2.8-11.2A13 13 0 0 1 21 21z" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>' | |
| 80 | + // D-pad, two crossed bars sharing a centre. | |
| 81 | + . '<rect x="14" y="28.5" width="14" height="4.6" rx="2.3" fill="currentColor"/>' | |
| 82 | + . '<rect x="18.7" y="23.8" width="4.6" height="14" rx="2.3" fill="currentColor"/>' | |
| 83 | + // Face buttons, offset on the diagonal the way a real pad has | |
| 84 | + // them. Two, not four: four would not separate at 20px. | |
| 85 | + . '<circle cx="40.5" cy="34" r="3.1" fill="currentColor"/>' | |
| 86 | + . '<circle cx="47.5" cy="28.5" r="3.1" fill="currentColor"/>' | |
| 37 | 87 | . '</svg>'; |
| 38 | 88 | } |
| 39 | 89 | |
| 40 | 90 | /** |
| @@ -39,13 +89,11 @@ | ||
| 39 | 89 | |
| 40 | 90 | /** |
| 41 | 91 | * Whether the current user can use desktop games. |
| 42 | 92 | * |
| 43 | - * @since 0.9.6 | |
| 44 | - * | |
| 45 | 93 | * @return bool |
| 46 | 94 | */ |
| 47 | -function desktop_mode_games_user_can_use() { | |
| 95 | +function openstation_games_user_can_use() { | |
| 48 | 96 | $can = is_user_logged_in() && current_user_can( 'read' ); |
| 49 | 97 | |
| 50 | 98 | /** |
| 51 | 99 | * Filter whether the current user can see the Games window, |
| @@ -50,31 +98,27 @@ | ||
| 50 | 98 | /** |
| 51 | 99 | * Filter whether the current user can see the Games window, |
| 52 | 100 | * icon, and play games. |
| 53 | 101 | * |
| 54 | - * @since 0.9.6 | |
| 55 | - * | |
| 56 | 102 | * @param bool $can Default: logged-in + `read` capability. |
| 57 | 103 | */ |
| 58 | - return (bool) apply_filters( 'desktop_mode_games_user_can_use', $can ); | |
| 104 | + return (bool) apply_filters( 'openstation_games_user_can_use', $can ); | |
| 59 | 105 | } |
| 60 | 106 | |
| 61 | 107 | /** |
| 62 | 108 | * Echoes the Games window's template body. |
| 63 | 109 | * |
| 64 | - * The `data-desktop-mode-games-*` hooks are the contract the JS | |
| 110 | + * The `data-os-games-*` hooks are the contract the JS | |
| 65 | 111 | * render callback relies on — keep them intact (or rename via the |
| 66 | 112 | * filter) when customizing the layout. |
| 67 | - * | |
| 68 | - * @since 0.9.6 | |
| 69 | 113 | */ |
| 70 | -function desktop_mode_games_render_template() { | |
| 114 | +function openstation_games_render_template() { | |
| 71 | 115 | ob_start(); |
| 72 | 116 | ?> |
| 73 | - <div class="desktop-mode-games" data-desktop-mode-games-root> | |
| 74 | - <div class="desktop-mode-games__library"> | |
| 75 | - <div class="desktop-mode-games__grid" data-desktop-mode-games-grid role="listbox" aria-label="<?php esc_attr_e( 'Games', 'desktop-mode' ); ?>"></div> | |
| 76 | - <div class="desktop-mode-games__detail" data-desktop-mode-games-detail hidden></div> | |
| 117 | + <div class="desktop-mode-games" data-os-games-root> | |
| 118 | + <div class="os-games__library"> | |
| 119 | + <div class="os-games__grid" data-os-games-grid role="listbox" aria-label="<?php esc_attr_e( 'Games', 'desktop-mode' ); ?>"></div> | |
| 120 | + <div class="os-games__detail" data-os-games-detail hidden></div> | |
| 77 | 121 | </div> |
| 78 | 122 | </div> |
| 79 | 123 | <?php |
| 80 | 124 | $html = (string) ob_get_clean(); |
| @@ -81,17 +125,15 @@ | ||
| 81 | 125 | |
| 82 | 126 | /** |
| 83 | 127 | * Filter the Games window's template HTML. |
| 84 | 128 | * |
| 85 | - * Keep the `data-desktop-mode-games-*` hooks intact so the JS | |
| 129 | + * Keep the `data-os-games-*` hooks intact so the JS | |
| 86 | 130 | * render callback can find its mount points. |
| 87 | 131 | * |
| 88 | - * @since 0.9.6 | |
| 89 | - * | |
| 90 | 132 | * @param string $html Default template HTML. |
| 91 | 133 | */ |
| 92 | - $filtered = (string) apply_filters( 'desktop_mode_games_template_html', $html ); | |
| 93 | - echo wp_kses( $filtered, desktop_mode_native_window_allowed_html() ); | |
| 134 | + $filtered = (string) apply_filters( 'openstation_games_template_html', $html ); | |
| 135 | + echo wp_kses( $filtered, openstation_native_window_allowed_html() ); | |
| 94 | 136 | } |
| 95 | 137 | |
| 96 | 138 | /** |
| 97 | 139 | * Register the Games window + desktop icon on `init`. |
| @@ -97,49 +139,71 @@ | ||
| 97 | 139 | * Register the Games window + desktop icon on `init`. |
| 98 | 140 | * |
| 99 | 141 | * Priority 20, after the native-window registry bootstraps — same |
| 100 | 142 | * timing as the Recycle Bin. |
| 101 | - * | |
| 102 | - * @since 0.9.6 | |
| 103 | 143 | */ |
| 104 | -function desktop_mode_games_register_window() { | |
| 105 | - if ( ! desktop_mode_games_user_can_use() ) { | |
| 144 | +function openstation_games_register_window() { | |
| 145 | + if ( ! openstation_games_user_can_use() ) { | |
| 106 | 146 | return; |
| 107 | 147 | } |
| 108 | 148 | |
| 109 | - $icon_uri = 'data:image/svg+xml;base64,' . base64_encode( desktop_mode_games_icon_svg() ); | |
| 149 | + $icon_uri = 'data:image/svg+xml;base64,' . base64_encode( openstation_games_icon_svg() ); | |
| 110 | 150 | |
| 111 | 151 | $window_args = array( |
| 112 | 152 | 'title' => __( 'Games', 'desktop-mode' ), |
| 113 | 153 | 'icon' => $icon_uri, |
| 114 | - 'template' => 'desktop_mode_games_render_template', | |
| 154 | + 'template' => 'openstation_games_render_template', | |
| 115 | 155 | 'script' => 'desktop-mode-games', |
| 156 | + // Companion styles load with the games bundle on first open. | |
| 157 | + // Every game window (`os-game-<id>`) is opened by | |
| 158 | + // `launchGame()`, which lives in that bundle — so a sheet | |
| 159 | + // riding it here is guaranteed in the tab before any game | |
| 160 | + // paints. Built-in games append their own via the | |
| 161 | + // `openstation_games_window_args` filter below. | |
| 162 | + 'styles' => array( 'desktop-mode-games' ), | |
| 116 | 163 | 'width' => 900, |
| 117 | 164 | 'height' => 600, |
| 118 | 165 | 'min_width' => 560, |
| 119 | 166 | 'min_height' => 400, |
| 120 | - 'placement' => 'dock', | |
| 167 | + // No dock tile from the window registration: the desktop icon | |
| 168 | + // below is this app's one launcher. Registering both used to | |
| 169 | + // mint two entries for one app, each with its own default, and | |
| 170 | + // the dock painted a tile while Preferences said "On the | |
| 171 | + // desktop". The navigation model collapses them either way — | |
| 172 | + // this keeps Games matching how My WordPress and Corkboard | |
| 173 | + // already register. | |
| 174 | + 'placement' => 'none', | |
| 121 | 175 | ); |
| 122 | 176 | |
| 123 | 177 | /** |
| 124 | 178 | * Filter the args used to register the Games native window. |
| 125 | 179 | * |
| 126 | - * @since 0.9.6 | |
| 127 | - * | |
| 128 | - * @param array $window_args Args passed to `desktop_mode_register_window()`. | |
| 180 | + * @param array $window_args Args passed to `openstation_register_window()`. | |
| 129 | 181 | */ |
| 130 | - $window_args = (array) apply_filters( 'desktop_mode_games_window_args', $window_args ); | |
| 182 | + $window_args = (array) apply_filters( 'openstation_games_window_args', $window_args ); | |
| 131 | 183 | |
| 132 | - $registered = desktop_mode_register_window( 'desktop-mode-games', $window_args ); | |
| 184 | + // Remember the finished style list — base sheet plus whatever the | |
| 185 | + // built-in games and any plugin appended through the filter above. | |
| 186 | + // A game window does NOT always arrive through the hub: the | |
| 187 | + // challenge toast's "Accept & Play" is designed to work with the | |
| 188 | + // hub closed, solo mode boots straight into | |
| 189 | + // `?openstation_solo=os-game-<id>`, and `wp.os.games.launch()` is | |
| 190 | + // documented for plugins. In each of those, `launchGame()` runs | |
| 191 | + // from the SHELL bundle's copy, the hub window never opens, and its | |
| 192 | + // companion sheets never load — the HUD paints as raw text. The | |
| 193 | + // shell needs the list to inject them on demand. | |
| 194 | + openstation_games_style_handles( $window_args['styles'] ); | |
| 195 | + | |
| 196 | + $registered = openstation_register_window( 'desktop-mode-games', $window_args ); | |
| 133 | 197 | if ( is_wp_error( $registered ) ) { |
| 134 | 198 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log |
| 135 | - error_log( '[desktop-mode] Games window registration failed: ' . $registered->get_error_message() ); | |
| 199 | + error_log( '[openstation] Games window registration failed: ' . $registered->get_error_message() ); | |
| 136 | 200 | return; |
| 137 | 201 | } |
| 138 | 202 | |
| 139 | 203 | $icon_args = array( |
| 140 | 204 | 'title' => __( 'Games', 'desktop-mode' ), |
| 141 | - 'icon_svg' => desktop_mode_games_icon_svg(), | |
| 205 | + 'icon_svg' => openstation_games_icon_svg(), | |
| 142 | 206 | 'window' => 'desktop-mode-games', |
| 143 | 207 | 'position' => 85, |
| 144 | 208 | ); |
| 145 | 209 | |
| @@ -145,34 +209,30 @@ | ||
| 145 | 209 | |
| 146 | 210 | /** |
| 147 | 211 | * Filter the args used to register the Games desktop icon. |
| 148 | 212 | * |
| 149 | - * @since 0.9.6 | |
| 150 | - * | |
| 151 | - * @param array $icon_args Args passed to `desktop_mode_register_icon()`. | |
| 213 | + * @param array $icon_args Args passed to `openstation_register_icon()`. | |
| 152 | 214 | */ |
| 153 | - $icon_args = (array) apply_filters( 'desktop_mode_games_icon_args', $icon_args ); | |
| 215 | + $icon_args = (array) apply_filters( 'openstation_games_icon_args', $icon_args ); | |
| 154 | 216 | |
| 155 | - desktop_mode_register_icon( 'desktop-mode-games', $icon_args ); | |
| 217 | + openstation_register_icon( 'desktop-mode-games', $icon_args ); | |
| 156 | 218 | } |
| 157 | -add_action( 'init', 'desktop_mode_games_register_window', 20 ); | |
| 219 | +add_action( 'init', 'openstation_games_register_window', 20 ); | |
| 158 | 220 | |
| 159 | 221 | /** |
| 160 | 222 | * Localize REST endpoints for the JS bundle. |
| 161 | 223 | * |
| 162 | - * The bundle reads its config off `window.desktopModeGamesConfig` | |
| 224 | + * The bundle reads its config off `window.openStationGamesConfig` | |
| 163 | 225 | * and never hardcodes URLs. |
| 164 | - * | |
| 165 | - * @since 0.9.6 | |
| 166 | 226 | */ |
| 167 | -function desktop_mode_games_localize_config() { | |
| 168 | - if ( ! desktop_mode_games_user_can_use() ) { | |
| 227 | +function openstation_games_localize_config() { | |
| 228 | + if ( ! openstation_games_user_can_use() ) { | |
| 169 | 229 | return; |
| 170 | 230 | } |
| 171 | 231 | |
| 172 | 232 | wp_localize_script( |
| 173 | 233 | 'desktop-mode-games', |
| 174 | - 'desktopModeGamesConfig', | |
| 234 | + 'openStationGamesConfig', | |
| 175 | 235 | array( |
| 176 | 236 | 'restNonce' => wp_create_nonce( 'wp_rest' ), |
| 177 | 237 | 'gamesUrlBase' => esc_url_raw( rest_url( 'desktop-mode/v1/games' ) ), |
| 178 | 238 | 'challengesUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/games/challenges' ) ), |
| @@ -178,8 +238,9 @@ | ||
| 178 | 238 | 'challengesUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/games/challenges' ) ), |
| 179 | 239 | 'usersSearchUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/games/users/search' ) ), |
| 180 | 240 | ) |
| 181 | 241 | ); |
| 182 | - | |
| 183 | - wp_enqueue_style( 'desktop-mode-games' ); | |
| 184 | 242 | } |
| 185 | -add_action( 'admin_enqueue_scripts', 'desktop_mode_games_localize_config', 30 ); | |
| 243 | +// Priority 5 for the same reason as the recycle bin: the lazy-load payload is | |
| 244 | +// built at priority 10, and localize data attached after that never reaches a | |
| 245 | +// bundle that loads on window open. | |
| 246 | +add_action( 'admin_enqueue_scripts', 'openstation_games_localize_config', 5 ); | |