861 lines
17 KiB
Text
861 lines
17 KiB
Text
|
|
---
|
|||
|
|
title: Frameless Windows
|
|||
|
|
description: Create custom window chrome with frameless windows
|
|||
|
|
sidebar:
|
|||
|
|
order: 4
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
|||
|
|
|
|||
|
|
## Frameless Windows
|
|||
|
|
|
|||
|
|
Wails provides **frameless window support** with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls.
|
|||
|
|
|
|||
|
|
## Quick Start
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Title: "Frameless App",
|
|||
|
|
Width: 800,
|
|||
|
|
Height: 600,
|
|||
|
|
Frameless: true,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**CSS for draggable title bar:**
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
height: 40px;
|
|||
|
|
background: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar button {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**HTML:**
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<div class="titlebar">
|
|||
|
|
<span>My Application</span>
|
|||
|
|
<button onclick="window.close()">×</button>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**That's it!** You have a custom title bar.
|
|||
|
|
|
|||
|
|
## Creating Frameless Windows
|
|||
|
|
|
|||
|
|
### Basic Frameless Window
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
Width: 800,
|
|||
|
|
Height: 600,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What you get:**
|
|||
|
|
- No title bar
|
|||
|
|
- No window borders
|
|||
|
|
- No system buttons
|
|||
|
|
- Transparent background (optional)
|
|||
|
|
|
|||
|
|
**What you need to implement:**
|
|||
|
|
- Draggable area
|
|||
|
|
- Close/minimise/maximise buttons
|
|||
|
|
- Resize handles (if resizable)
|
|||
|
|
|
|||
|
|
### With Transparent Background
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
BackgroundType: application.BackgroundTypeTransparent,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Use cases:**
|
|||
|
|
- Rounded corners
|
|||
|
|
- Custom shapes
|
|||
|
|
- Overlay windows
|
|||
|
|
- Splash screens
|
|||
|
|
|
|||
|
|
## Drag Regions
|
|||
|
|
|
|||
|
|
### CSS-Based Dragging
|
|||
|
|
|
|||
|
|
Use the `--wails-draggable` CSS property:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Draggable area */
|
|||
|
|
.titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Non-draggable elements within draggable area */
|
|||
|
|
.titlebar button {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Values:**
|
|||
|
|
- `drag` - Area is draggable
|
|||
|
|
- `no-drag` - Area is not draggable (even if parent is)
|
|||
|
|
|
|||
|
|
### Complete Title Bar Example
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<div class="titlebar">
|
|||
|
|
<div class="title">My Application</div>
|
|||
|
|
<div class="controls">
|
|||
|
|
<button class="minimize">−</button>
|
|||
|
|
<button class="maximize">□</button>
|
|||
|
|
<button class="close">×</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
height: 40px;
|
|||
|
|
background: #2c2c2c;
|
|||
|
|
color: white;
|
|||
|
|
padding: 0 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-size: 14px;
|
|||
|
|
user-select: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
width: 32px;
|
|||
|
|
height: 32px;
|
|||
|
|
border: none;
|
|||
|
|
background: transparent;
|
|||
|
|
color: white;
|
|||
|
|
font-size: 16px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button:hover {
|
|||
|
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls .close:hover {
|
|||
|
|
background: #e81123;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**JavaScript for buttons:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { Window } from '@wailsio/runtime'
|
|||
|
|
|
|||
|
|
document.querySelector('.minimize').addEventListener('click', () => Window.Minimise())
|
|||
|
|
document.querySelector('.maximize').addEventListener('click', () => Window.Maximise())
|
|||
|
|
document.querySelector('.close').addEventListener('click', () => Window.Close())
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## System Buttons
|
|||
|
|
|
|||
|
|
### Implementing Close/Minimise/Maximise
|
|||
|
|
|
|||
|
|
**Go side:**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type WindowControls struct {
|
|||
|
|
window *application.WebviewWindow
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (wc *WindowControls) Minimise() {
|
|||
|
|
wc.window.Minimise()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (wc *WindowControls) Maximise() {
|
|||
|
|
if wc.window.IsMaximised() {
|
|||
|
|
wc.window.UnMaximise()
|
|||
|
|
} else {
|
|||
|
|
wc.window.Maximise()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (wc *WindowControls) Close() {
|
|||
|
|
wc.window.Close()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**JavaScript side:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { Minimise, Maximise, Close } from './bindings/WindowControls'
|
|||
|
|
|
|||
|
|
document.querySelector('.minimize').addEventListener('click', Minimise)
|
|||
|
|
document.querySelector('.maximize').addEventListener('click', Maximise)
|
|||
|
|
document.querySelector('.close').addEventListener('click', Close)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Or use runtime methods:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { Window } from '@wailsio/runtime'
|
|||
|
|
|
|||
|
|
document.querySelector('.minimize').addEventListener('click', () => Window.Minimise())
|
|||
|
|
document.querySelector('.maximize').addEventListener('click', () => Window.Maximise())
|
|||
|
|
document.querySelector('.close').addEventListener('click', () => Window.Close())
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Toggle Maximise State
|
|||
|
|
|
|||
|
|
Track maximise state for button icon:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { Window } from '@wailsio/runtime'
|
|||
|
|
|
|||
|
|
async function toggleMaximise() {
|
|||
|
|
const isMaximised = await Window.IsMaximised()
|
|||
|
|
|
|||
|
|
if (isMaximised) {
|
|||
|
|
await Window.Restore()
|
|||
|
|
} else {
|
|||
|
|
await Window.Maximise()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateMaximiseButton()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function updateMaximiseButton() {
|
|||
|
|
const isMaximised = await Window.IsMaximised()
|
|||
|
|
const button = document.querySelector('.maximize')
|
|||
|
|
button.textContent = isMaximised ? '❐' : '□'
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Resize Handles
|
|||
|
|
|
|||
|
|
### CSS-Based Resize
|
|||
|
|
|
|||
|
|
Wails provides automatic resize handles for frameless windows:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Enable resize on all edges */
|
|||
|
|
body {
|
|||
|
|
--wails-resize: all;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Or specific edges */
|
|||
|
|
.resize-top {
|
|||
|
|
--wails-resize: top;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-bottom {
|
|||
|
|
--wails-resize: bottom;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-left {
|
|||
|
|
--wails-resize: left;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-right {
|
|||
|
|
--wails-resize: right;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Corners */
|
|||
|
|
.resize-top-left {
|
|||
|
|
--wails-resize: top-left;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-top-right {
|
|||
|
|
--wails-resize: top-right;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-bottom-left {
|
|||
|
|
--wails-resize: bottom-left;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-bottom-right {
|
|||
|
|
--wails-resize: bottom-right;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Values:**
|
|||
|
|
- `all` - Resize from all edges
|
|||
|
|
- `top`, `bottom`, `left`, `right` - Specific edges
|
|||
|
|
- `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners
|
|||
|
|
- `none` - No resize
|
|||
|
|
|
|||
|
|
### Resize Handle Example
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<div class="window">
|
|||
|
|
<div class="titlebar">...</div>
|
|||
|
|
<div class="content">...</div>
|
|||
|
|
<div class="resize-handle resize-bottom-right"></div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.resize-handle {
|
|||
|
|
position: absolute;
|
|||
|
|
width: 16px;
|
|||
|
|
height: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.resize-bottom-right {
|
|||
|
|
--wails-resize: bottom-right;
|
|||
|
|
bottom: 0;
|
|||
|
|
right: 0;
|
|||
|
|
cursor: nwse-resize;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Platform-Specific Behaviour
|
|||
|
|
|
|||
|
|
<Tabs syncKey="platform">
|
|||
|
|
<TabItem label="Windows" icon="seti:windows">
|
|||
|
|
**Windows frameless windows:**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
Windows: application.WindowsOptions{
|
|||
|
|
DisableFramelessWindowDecorations: false,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Features:**
|
|||
|
|
- Automatic drop shadow
|
|||
|
|
- Snap layouts support (Windows 11)
|
|||
|
|
- Aero Snap support
|
|||
|
|
- DPI scaling
|
|||
|
|
|
|||
|
|
**Disable decorations:**
|
|||
|
|
```go
|
|||
|
|
Windows: application.WindowsOptions{
|
|||
|
|
DisableFramelessWindowDecorations: true,
|
|||
|
|
},
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Snap Assist:**
|
|||
|
|
```go
|
|||
|
|
// Trigger Windows 11 Snap Assist
|
|||
|
|
window.SnapAssist()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Custom title bar height:**
|
|||
|
|
Windows automatically detects drag regions from CSS.
|
|||
|
|
</TabItem>
|
|||
|
|
|
|||
|
|
<TabItem label="macOS" icon="apple">
|
|||
|
|
**macOS frameless windows:**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
Mac: application.MacOptions{
|
|||
|
|
TitleBarAppearsTransparent: true,
|
|||
|
|
InvisibleTitleBarHeight: 40,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Features:**
|
|||
|
|
- Native fullscreen support
|
|||
|
|
- Traffic light buttons (optional)
|
|||
|
|
- Vibrancy effects
|
|||
|
|
- Transparent title bar
|
|||
|
|
|
|||
|
|
**Hide traffic lights:**
|
|||
|
|
```go
|
|||
|
|
Mac: application.MacOptions{
|
|||
|
|
TitleBarStyle: application.MacTitleBarStyleHidden,
|
|||
|
|
},
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Invisible title bar:**
|
|||
|
|
Allows dragging whilst hiding the title bar. This only takes effect when the window is frameless or uses `AppearsTransparent`:
|
|||
|
|
```go
|
|||
|
|
Mac: application.MacOptions{
|
|||
|
|
InvisibleTitleBarHeight: 40,
|
|||
|
|
},
|
|||
|
|
```
|
|||
|
|
</TabItem>
|
|||
|
|
|
|||
|
|
<TabItem label="Linux" icon="linux">
|
|||
|
|
**Linux frameless windows:**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Features:**
|
|||
|
|
- Basic frameless support
|
|||
|
|
- CSS drag regions
|
|||
|
|
- Varies by desktop environment
|
|||
|
|
|
|||
|
|
**Desktop environment notes:**
|
|||
|
|
- **GNOME:** Good support
|
|||
|
|
- **KDE Plasma:** Good support
|
|||
|
|
- **XFCE:** Basic support
|
|||
|
|
- **Tiling WMs:** Limited support
|
|||
|
|
|
|||
|
|
**Compositor required:**
|
|||
|
|
Transparency requires a compositor (most modern DEs have one).
|
|||
|
|
</TabItem>
|
|||
|
|
</Tabs>
|
|||
|
|
|
|||
|
|
## Common Patterns
|
|||
|
|
|
|||
|
|
### Pattern 1: Modern Title Bar
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<div class="modern-titlebar">
|
|||
|
|
<div class="app-icon">
|
|||
|
|
<img src="/icon.png" alt="App Icon">
|
|||
|
|
</div>
|
|||
|
|
<div class="title">My Application</div>
|
|||
|
|
<div class="controls">
|
|||
|
|
<button class="minimize">−</button>
|
|||
|
|
<button class="maximize">□</button>
|
|||
|
|
<button class="close">×</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.modern-titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
height: 40px;
|
|||
|
|
background: linear-gradient(to bottom, #3a3a3a, #2c2c2c);
|
|||
|
|
border-bottom: 1px solid #1a1a1a;
|
|||
|
|
padding: 0 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.app-icon {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
width: 24px;
|
|||
|
|
height: 24px;
|
|||
|
|
margin-right: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 13px;
|
|||
|
|
color: #e0e0e0;
|
|||
|
|
user-select: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 1px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
width: 46px;
|
|||
|
|
height: 32px;
|
|||
|
|
border: none;
|
|||
|
|
background: transparent;
|
|||
|
|
color: #e0e0e0;
|
|||
|
|
font-size: 14px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: background 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button:hover {
|
|||
|
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls .close:hover {
|
|||
|
|
background: #e81123;
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 2: Splash Screen
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
splash := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Title: "Loading...",
|
|||
|
|
Width: 400,
|
|||
|
|
Height: 300,
|
|||
|
|
Frameless: true,
|
|||
|
|
AlwaysOnTop: true,
|
|||
|
|
BackgroundType: application.BackgroundTypeTransparent,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
body {
|
|||
|
|
background: transparent;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.splash {
|
|||
|
|
background: white;
|
|||
|
|
border-radius: 12px;
|
|||
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|||
|
|
padding: 40px;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 3: Rounded Window
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
BackgroundType: application.BackgroundTypeTransparent,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
body {
|
|||
|
|
background: transparent;
|
|||
|
|
margin: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.window {
|
|||
|
|
background: white;
|
|||
|
|
border-radius: 16px;
|
|||
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
|||
|
|
overflow: hidden;
|
|||
|
|
height: calc(100vh - 16px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
border-bottom: 1px solid #e0e0e0;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pattern 4: Overlay Window
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Frameless: true,
|
|||
|
|
AlwaysOnTop: true,
|
|||
|
|
BackgroundType: application.BackgroundTypeTransparent,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
body {
|
|||
|
|
background: transparent;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.overlay {
|
|||
|
|
background: rgba(0, 0, 0, 0.8);
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Complete Example
|
|||
|
|
|
|||
|
|
Here's a production-ready frameless window:
|
|||
|
|
|
|||
|
|
**Go:**
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
package main
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
_ "embed"
|
|||
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
//go:embed frontend/dist
|
|||
|
|
var assets embed.FS
|
|||
|
|
|
|||
|
|
func main() {
|
|||
|
|
app := application.New(application.Options{
|
|||
|
|
Name: "Frameless App",
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
|||
|
|
Title: "Frameless Application",
|
|||
|
|
Width: 1000,
|
|||
|
|
Height: 700,
|
|||
|
|
MinWidth: 800,
|
|||
|
|
MinHeight: 600,
|
|||
|
|
Frameless: true,
|
|||
|
|
|
|||
|
|
Assets: application.AssetOptions{
|
|||
|
|
Handler: application.AssetFileServerFS(assets),
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
Mac: application.MacOptions{
|
|||
|
|
TitleBarAppearsTransparent: true,
|
|||
|
|
InvisibleTitleBarHeight: 40,
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
Windows: application.WindowsOptions{
|
|||
|
|
DisableFramelessWindowDecorations: false,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
window.Center()
|
|||
|
|
window.Show()
|
|||
|
|
|
|||
|
|
app.Run()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**HTML:**
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
<head>
|
|||
|
|
<link rel="stylesheet" href="/style.css">
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="window">
|
|||
|
|
<div class="titlebar">
|
|||
|
|
<div class="title">Frameless Application</div>
|
|||
|
|
<div class="controls">
|
|||
|
|
<button class="minimize" title="Minimise">−</button>
|
|||
|
|
<button class="maximize" title="Maximise">□</button>
|
|||
|
|
<button class="close" title="Close">×</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="content">
|
|||
|
|
<h1>Hello from Frameless Window!</h1>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<script src="/main.js" type="module"></script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**CSS:**
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
* {
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 0;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body {
|
|||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.window {
|
|||
|
|
height: 100vh;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
height: 40px;
|
|||
|
|
background: #ffffff;
|
|||
|
|
border-bottom: 1px solid #e0e0e0;
|
|||
|
|
padding: 0 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #333;
|
|||
|
|
user-select: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
width: 32px;
|
|||
|
|
height: 32px;
|
|||
|
|
border: none;
|
|||
|
|
background: transparent;
|
|||
|
|
color: #666;
|
|||
|
|
font-size: 16px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls button:hover {
|
|||
|
|
background: #f0f0f0;
|
|||
|
|
color: #333;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.controls .close:hover {
|
|||
|
|
background: #e81123;
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.content {
|
|||
|
|
flex: 1;
|
|||
|
|
padding: 40px;
|
|||
|
|
overflow: auto;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**JavaScript:**
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { Window } from '@wailsio/runtime'
|
|||
|
|
|
|||
|
|
// Minimise button
|
|||
|
|
document.querySelector('.minimize').addEventListener('click', () => {
|
|||
|
|
Window.Minimise()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// Maximise/restore button
|
|||
|
|
const maximiseBtn = document.querySelector('.maximize')
|
|||
|
|
maximiseBtn.addEventListener('click', async () => {
|
|||
|
|
const isMaximised = await Window.IsMaximised()
|
|||
|
|
|
|||
|
|
if (isMaximised) {
|
|||
|
|
await Window.Restore()
|
|||
|
|
} else {
|
|||
|
|
await Window.Maximise()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
updateMaximiseButton()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// Close button
|
|||
|
|
document.querySelector('.close').addEventListener('click', () => {
|
|||
|
|
Window.Close()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// Update maximise button icon
|
|||
|
|
async function updateMaximiseButton() {
|
|||
|
|
const isMaximised = await Window.IsMaximised()
|
|||
|
|
maximiseBtn.textContent = isMaximised ? '❐' : '□'
|
|||
|
|
maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Initial state
|
|||
|
|
updateMaximiseButton()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Best Practices
|
|||
|
|
|
|||
|
|
### ✅ Do
|
|||
|
|
|
|||
|
|
- **Provide draggable area** - Users need to move the window
|
|||
|
|
- **Implement system buttons** - Close, minimise, maximise
|
|||
|
|
- **Set minimum size** - Prevent unusable layouts
|
|||
|
|
- **Test on all platforms** - Behaviour varies
|
|||
|
|
- **Use CSS for drag regions** - Flexible and maintainable
|
|||
|
|
- **Provide visual feedback** - Hover states on buttons
|
|||
|
|
|
|||
|
|
### ❌ Don't
|
|||
|
|
|
|||
|
|
- **Don't forget resize handles** - If window is resizable
|
|||
|
|
- **Don't make entire window draggable** - Prevents interaction
|
|||
|
|
- **Don't forget no-drag on buttons** - They won't work
|
|||
|
|
- **Don't use tiny drag areas** - Hard to grab
|
|||
|
|
- **Don't forget platform differences** - Test thoroughly
|
|||
|
|
|
|||
|
|
## Troubleshooting
|
|||
|
|
|
|||
|
|
### Window Won't Drag
|
|||
|
|
|
|||
|
|
**Cause:** Missing `--wails-draggable: drag`
|
|||
|
|
|
|||
|
|
**Solution:**
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.titlebar {
|
|||
|
|
--wails-draggable: drag;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Buttons Don't Work
|
|||
|
|
|
|||
|
|
**Cause:** Buttons are in draggable area
|
|||
|
|
|
|||
|
|
**Solution:**
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.titlebar button {
|
|||
|
|
--wails-draggable: no-drag;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Can't Resize Window
|
|||
|
|
|
|||
|
|
**Cause:** Missing resize handles
|
|||
|
|
|
|||
|
|
**Solution:**
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
body {
|
|||
|
|
--wails-resize: all;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Next Steps
|
|||
|
|
|
|||
|
|
<CardGrid>
|
|||
|
|
<Card title="Window Basics" icon="laptop">
|
|||
|
|
Learn the fundamentals of window management.
|
|||
|
|
|
|||
|
|
[Learn More →](/features/windows/basics)
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="Window Options" icon="seti:config">
|
|||
|
|
Complete reference for window options.
|
|||
|
|
|
|||
|
|
[Learn More →](/features/windows/options)
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="Window Events" icon="rocket">
|
|||
|
|
Handle window lifecycle events.
|
|||
|
|
|
|||
|
|
[Learn More →](/features/windows/events)
|
|||
|
|
</Card>
|
|||
|
|
|
|||
|
|
<Card title="Multiple Windows" icon="puzzle">
|
|||
|
|
Patterns for multi-window applications.
|
|||
|
|
|
|||
|
|
[Learn More →](/features/windows/multiple)
|
|||
|
|
</Card>
|
|||
|
|
</CardGrid>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless).
|