<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>tauri-plugin-decor example</title>
<style>
:root {
--tb-h: 40px;
}
html,
body {
height: 100%;
margin: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #fff;
color: #111;
}
body {
padding-top: var(--tb-h);
}
header.app-titlebar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--tb-h);
background: #f3f4f6;
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
padding: 0 86px;
font-size: 13px;
color: #374151;
cursor: grab;
user-select: none;
-webkit-user-select: none;
z-index: 50;
}
header.app-titlebar:active {
cursor: grabbing;
}
main {
padding: 24px;
max-width: 760px;
margin: 0 auto;
}
h1 {
margin: 0 0 12px;
font-size: 22px;
}
p,
li {
font-size: 15px;
line-height: 1.5;
}
code {
background: #f3f4f6;
padding: 1px 6px;
border-radius: 4px;
}
.panel {
margin-top: 24px;
padding: 16px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: #fafafa;
}
.panel h2 {
margin: 0 0 4px;
font-size: 16px;
}
.panel .sub {
margin: 0 0 12px;
color: #6b7280;
font-size: 13px;
}
.row {
display: flex;
align-items: center;
gap: 12px;
margin: 8px 0;
font-size: 14px;
}
.row label {
min-width: 140px;
}
input[type="range"] {
flex: 1;
}
input[type="color"] {
width: 56px;
height: 28px;
padding: 0;
border: 1px solid #d1d5db;
border-radius: 4px;
background: transparent;
}
.val {
min-width: 56px;
text-align: right;
font-variant-numeric: tabular-nums;
color: #6b7280;
}
.preset-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
button.preset {
padding: 8px 14px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: #fff;
cursor: pointer;
font-size: 13px;
color: #111;
}
button.preset:hover {
background: #f3f4f6;
}
button.preset:active {
background: #e5e7eb;
}
.log {
margin-top: 12px;
padding: 10px 12px;
background: #111;
color: #d1d5db;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
white-space: pre-wrap;
min-height: 20px;
}
.row.disabled {
opacity: 0.45;
}
.platform-tag {
display: inline-block;
font-size: 10px;
padding: 1px 6px;
margin-left: 6px;
border-radius: 999px;
background: #e5e7eb;
color: #4b5563;
text-transform: uppercase;
letter-spacing: 0.5px;
vertical-align: middle;
}
.platform-tag.mac { background: #fef3c7; color: #92400e; }
.platform-tag.win { background: #dbeafe; color: #1e40af; }
.platform-tag.lin { background: #d1fae5; color: #065f46; }
</style>
</head>
<body>
<header class="app-titlebar" data-tauri-drag-region>
</header>
<main>
<h1>tauri-plugin-decor example</h1>
<p>
Drag the gray bar at the top to move the window. The plugin's overlay
controls live there too. Below: the two supported ways to drive runtime
updates.
</p>
<div class="panel">
<h2>Way #1 — Frontend command</h2>
<p class="sub">
Sliders call <code>invoke("plugin:decor|update_style", { style })</code>.
The command lands in <code>Decor::reconfigure</code> on the Rust
side and the new values are pushed back via the internal
<code>decor://style-changed</code> Tauri event.
</p>
<div class="row" data-platforms="mac,win,lin">
<label for="height">
Controls height
<span class="platform-tag mac">mac</span>
<span class="platform-tag win">win</span>
<span class="platform-tag lin">lin</span>
</label>
<input id="height" type="range" min="20" max="64" step="1" value="24" />
<span class="val" id="height-v">24</span>
</div>
<div class="row" data-platforms="mac,win,lin">
<label for="scale">
Controls scale
<span class="platform-tag mac">mac</span>
<span class="platform-tag win">win</span>
<span class="platform-tag lin">lin</span>
</label>
<input id="scale" type="range" min="0.7" max="1.6" step="0.05" value="1.0" />
<span class="val" id="scale-v">1.00</span>
</div>
<div class="row" data-platforms="mac">
<label for="inset">
Inset X
<span class="platform-tag mac">mac</span>
</label>
<input id="inset" type="range" min="6" max="40" step="1" value="18" />
<span class="val" id="inset-v">18</span>
</div>
<div class="row" data-platforms="mac">
<label for="spacing">
Spacing
<span class="platform-tag mac">mac</span>
</label>
<input id="spacing" type="range" min="14" max="40" step="1" value="26" />
<span class="val" id="spacing-v">26</span>
</div>
<div class="row" data-platforms="win,lin">
<label for="btnw">
Button width
<span class="platform-tag win">win</span>
<span class="platform-tag lin">lin</span>
</label>
<input id="btnw" type="range" min="28" max="80" step="1" value="46" />
<span class="val" id="btnw-v">46</span>
</div>
<div class="row" data-platforms="win,lin">
<label for="closeBg">
Close hover
<span class="platform-tag win">win</span>
<span class="platform-tag lin">lin</span>
</label>
<input id="closeBg" type="color" value="#c42b1c" />
</div>
<div class="row" data-platforms="win,lin">
<label for="hoverBg">
Button hover
<span class="platform-tag win">win</span>
<span class="platform-tag lin">lin</span>
</label>
<input id="hoverBg" type="color" value="#000000" />
</div>
<div class="log" id="log">awaiting first update…</div>
</div>
<div class="panel">
<h2>Way #2 — Rust-side preset</h2>
<p class="sub">
These buttons invoke a custom command in <code>main.rs</code> that
calls <code>app.decor().reconfigure(DecorStyle { … })</code>.
Same code path inside the plugin as Way #1 — just driven from Rust.
</p>
<div class="preset-row">
<button class="preset" data-preset="compact">Compact</button>
<button class="preset" data-preset="default">Default</button>
<button class="preset" data-preset="comfy">Comfy</button>
</div>
</div>
</main>
<script>
const { invoke } = window.__TAURI__.core;
const logEl = document.getElementById("log");
const platform = (() => {
const ua = navigator.userAgent;
if (/Mac OS X|macOS/i.test(ua)) return "mac";
if (/Windows/i.test(ua)) return "win";
if (/Linux/i.test(ua)) return "lin";
return "unknown";
})();
document.querySelectorAll(".row[data-platforms]").forEach((row) => {
const platforms = row.dataset.platforms.split(",");
if (!platforms.includes(platform)) {
row.classList.add("disabled");
row.title = `no-op on ${platform}; supported on: ${platforms.join(", ")}`;
}
});
const setVal = (id, v) => (document.getElementById(`${id}-v`).textContent = v);
const sendStyle = (style) => {
logEl.textContent = "→ " + JSON.stringify(style);
invoke("plugin:decor|update_style", { style })
.then(() => {
logEl.textContent = "✓ " + JSON.stringify(style);
})
.catch((err) => {
logEl.textContent = "✗ " + JSON.stringify(style) + " " + String(err);
console.error("[decor] update_style failed:", err);
});
};
document.getElementById("height").addEventListener("input", (e) => {
const v = Number(e.target.value);
setVal("height", v);
sendStyle({ controls_height: v });
});
document.getElementById("scale").addEventListener("input", (e) => {
const v = Number(e.target.value);
setVal("scale", v.toFixed(2));
sendStyle({ controls_scale: v });
});
document.getElementById("inset").addEventListener("input", (e) => {
const v = Number(e.target.value);
setVal("inset", v);
sendStyle({ controls_inset_x: v });
});
document.getElementById("spacing").addEventListener("input", (e) => {
const v = Number(e.target.value);
setVal("spacing", v);
sendStyle({ controls_spacing: v });
});
document.getElementById("btnw").addEventListener("input", (e) => {
const v = Number(e.target.value);
setVal("btnw", v);
sendStyle({ controls_button_width: v });
});
const hexToRgba = (hex, alpha = 1) => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r},${g},${b},${alpha})`;
};
document.getElementById("closeBg").addEventListener("input", (e) => {
sendStyle({ controls_close_hover_bg: hexToRgba(e.target.value, 1) });
});
document.getElementById("hoverBg").addEventListener("input", (e) => {
sendStyle({ controls_button_hover_bg: hexToRgba(e.target.value, 0.25) });
});
const syncFormToPreset = (preset) => {
const presets = {
compact: { height: 20, scale: 1.0, btnw: 38 },
default: { height: 24, scale: 1.0, btnw: 46 },
comfy: { height: 32, scale: 1.0, btnw: 56 },
};
const p = presets[preset] || presets.default;
const set = (id, v, fmt = (x) => x) => {
document.getElementById(id).value = v;
setVal(id, fmt(v));
};
set("height", p.height);
set("scale", p.scale, (x) => x.toFixed(2));
set("btnw", p.btnw);
};
document.querySelectorAll("button.preset").forEach((btn) => {
btn.addEventListener("click", () => {
const preset = btn.dataset.preset;
logEl.textContent = "→ apply_rust_preset(" + preset + ")";
invoke("apply_rust_preset", { preset })
.then(() => {
logEl.textContent = "✓ apply_rust_preset(" + preset + ")";
})
.catch((err) => {
logEl.textContent = "✗ apply_rust_preset(" + preset + ") " + String(err);
console.error("[decor] apply_rust_preset failed:", err);
});
syncFormToPreset(preset);
});
});
</script>
</body>
</html>