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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.24.0] - 2026-08-01

### Added

- **`ScaleChangedEvent`** (ADR-059, gogpu#409) — runtime DPI/scale factor change event. Emitted when window moves between monitors with different DPI or OS DPI settings change. Carries `ScaleFactor`, `Width`, `Height`. Event ordering: `ScaleChangedEvent` before `ResizeEvent` (cause before effect, winit pattern). Enterprise research: winit, Qt6, SDL3, Flutter — all emit separate DPI event.

## [0.23.0] - 2026-07-30

### Added
Expand Down
20 changes: 20 additions & 0 deletions input_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,23 @@ type ResizeEvent struct {
}

func (ResizeEvent) inputEventTag() {}

// ScaleChangedEvent represents a DPI scale factor change.
//
// Emitted when a window moves between monitors with different DPI (e.g.,
// Retina 2x → FullHD 1x), or when the OS DPI setting changes at runtime.
//
// ScaleFactor is the new display scale (1.0 = standard, 2.0 = Retina/HiDPI).
// Width and Height are the new logical size in DIP — may change during DPI
// transition because the OS adjusts the window to preserve visual size.
//
// Event ordering: ScaleChangedEvent is emitted BEFORE ResizeEvent (cause
// before effect, winit pattern). Consumers should update their rendering
// scale before processing the new dimensions.
type ScaleChangedEvent struct {
ScaleFactor float64
Width int
Height int
}

func (ScaleChangedEvent) inputEventTag() {}
31 changes: 28 additions & 3 deletions input_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ func TestInputEventInterface(t *testing.T) {
FocusEvent{Focused: true},
FocusEvent{Focused: false},
ResizeEvent{Width: 800, Height: 600},
ScaleChangedEvent{ScaleFactor: 2.0, Width: 800, Height: 600},
PointerEvent{Type: PointerDown, X: 100, Y: 200},
PointerEvent{Type: PointerMove, X: 150, Y: 250},
ScrollEvent{DeltaY: -3.0, DeltaMode: ScrollDeltaLine},
}

if len(events) != 9 {
t.Fatalf("expected 9 events, got %d", len(events))
if len(events) != 10 {
t.Fatalf("expected 10 events, got %d", len(events))
}

for i, ev := range events {
Expand All @@ -37,6 +38,7 @@ func TestInputEventTypeSwitch(t *testing.T) {
CharEvent{Char: 'x'},
FocusEvent{Focused: true},
ResizeEvent{Width: 1024, Height: 768},
ScaleChangedEvent{ScaleFactor: 2.0, Width: 800, Height: 600},
}

counts := map[string]int{}
Expand All @@ -54,12 +56,14 @@ func TestInputEventTypeSwitch(t *testing.T) {
counts["focus"]++
case ResizeEvent:
counts["resize"]++
case ScaleChangedEvent:
counts["scale"]++
default:
t.Errorf("unexpected event type: %T", ev)
}
}

for _, name := range []string{"key", "pointer", "scroll", "char", "focus", "resize"} {
for _, name := range []string{"key", "pointer", "scroll", "char", "focus", "resize", "scale"} {
if counts[name] != 1 {
t.Errorf("expected 1 %s event, got %d", name, counts[name])
}
Expand Down Expand Up @@ -107,6 +111,27 @@ func TestResizeEventFields(t *testing.T) {
}
}

func TestScaleChangedEventFields(t *testing.T) {
ev := ScaleChangedEvent{ScaleFactor: 2.0, Width: 800, Height: 600}
if ev.ScaleFactor != 2.0 {
t.Errorf("ScaleFactor = %v, want 2.0", ev.ScaleFactor)
}
if ev.Width != 800 || ev.Height != 600 {
t.Errorf("got %dx%d, want 800x600", ev.Width, ev.Height)
}
}

func TestScaleChangedEventImplementsInputEvent(t *testing.T) {
var ev InputEvent = ScaleChangedEvent{ScaleFactor: 1.5, Width: 1024, Height: 768}
se, ok := ev.(ScaleChangedEvent)
if !ok {
t.Fatal("ScaleChangedEvent does not implement InputEvent")
}
if se.ScaleFactor != 1.5 {
t.Errorf("ScaleFactor = %v, want 1.5", se.ScaleFactor)
}
}

func TestPointerEventImplementsInputEvent(t *testing.T) {
var ev InputEvent = PointerEvent{Type: PointerDown, X: 50, Y: 100}
pe, ok := ev.(PointerEvent)
Expand Down
Loading