UI: Renderer-Umschalter (WebGL2 / eigene Engine) in der Statusleiste
This commit is contained in:
+18
-1
@@ -65,7 +65,7 @@ import { TextEditorDialog } from "./ui/TextEditorDialog";
|
|||||||
import { RoomStampEditor } from "./panels/RoomStampEditor";
|
import { RoomStampEditor } from "./panels/RoomStampEditor";
|
||||||
import { defaultRoomStamp } from "./model/roomStamp";
|
import { defaultRoomStamp } from "./model/roomStamp";
|
||||||
import type { DetailLevel, RenderMode, View3d, ViewType } from "./ui/TopBar";
|
import type { DetailLevel, RenderMode, View3d, ViewType } from "./ui/TopBar";
|
||||||
import { StatusBar } from "./ui/StatusBar";
|
import { StatusBar, type RendererMode } from "./ui/StatusBar";
|
||||||
import { getTool } from "./tools/tools";
|
import { getTool } from "./tools/tools";
|
||||||
import { applyAngleConstraint, computeSnap } from "./tools/snapping";
|
import { applyAngleConstraint, computeSnap } from "./tools/snapping";
|
||||||
import {
|
import {
|
||||||
@@ -2187,6 +2187,20 @@ export default function App() {
|
|||||||
// aktiv. Sonst sind Cursor/Zoom bedeutungslos (PlanView ist nicht montiert →
|
// aktiv. Sonst sind Cursor/Zoom bedeutungslos (PlanView ist nicht montiert →
|
||||||
// das Handle ist null, Aktionen sind No-Ops).
|
// das Handle ist null, Aktionen sind No-Ops).
|
||||||
const planActive = activeLevel.kind === "floor" && viewType === "grundriss";
|
const planActive = activeLevel.kind === "floor" && viewType === "grundriss";
|
||||||
|
// Plan-Renderer-Umschalter (Statusleiste): reines Ableiten/Setzen des `engine`-
|
||||||
|
// URL-Params (siehe PlanView.tsx `wantWasm`); die Renderer-Auswahl selbst bleibt
|
||||||
|
// dort. Ein Wechsel lädt die Seite neu — kein Live-Remount der Renderer-Hooks.
|
||||||
|
const rendererMode: RendererMode =
|
||||||
|
new URLSearchParams(window.location.search).get("engine") === "wasm"
|
||||||
|
? "wasm"
|
||||||
|
: "webgl2";
|
||||||
|
const webGpuAvailable = typeof navigator !== "undefined" && "gpu" in navigator;
|
||||||
|
const setRendererMode = (mode: RendererMode) => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
if (mode === "wasm") url.searchParams.set("engine", "wasm");
|
||||||
|
else url.searchParams.delete("engine");
|
||||||
|
window.location.assign(url.toString());
|
||||||
|
};
|
||||||
// Übernimmt ein Zeichenwerkzeug ODER eine laufende Transformation die linke
|
// Übernimmt ein Zeichenwerkzeug ODER eine laufende Transformation die linke
|
||||||
// Maustaste der Plan-Ansicht? Dann liegt der Pointer-Input beim Tool/Transform
|
// Maustaste der Plan-Ansicht? Dann liegt der Pointer-Input beim Tool/Transform
|
||||||
// (statt Auswahl/Marquee), und die Editier-Griffe sind ausgeblendet.
|
// (statt Auswahl/Marquee), und die Editier-Griffe sind ausgeblendet.
|
||||||
@@ -2932,6 +2946,9 @@ export default function App() {
|
|||||||
zoomPercent={planActive ? zoomPercent : null}
|
zoomPercent={planActive ? zoomPercent : null}
|
||||||
activeFloor={activeLevel.name}
|
activeFloor={activeLevel.name}
|
||||||
activeLayer={activeLayerName}
|
activeLayer={activeLayerName}
|
||||||
|
rendererMode={rendererMode}
|
||||||
|
webGpuAvailable={webGpuAvailable}
|
||||||
|
onRendererModeChange={setRendererMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Schwebendes Ressourcen-Fenster ÜBER allem (hoher z-index, abgedunkelter
|
{/* Schwebendes Ressourcen-Fenster ÜBER allem (hoher z-index, abgedunkelter
|
||||||
|
|||||||
@@ -186,6 +186,10 @@ export const de = {
|
|||||||
"status.floor": "Geschoss",
|
"status.floor": "Geschoss",
|
||||||
"status.layer": "Ebene",
|
"status.layer": "Ebene",
|
||||||
"status.none": "—",
|
"status.none": "—",
|
||||||
|
"status.renderer": "Renderer",
|
||||||
|
"status.rendererWebgl": "WebGL2",
|
||||||
|
"status.rendererWasm": "Engine",
|
||||||
|
"status.rendererWasmUnavailable": "WebGPU nicht verfügbar",
|
||||||
|
|
||||||
// ── Navigator-Panels ─────────────────────────────────────────────────────
|
// ── Navigator-Panels ─────────────────────────────────────────────────────
|
||||||
"nav.drawingLevels": "Zeichnungsebenen",
|
"nav.drawingLevels": "Zeichnungsebenen",
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"status.floor": "Floor",
|
"status.floor": "Floor",
|
||||||
"status.layer": "Layer",
|
"status.layer": "Layer",
|
||||||
"status.none": "—",
|
"status.none": "—",
|
||||||
|
"status.renderer": "Renderer",
|
||||||
|
"status.rendererWebgl": "WebGL2",
|
||||||
|
"status.rendererWasm": "Engine",
|
||||||
|
"status.rendererWasmUnavailable": "WebGPU unavailable",
|
||||||
|
|
||||||
// Navigator panels.
|
// Navigator panels.
|
||||||
"nav.drawingLevels": "Drawing levels",
|
"nav.drawingLevels": "Drawing levels",
|
||||||
|
|||||||
@@ -760,6 +760,49 @@ body {
|
|||||||
background: var(--border);
|
background: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Renderer-Umschalter (WebGL2 / Engine) in der Statusleiste — kleine
|
||||||
|
Segmentpille, an die 22px-Leiste angepasst (siehe .tb-seg für das größere
|
||||||
|
Vorbild in der Oberleiste). */
|
||||||
|
.sb-renderer-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--input);
|
||||||
|
}
|
||||||
|
.sb-renderer-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border: none;
|
||||||
|
border-left: 1px solid var(--border);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.14s, color 0.14s;
|
||||||
|
}
|
||||||
|
.sb-renderer-btn:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
.sb-renderer-btn:hover:not(:disabled) {
|
||||||
|
background: var(--accent-dim);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.sb-renderer-btn.active {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.sb-renderer-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Command-Line (Rhino-artig) — über der Statusleiste ──────────────────────
|
/* ── Command-Line (Rhino-artig) — über der Statusleiste ──────────────────────
|
||||||
Dunkle, ruhige Leiste mit Prompt links, klickbaren Inline-Optionen und einer
|
Dunkle, ruhige Leiste mit Prompt links, klickbaren Inline-Optionen und einer
|
||||||
monospace-Texteingabe rechts; das Autocomplete-Dropdown öffnet nach oben. */
|
monospace-Texteingabe rechts; das Autocomplete-Dropdown öffnet nach oben. */
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
import type { Vec2 } from "../model/types";
|
import type { Vec2 } from "../model/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
|
/** Plan-Renderer-Wahl, gespiegelt aus dem `?engine`-URL-Param (siehe PlanView). */
|
||||||
|
export type RendererMode = "webgl2" | "wasm";
|
||||||
|
|
||||||
export interface StatusBarProps {
|
export interface StatusBarProps {
|
||||||
/** Kontextueller Werkzeug-Hinweis links (z. B. „Auswahl"). */
|
/** Kontextueller Werkzeug-Hinweis links (z. B. „Auswahl"). */
|
||||||
hint: string;
|
hint: string;
|
||||||
@@ -29,6 +32,15 @@ export interface StatusBarProps {
|
|||||||
activeFloor: string;
|
activeFloor: string;
|
||||||
/** Name der aktiven Ebene (Kategorie) oder „—". */
|
/** Name der aktiven Ebene (Kategorie) oder „—". */
|
||||||
activeLayer: string;
|
activeLayer: string;
|
||||||
|
/** Aktuell gewählter Plan-Renderer (aus dem `?engine`-URL-Param abgeleitet). */
|
||||||
|
rendererMode: RendererMode;
|
||||||
|
/** Ist `navigator.gpu` vorhanden? Steuert, ob „Engine" wählbar ist. */
|
||||||
|
webGpuAvailable: boolean;
|
||||||
|
/**
|
||||||
|
* Renderer wechseln (setzt/entfernt den `engine`-URL-Param und lädt neu — kein
|
||||||
|
* Live-Remount der Renderer-Hooks).
|
||||||
|
*/
|
||||||
|
onRendererModeChange: (mode: RendererMode) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ein Wert-Feld im rechten Cluster: kleine Beschriftung + Wert. */
|
/** Ein Wert-Feld im rechten Cluster: kleine Beschriftung + Wert. */
|
||||||
@@ -57,6 +69,9 @@ export function StatusBar({
|
|||||||
zoomPercent,
|
zoomPercent,
|
||||||
activeFloor,
|
activeFloor,
|
||||||
activeLayer,
|
activeLayer,
|
||||||
|
rendererMode,
|
||||||
|
webGpuAvailable,
|
||||||
|
onRendererModeChange,
|
||||||
}: StatusBarProps) {
|
}: StatusBarProps) {
|
||||||
const coords = cursor
|
const coords = cursor
|
||||||
? `${fmt(cursor.x)}, ${fmt(cursor.y)}`
|
? `${fmt(cursor.x)}, ${fmt(cursor.y)}`
|
||||||
@@ -69,6 +84,30 @@ export function StatusBar({
|
|||||||
|
|
||||||
{/* Rechts: abgeleiteter Wert-Cluster. */}
|
{/* Rechts: abgeleiteter Wert-Cluster. */}
|
||||||
<div className="sb-cluster">
|
<div className="sb-cluster">
|
||||||
|
<span className="sb-field">
|
||||||
|
<span className="sb-key">{t("status.renderer")}</span>
|
||||||
|
<span className="sb-renderer-toggle" role="group" aria-label={t("status.renderer")}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`sb-renderer-btn${rendererMode === "webgl2" ? " active" : ""}`}
|
||||||
|
aria-pressed={rendererMode === "webgl2"}
|
||||||
|
onClick={() => onRendererModeChange("webgl2")}
|
||||||
|
>
|
||||||
|
{t("status.rendererWebgl")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`sb-renderer-btn${rendererMode === "wasm" ? " active" : ""}`}
|
||||||
|
aria-pressed={rendererMode === "wasm"}
|
||||||
|
disabled={!webGpuAvailable}
|
||||||
|
title={webGpuAvailable ? undefined : t("status.rendererWasmUnavailable")}
|
||||||
|
onClick={() => onRendererModeChange("wasm")}
|
||||||
|
>
|
||||||
|
{t("status.rendererWasm")}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<Sep />
|
||||||
<Field label={t("status.xy")} value={coords} />
|
<Field label={t("status.xy")} value={coords} />
|
||||||
<Sep />
|
<Sep />
|
||||||
<Field label={t("status.unit")} value={unit} />
|
<Field label={t("status.unit")} value={unit} />
|
||||||
|
|||||||
Reference in New Issue
Block a user