MyGo v0.5.5 returns ui.Element as a checked value handle instead of a
pointer. Migrated with `mygo migrate-ui`, then fixed what the codemod
leaves to review:
- Element nil checks become .Valid(); absent elements are ui.Element{}.
- Input handlers and paint callbacks run outside the build pass, where
elements expire and Context methods are inert: read IsDisabled into a
local during the build, keep a ui.Handle for Focus, use c.Services()
for clipboard access, create ui.Local state during the build.
- Add core.AutoFocus: v0.5.5 records an overlay's opener after its
content builds, so same-frame autofocus lost focus restore on close.
It focuses one frame later; cascader rows defer their focus likewise.
- core.OverlayLayer identifies build passes by the root element, not
c.Now(), which repeats across passes and under the Tester clock.
- Context menus survive the opening press counted as PressedOutside;
split button tooltip no longer reorders its open menu; disabled
menubar rows pass presses to the panel; Form defers blur-started
async checks so the growing message no longer moves Submit away
from the release; Combobox drops its disabled copy now that MyGo
blocks typing under a disabled wrapper (canary flipped to a guard).
- The Tester clock is virtual: tests use Advance/Now instead of
time.Sleep/time.Now and read element state inside the view. The web
mirror advances the clock to wall time before each frame and input.
- AGENTS.md and docs describe the value Element API and the new pin.
Snapshot goldens are darwin-only and were not re-verified here.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
82 lines
2.5 KiB
Go
82 lines
2.5 KiB
Go
package media
|
||
|
||
import (
|
||
"image"
|
||
"math"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
)
|
||
|
||
// cropperMin is the smallest crop side in pixels.
|
||
const cropperMin = 8
|
||
|
||
// cropperDragRect moves (mode 1) or corner-resizes (2-5) start.
|
||
func cropperDragRect(start image.Rectangle, mode, dx, dy int, size image.Point, aspect float64) image.Rectangle {
|
||
if mode == 1 {
|
||
dx = max(-start.Min.X, min(size.X-start.Max.X, dx))
|
||
dy = max(-start.Min.Y, min(size.Y-start.Max.Y, dy))
|
||
return start.Add(image.Pt(dx, dy))
|
||
}
|
||
corners := [4]image.Point{start.Min, {start.Max.X, start.Min.Y}, {start.Min.X, start.Max.Y}, start.Max}
|
||
moving, anchor := corners[mode-2], corners[5-mode]
|
||
x := max(0, min(size.X, moving.X+dx))
|
||
y := max(0, min(size.Y, moving.Y+dy))
|
||
sx, sy := 1, 1
|
||
room := image.Pt(size.X-anchor.X, size.Y-anchor.Y)
|
||
if x < anchor.X {
|
||
sx, room.X = -1, anchor.X
|
||
}
|
||
if y < anchor.Y {
|
||
sy, room.Y = -1, anchor.Y
|
||
}
|
||
w, h := cropperClampSize(max(cropperMin, (x-anchor.X)*sx), max(cropperMin, (y-anchor.Y)*sy), room, aspect)
|
||
return image.Rectangle{Min: anchor, Max: anchor.Add(image.Pt(w*sx, h*sy))}.Canon()
|
||
}
|
||
|
||
// cropperClampSize fits w×h inside room, keeping aspect when locked.
|
||
func cropperClampSize(w, h int, room image.Point, aspect float64) (int, int) {
|
||
w, h = min(w, room.X), min(h, room.Y)
|
||
if aspect <= 0 {
|
||
return w, h
|
||
}
|
||
h = int(math.Round(float64(w) / aspect))
|
||
if h > room.Y {
|
||
h = room.Y
|
||
w = int(math.Round(float64(h) * aspect))
|
||
}
|
||
return w, h
|
||
}
|
||
|
||
// cropperKeys: arrows move, Shift+arrows resize, 1% per press.
|
||
func cropperKeys(stage ui.Element, cr *ImageCrop, size image.Point, aspect float64) {
|
||
step := max(1, size.X/100)
|
||
r := cr.Rect
|
||
move := func(dx, dy int) { cr.Rect = cropperDragRect(r, 1, dx, dy, size, aspect) }
|
||
grow := func(dw, dh int) {
|
||
w, h := max(cropperMin, r.Dx()+dw), max(cropperMin, r.Dy()+dh)
|
||
if aspect > 0 && dw == 0 {
|
||
w = int(math.Round(float64(h) * aspect))
|
||
}
|
||
w, h = cropperClampSize(w, h, image.Pt(size.X-r.Min.X, size.Y-r.Min.Y), aspect)
|
||
cr.Rect = image.Rectangle{Min: r.Min, Max: r.Min.Add(image.Pt(w, h))}
|
||
}
|
||
switch {
|
||
case stage.Shortcut(0, ui.KeyLeft):
|
||
move(-step, 0)
|
||
case stage.Shortcut(0, ui.KeyRight):
|
||
move(step, 0)
|
||
case stage.Shortcut(0, ui.KeyUp):
|
||
move(0, -step)
|
||
case stage.Shortcut(0, ui.KeyDown):
|
||
move(0, step)
|
||
case stage.Shortcut(ui.Shift, ui.KeyRight):
|
||
grow(step, 0)
|
||
case stage.Shortcut(ui.Shift, ui.KeyLeft):
|
||
grow(-step, 0)
|
||
case stage.Shortcut(ui.Shift, ui.KeyDown):
|
||
grow(0, step)
|
||
case stage.Shortcut(ui.Shift, ui.KeyUp):
|
||
grow(0, -step)
|
||
}
|
||
}
|