Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.11.0] - 2026-03-20

### Added

- **WindowChrome interface** for custom window chrome (frameless windows)
- `SetFrameless(bool)` / `IsFrameless() bool` — enable/disable frameless mode
- `SetHitTestCallback(HitTestCallback)` — custom hit testing for drag, resize, buttons
- `Minimize()` / `Maximize()` / `IsMaximized() bool` / `Close()` — window controls
- Optional interface — use type assertion:
`if wc, ok := provider.(gpucontext.WindowChrome); ok { ... }`

- **HitTestResult enum** (13 values) for custom window regions
- `HitTestClient` — normal content area
- `HitTestCaption` — title bar drag area
- `HitTestClose` / `HitTestMaximize` / `HitTestMinimize` — window buttons
- `HitTestResizeN/S/W/E/NW/NE/SW/SE` — 8 resize edges/corners
- `String()` method for debugging

- **HitTestCallback type** — `func(x, y float64) HitTestResult`

- **NullWindowChrome** — no-op implementation for testing

## [0.10.0] - 2026-03-15

### Removed
Expand Down Expand Up @@ -128,6 +150,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **TouchCancelled → TouchCanceled** — US English spelling (misspell linter)
- Removed unused `DeviceHandle` alias

[0.11.0]: https://github.com/gogpu/gpucontext/releases/tag/v0.11.0
[0.10.0]: https://github.com/gogpu/gpucontext/releases/tag/v0.10.0
[0.9.0]: https://github.com/gogpu/gpucontext/releases/tag/v0.9.0
[0.8.0]: https://github.com/gogpu/gpucontext/releases/tag/v0.8.0
[0.7.0]: https://github.com/gogpu/gpucontext/releases/tag/v0.7.0
Expand Down
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Shared GPU infrastructure for the [gogpu](https://github.com/gogpu) ecosystem.
| Package | Purpose | Dependencies |
|---------|---------|--------------|
| [gputypes](https://github.com/gogpu/gputypes) | WebGPU types (enums, structs, constants) | **ZERO** |
| **gpucontext** | Interfaces (DeviceProvider, EventSource, Texture) | imports gputypes |
| **gpucontext** | Interfaces (DeviceProvider, EventSource, WindowChrome, Texture) | imports gputypes |

gpucontext imports gputypes to use shared types in interface signatures, ensuring type compatibility across the ecosystem.

Expand All @@ -34,6 +34,7 @@ go get github.com/gogpu/gpucontext
- **ScrollEventSource** — Scroll/wheel events with pixel/line/page modes
- **Texture** — Minimal interface for GPU textures with TextureUpdater/TextureDrawer/TextureCreator
- **IME Support** — Input Method Editor for CJK languages (Chinese, Japanese, Korean)
- **WindowChrome** — Custom window chrome for frameless windows (hit testing, minimize/maximize/close)
- **Registry[T]** — Generic registry with priority-based backend selection
- **WebGPU Interfaces** — Device, Queue, Adapter, Surface interfaces
- **WebGPU Types** — Re-exports from [gputypes](https://github.com/gogpu/gputypes) (TextureFormat, etc.)
Expand Down Expand Up @@ -247,6 +248,31 @@ func (ctx *Context) DrawTexture(tex gpucontext.Texture, x, y float32) error {
}
```

### WindowChrome (frameless windows)

`WindowChrome` enables custom window chrome for frameless windows with custom title bars:

```go
// In gogpu/ui - custom title bar with hit testing
func (ui *UI) SetupFramelessWindow(provider gpucontext.WindowProvider) {
if wc, ok := provider.(gpucontext.WindowChrome); ok {
wc.SetFrameless(true)
wc.SetHitTestCallback(func(x, y float64) gpucontext.HitTestResult {
if y < 40 { // title bar height
return gpucontext.HitTestCaption // enables window dragging
}
return gpucontext.HitTestClient
})
}
}

// Window controls
wc.Minimize()
wc.Maximize() // toggles maximized/restored
wc.IsMaximized() // for button icon state
wc.Close()
```

### Backend Registry

The `Registry[T]` provides thread-safe registration with priority-based selection:
Expand Down Expand Up @@ -289,7 +315,7 @@ names := backends.Available() // ["vulkan", "software"]
gpucontext
(imports gputypes)
DeviceProvider,
DeviceProvider, WindowChrome,
WindowProvider, PlatformProvider,
EventSource, Texture, Registry
Expand Down
1 change: 1 addition & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// - WindowProvider: Interface for window geometry, DPI, and redraw requests
// - PlatformProvider: Interface for clipboard, cursor, dark mode, accessibility
// - ScrollEventSource: Interface for detailed scroll events
// - WindowChrome: Interface for custom window chrome (frameless windows)
// - Texture: Minimal interface for GPU textures
// - TextureDrawer: Interface for drawing textures (2D rendering)
// - TextureCreator: Interface for creating textures from pixel data
Expand Down
184 changes: 184 additions & 0 deletions window_chrome.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2026 The gogpu Authors
// SPDX-License-Identifier: MIT

package gpucontext

// HitTestResult represents what region of the window the cursor is over.
//
// When a window is frameless (no OS title bar), the application must tell
// the OS what part of the window the cursor is in, so the OS can handle
// dragging, resizing, and window button interactions.
//
// These values map directly to platform-specific hit test constants:
// - Windows: WM_NCHITTEST return values (HTCLIENT, HTCAPTION, etc.)
// - macOS: NSWindow regions
// - Linux: xdg-toplevel resize edges
type HitTestResult int

const (
// HitTestClient indicates the cursor is over the client (content) area.
// The application handles all input normally.
HitTestClient HitTestResult = iota

// HitTestCaption indicates the cursor is over the title bar / drag area.
// The OS handles window dragging on mouse down.
HitTestCaption

// HitTestClose indicates the cursor is over the close button region.
HitTestClose

// HitTestMaximize indicates the cursor is over the maximize button region.
HitTestMaximize

// HitTestMinimize indicates the cursor is over the minimize button region.
HitTestMinimize

// HitTestResizeN indicates the cursor is over the top resize edge.
HitTestResizeN

// HitTestResizeS indicates the cursor is over the bottom resize edge.
HitTestResizeS

// HitTestResizeW indicates the cursor is over the left resize edge.
HitTestResizeW

// HitTestResizeE indicates the cursor is over the right resize edge.
HitTestResizeE

// HitTestResizeNW indicates the cursor is over the top-left resize corner.
HitTestResizeNW

// HitTestResizeNE indicates the cursor is over the top-right resize corner.
HitTestResizeNE

// HitTestResizeSW indicates the cursor is over the bottom-left resize corner.
HitTestResizeSW

// HitTestResizeSE indicates the cursor is over the bottom-right resize corner.
HitTestResizeSE
)

// String returns the hit test result name for debugging.
func (h HitTestResult) String() string {
switch h {
case HitTestClient:
return "Client"
case HitTestCaption:
return "Caption"
case HitTestClose:
return "Close"
case HitTestMaximize:
return "Maximize"
case HitTestMinimize:
return "Minimize"
case HitTestResizeN:
return "ResizeN"
case HitTestResizeS:
return "ResizeS"
case HitTestResizeW:
return "ResizeW"
case HitTestResizeE:
return "ResizeE"
case HitTestResizeNW:
return "ResizeNW"
case HitTestResizeNE:
return "ResizeNE"
case HitTestResizeSW:
return "ResizeSW"
case HitTestResizeSE:
return "ResizeSE"
default:
return "Unknown"
}
}

// HitTestCallback is called by the platform layer to determine what region
// of the window the cursor is in. The coordinates (x, y) are in logical
// points (DIP) relative to the window's top-left corner.
//
// The callback is invoked during mouse move events when the window is frameless.
// It must return quickly to avoid input lag.
type HitTestCallback func(x, y float64) HitTestResult

// WindowChrome provides control over window chrome (title bar, borders).
//
// This interface enables replacing the OS window chrome with a custom
// GPU-rendered title bar. When frameless mode is enabled, the OS removes
// the title bar and borders, and the application takes responsibility for:
// - Rendering its own title bar
// - Providing hit-test regions (drag area, buttons, resize edges)
// - Handling minimize/maximize/close actions
//
// Implementations:
// - gogpu.App implements WindowChrome via platform-specific code
// - NullWindowChrome provides no-op defaults for testing
//
// WindowChrome is optional. Use type assertion to check availability:
//
// if wc, ok := provider.(gpucontext.WindowChrome); ok {
// wc.SetFrameless(true)
// wc.SetHitTestCallback(myHitTest)
// }
type WindowChrome interface {
// SetFrameless enables or disables frameless (borderless) window mode.
// When true, the OS title bar and borders are removed.
// The application must provide its own title bar via SetHitTestCallback.
SetFrameless(frameless bool)

// IsFrameless returns true if the window is in frameless mode.
IsFrameless() bool

// SetHitTestCallback sets the callback that determines what region
// of the window the cursor is over. This is used by the platform layer
// to route mouse events to the OS for dragging, resizing, etc.
//
// Pass nil to clear the callback (all areas become HitTestClient).
SetHitTestCallback(callback HitTestCallback)

// Minimize minimizes the window to the taskbar/dock.
Minimize()

// Maximize toggles between maximized and restored window state.
// If the window is currently maximized, it is restored to its previous size.
Maximize()

// IsMaximized returns true if the window is currently maximized.
IsMaximized() bool

// Close requests the window to close.
// This triggers the normal close flow (close events, cleanup).
Close()
}

// NullWindowChrome implements WindowChrome with no-op behavior.
// Used for testing and platforms without window chrome support.
//
// Default return values:
// - IsFrameless: false
// - IsMaximized: false
// - All actions: no-op
type NullWindowChrome struct{}

// SetFrameless does nothing.
func (NullWindowChrome) SetFrameless(bool) {}

// IsFrameless returns false.
func (NullWindowChrome) IsFrameless() bool { return false }

// SetHitTestCallback does nothing.
func (NullWindowChrome) SetHitTestCallback(HitTestCallback) {}

// Minimize does nothing.
func (NullWindowChrome) Minimize() {}

// Maximize does nothing.
func (NullWindowChrome) Maximize() {}

// IsMaximized returns false.
func (NullWindowChrome) IsMaximized() bool { return false }

// Close does nothing.
func (NullWindowChrome) Close() {}

// Ensure NullWindowChrome implements WindowChrome.
var _ WindowChrome = NullWindowChrome{}
Loading
Loading