diff --git a/addons/drop/drop.go b/addons/drop/drop.go new file mode 100644 index 0000000..0435f98 --- /dev/null +++ b/addons/drop/drop.go @@ -0,0 +1,7 @@ +// +build !windows + +package drop + +func Register(dropper Dropper) error { + return nil +} diff --git a/addons/drop/drop_windows.c b/addons/drop/drop_windows.c new file mode 100644 index 0000000..ff6dc28 --- /dev/null +++ b/addons/drop/drop_windows.c @@ -0,0 +1,52 @@ + +#include +#include +#include + +#include "_cgo_export.h" + +#define droppedFilePathSize 32767 + +HHOOK nextHook; +uint32_t nextDropID = 0; + +LRESULT CALLBACK DragAndDropHook( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam) +{ + if (nCode < 0 || wParam == 0) + return CallNextHookEx(nextHook, nCode, wParam, lParam); + + LPMSG message = (LPMSG)lParam; + switch (message->message) + { + case WM_DROPFILES: + { + wchar_t droppedFilePath[droppedFilePathSize]; + uint32_t dropID = nextDropID++; + + HDROP drop = (HDROP)message->wParam; + POINT position; + DragQueryPoint(drop, &position); + droppedFilesPosition(dropID, position.x, position.y); + UINT numberOfFiles = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); + for (UINT fileIndex = 0; fileIndex < numberOfFiles; fileIndex++) + { + UINT length = DragQueryFileW(drop, fileIndex, droppedFilePath, droppedFilePathSize); + droppedFilesFile(dropID, &droppedFilePath[0], length); + } + droppedFilesDrop(dropID); + } + break; + } + return CallNextHookEx(nextHook, nCode, wParam, lParam); +} + +void SetDragAndDropHook(void *window) +{ + HWND windowHandle = (HWND)window; + DragAcceptFiles(windowHandle, TRUE); + DWORD threadId = GetWindowThreadProcessId(windowHandle, NULL); + nextHook = SetWindowsHookEx(WH_GETMESSAGE, DragAndDropHook, NULL, threadId); +} diff --git a/addons/drop/drop_windows.go b/addons/drop/drop_windows.go new file mode 100644 index 0000000..774382e --- /dev/null +++ b/addons/drop/drop_windows.go @@ -0,0 +1,70 @@ +// +build windows + +package drop + +import ( + "errors" + "unsafe" + + "golang.org/x/text/encoding/unicode" + "opslag.de/schobers/geom" +) + +/* +#include +#include + +void SetDragAndDropHook(void* window); +*/ +import "C" + +var handler Dropper = nil +var drops map[uint32]droppedFiles = map[uint32]droppedFiles{} + +type droppedFiles struct { + X, Y int + Files []string +} + +//export droppedFilesPosition +func droppedFilesPosition(id C.uint32_t, x, y C.INT) { + files := drops[uint32(id)] + files.X = int(x) + files.Y = int(y) + drops[uint32(id)] = files +} + +//export droppedFilesFile +func droppedFilesFile(id C.uint32_t, filePath *C.wchar_t, filePathLength C.UINT) { + files := drops[uint32(id)] + pathBytes := C.GoBytes(unsafe.Pointer(filePath), C.int(filePathLength)*C.sizeof_wchar_t) + decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder() + path, err := decoder.Bytes(pathBytes) + if err != nil { + return + } + files.Files = append(files.Files, string(path)) + drops[uint32(id)] = files +} + +//export droppedFilesDrop +func droppedFilesDrop(id C.uint32_t) { + h := handler + if h == nil { + return + } + drop, ok := drops[uint32(id)] + if !ok { + return + } + h.FilesDropped(geom.Pt(drop.X, drop.Y).ToF32(), drop.Files) +} + +func Register(dropper Dropper) error { + if handler != nil { + return errors.New(`can only register single dropper`) + } + handler = dropper + C.SetDragAndDropHook(unsafe.Pointer(dropper.WindowHandle())) + return nil +} diff --git a/addons/drop/dropper.go b/addons/drop/dropper.go new file mode 100644 index 0000000..adc9bdd --- /dev/null +++ b/addons/drop/dropper.go @@ -0,0 +1,9 @@ +package drop + +import "opslag.de/schobers/geom" + +type Dropper interface { + WindowHandle() uintptr + + FilesDropped(geom.PointF32, []string) +} diff --git a/allg5ui/renderer_default.go b/allg5ui/renderer_default.go new file mode 100644 index 0000000..b434245 --- /dev/null +++ b/allg5ui/renderer_default.go @@ -0,0 +1,5 @@ +// +build !windows + +package allg5ui + +func (r *Renderer) WindowHandle() uintptr { return 0 } diff --git a/allg5ui/renderer_windows.go b/allg5ui/renderer_windows.go new file mode 100644 index 0000000..dd0e992 --- /dev/null +++ b/allg5ui/renderer_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package allg5ui + +func (r *Renderer) WindowHandle() uintptr { + return uintptr(r.disp.WindowHandle()) +} diff --git a/sdlui/renderer.go b/sdlui/renderer.go index ab20896..19f50c7 100644 --- a/sdlui/renderer.go +++ b/sdlui/renderer.go @@ -499,6 +499,26 @@ func (r *Renderer) TextTexture(font ui.Font, color color.Color, text string) (ui return r.text(font, color, text) } +func (r *Renderer) WindowHandle() uintptr { + info, err := r.window.GetWMInfo() + if err != nil { + return 0 + } + switch info.Subsystem { + case sdl.SYSWM_COCOA: + return uintptr(info.GetCocoaInfo().Window) + case sdl.SYSWM_DIRECTFB: + return uintptr(info.GetDFBInfo().Window) + case sdl.SYSWM_UIKIT: + return uintptr(info.GetUIKitInfo().Window) + case sdl.SYSWM_WINDOWS: + return uintptr(info.GetWindowsInfo().Window) + case sdl.SYSWM_X11: + return uintptr(info.GetX11Info().Window) + } + return 0 +} + // Resources func (r *Renderer) Resources() ui.Resources { return r.resources } diff --git a/ui/examples/02_drop/drop.go b/ui/examples/02_drop/drop.go new file mode 100644 index 0000000..91b5d06 --- /dev/null +++ b/ui/examples/02_drop/drop.go @@ -0,0 +1,111 @@ +package main + +import ( + "image/color" + "log" + "strings" + "time" + + "opslag.de/schobers/geom" + "opslag.de/schobers/zntg/addons/drop" + _ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI + + "opslag.de/schobers/zntg/ui" +) + +type dropFiles struct { + ui.StackPanel + + ctx ui.Context + files *ui.Paragraph + ping *ping +} + +type ping struct { + ui.OverlayBase + + circle ui.Texture + position geom.PointF32 + tick time.Time +} + +func (p *ping) Render(ctx ui.Context) { + elapsed := time.Since(p.tick) + const animationDuration = 500 * time.Millisecond + if elapsed > animationDuration { + return + } + tint := color.Gray{Y: uint8(elapsed * 255 / animationDuration)} + center := geom.PtF32(float32(p.circle.Width()), float32(p.circle.Height())).Mul(.5) + ctx.Renderer().DrawTexturePointOptions(p.circle, p.position.Sub(center), ui.DrawOptions{Tint: tint}) + ctx.Animate() +} + +func (d *dropFiles) WindowHandle() uintptr { return d.ctx.Renderer().WindowHandle() } + +func (d *dropFiles) FilesDropped(position geom.PointF32, files []string) { + d.files.Text = "Files dropped:\n" + strings.Join(files, "\n") + d.ping.tick = time.Now() + d.ping.position = position + d.ctx.Animate() +} + +func newCircle() ui.ImageSource { + const side = 16 + center := geom.PtF32(.5*side, .5*side) + return &ui.AlphaPixelImageSource{ + ImageAlphaPixelTestFn: func(p geom.PointF32) uint8 { + dist := p.Distance(center) + if dist < 7 { + return 255 + } else if dist > 8 { + return 0 + } + return uint8((8 - dist) * 255 * 0.5) + }, + Size: geom.Pt(side, side), + } +} + +func (d *dropFiles) Init(ctx ui.Context) error { + d.ctx = ctx + drop.Register(d) + + _, err := ctx.Fonts().CreateFontPath("default", "../resources/font/OpenSans-Regular.ttf", 14) + if err != nil { + return err + } + + d.files = ui.BuildParagraph("", nil) + + pingCircle, err := ctx.Renderer().CreateTexture(newCircle()) + if err != nil { + return err + } + d.ping = &ping{circle: pingCircle} + ctx.Overlays().AddOnTop("ping", d.ping, true) + + d.Background = color.White + d.Children = []ui.Control{ + &ui.Label{Text: "Drop files on this window!"}, + d.files, + } + return nil +} + +func run() error { + var render, err = ui.NewRendererDefault("Files Drop Example", 800, 600) + if err != nil { + return err + } + defer render.Destroy() + + return ui.RunWait(render, ui.DefaultStyle(), &dropFiles{}, true) +} + +func main() { + var err = run() + if err != nil { + log.Fatal(err) + } +} diff --git a/ui/renderer.go b/ui/renderer.go index bc51bdf..91af1c4 100644 --- a/ui/renderer.go +++ b/ui/renderer.go @@ -41,6 +41,7 @@ type Renderer interface { Text(font Font, p geom.PointF32, color color.Color, text string) TextAlign(font Font, p geom.PointF32, color color.Color, text string, align HorizontalAlignment) TextTexture(font Font, color color.Color, text string) (Texture, error) + WindowHandle() uintptr // Resources Resources() Resources