dd basic drag thumbnail support to dnd kitten

This commit is contained in:
Kovid Goyal
2026-05-03 22:18:18 +05:30
parent ee38d3f0c8
commit 4fe29f2630
6 changed files with 108 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ package dnd
import (
"errors"
"fmt"
"image"
"io"
"maps"
"os"
@@ -11,6 +12,7 @@ import (
"strings"
"github.com/emmansun/base64"
"github.com/kovidgoyal/imaging"
"github.com/kovidgoyal/kitty/tools/tui/loop"
"github.com/kovidgoyal/kitty/tools/utils"
"github.com/kovidgoyal/kitty/tools/utils/streaming_base64"
@@ -50,6 +52,72 @@ type drag_status struct {
remote_item_write_id loop.IdType
}
func find_drag_image(drag_sources map[string]*drag_source) image.Image {
for mime, ds := range drag_sources {
if strings.HasPrefix(mime, "image/") && ds.path != "" {
q, err := imaging.Open(ds.path)
if err == nil {
return q
}
}
}
var uri_list []string
if ds := drag_sources["text/uri-list"]; ds != nil && len(ds.data) > 0 {
if q, err := parse_uri_list(string(ds.data)); err == nil {
for _, path := range q {
if path != "" {
uri_list = append(uri_list, path)
}
}
}
}
for _, path := range uri_list {
q, err := imaging.Open(path)
if err == nil {
return q
}
}
// TODO: Try to generate an image based preview using the machinery from the choose-files kitten
return nil
}
func (dnd *dnd) set_drag_image() (err error) {
img := dnd.drag_thumbnail
if img == nil {
img = find_drag_image(dnd.drag_sources)
}
if img == nil {
return
}
num_channels := utils.IfElse(imaging.IsOpaque(img), 3, 4)
sz := dnd.opts.DragThumbnailSize
if max(img.Bounds().Dx(), img.Bounds().Dy()) > sz {
w, h := 0, 0
if img.Bounds().Dx() >= img.Bounds().Dy() {
w = sz
} else {
h = sz
}
img = imaging.ResizeWithOpacity(img, w, h, imaging.Lanczos, num_channels == 3)
if dnd.drag_thumbnail != nil {
dnd.drag_thumbnail = img
}
}
var pix []byte
if imaging.IsOpaque(img) {
_, pix = 3, imaging.AsRGBData8(img)
} else {
pix = imaging.AsRGBAData8(img)
}
cmd := DC{
Type: 'p', X: -1, Y: utils.IfElse(num_channels == 3, 24, 32), Xp: img.Bounds().Dx(), Yp: img.Bounds().Dy(),
Payload: pix}
dnd.lp.QueueDnDData(cmd)
cmd.Payload = nil
dnd.lp.QueueDnDData(cmd)
return nil
}
func (dnd *dnd) on_potential_drag_start(cell_x, cell_y int) (err error) {
if !dnd.allow_drags || dnd.drag_status.active {
return
@@ -72,7 +140,11 @@ func (dnd *dnd) on_potential_drag_start(cell_x, cell_y int) (err error) {
}
}
dnd.drag_status.offered_mimes = mimes
// TODO: set the drag image
err = dnd.set_drag_image()
if err != nil {
dnd.finish_drag("EIO")
return err
}
dnd.lp.QueueDnDData(DC{Type: 'P', X: -1}) // start drag
dnd.drag_status.active = true

View File

@@ -5,6 +5,7 @@ package dnd
import (
"bytes"
"fmt"
"image"
"io"
"maps"
"net/url"
@@ -15,6 +16,7 @@ import (
"strings"
"sync/atomic"
"github.com/kovidgoyal/imaging"
"github.com/kovidgoyal/kitty/tools/cli"
"github.com/kovidgoyal/kitty/tools/tty"
"github.com/kovidgoyal/kitty/tools/tui/loop"
@@ -78,6 +80,7 @@ type dnd struct {
opts *Options
drop_dests map[string]*drop_dest
drag_sources map[string]*drag_source
drag_thumbnail image.Image
allow_drops, allow_drags bool
lp *loop.Loop
@@ -382,6 +385,12 @@ func dnd_main(cmd *cli.Command, opts *Options, args []string) (rc int, err error
}
}
dnd := dnd{opts: opts, drop_dests: drop_dests, drag_sources: drag_sources}
if opts.DragThumbnail != "" {
if dnd.drag_thumbnail, err = imaging.Open(opts.DragThumbnail); err != nil {
return 1, err
}
}
defer func() {
dnd.end_drop(false)
if dnd.confirm_drop.staging_dir != nil {

View File

@@ -33,6 +33,19 @@ default=disallowed
type=choices
Allow dropping anywhere, not just on the Copy or Move drop regions. Dropping anywhere will perform
the specified action.
--drag-thumbnail
Path to an image to use as the drag icon when starting a drag. Must be in PNG/JPEG/GIF/WEBP formats.
Other formats will require the presence of ImageMagick on the system to load the image.
If not specified, an image is derived based on the data being dragged.
--drag-thumbnail-size
default=512
type=int
The thumbnail size for the image used as the drag icon. Images larger than this size are downscaled.
Note that the terminal may reject the drag if the image is too large.
'''.format