1package ui23import (4 "log/slog"56 tea "charm.land/bubbletea/v2"78 "src.rest/cat/012/internal/macro"910 "src.rest/cat/012/internal/sheet"11)1213// The fill handle, as in Sheets: the bottom-right corner of the selection14// shows a small handle (▟) when the mouse is over that cell. Dragging it15// down, up, right or left fills the cells it passes over, continuing a16// series (1, 2, 3; Jan, Feb; Item 1, Item 2) or copying. While dragging,17// the range that will be filled is highlighted and the context line says18// what releasing does; Esc cancels.1920// fillCorner is the cell with the fill handle: the bottom-right corner of21// the selection. Whole rows and columns have none.22func (g *grid) fillCorner() sheet.Addr {23 if g.whole != wholeNone {24 return sheet.Addr{Col: -1, Row: -1}25 }26 return g.selection().To27}2829// showFillHandle reports whether the fill handle is drawn in cell a:30// while the mouse is over the corner cell, or dragging the handle.31func (m *Model) showFillHandle(a sheet.Addr) bool {32 if m.mode != modeReady || a != m.fillCorner() {33 return false34 }35 switch {36 case m.mouse.drag == dragFill:37 return true38 case m.mouse.drag != dragNone:39 return false40 }41 return (m.mouse.hover.kind == hitCell || m.mouse.hover.kind == hitFillHandle) && m.mouse.hover.addr == a42}4344// startFill starts dragging the fill handle.45func (m *Model) startFill() {46 m.mouse.drag = dragFill47 m.mouse.fillAt, m.mouse.fillTo = m.fillCorner(), m.selection()48}4950// dragFillTo points the fill at cell a: the selection grows along the51// axis a is further out on, as in Sheets.52func (m *Model) dragFillTo(a sheet.Addr) {53 m.mouse.fillAt = a54 src := m.selection()55 down, up := a.Row-src.To.Row, src.From.Row-a.Row56 right, left := a.Col-src.To.Col, src.From.Col-a.Col57 r := src58 switch {59 case max(down, up) <= 0 && max(right, left) <= 0:60 case max(down, up) >= max(right, left):61 if down > 0 {62 r.To.Row = a.Row63 } else {64 r.From.Row = a.Row65 }66 case right > 0:67 r.To.Col = a.Col68 default:69 r.From.Col = a.Col70 }71 m.mouse.fillTo = r72}7374// finishFill fills the range the handle was dragged over and selects it.75func (m *Model) finishFill() {76 m.mouse.drag = dragNone77 src, dst := m.selection(), m.mouse.fillTo78 if dst == src || m.refuseEdit(dst, false) {79 return80 }81 if m.askProtected(dst, func(m *Model) tea.Cmd {82 m.mouse.fillTo = dst83 m.finishFill()84 return nil85 }) {86 return87 }88 m.recordFlush()89 span := m.spans.Start("fill", slog.Int("cells", (dst.To.Row-dst.From.Row+1)*(dst.To.Col-dst.From.Col+1)))90 got, ok := m.writeChecked("Fill", func() (sheet.Rect, error) {91 got, err := m.sheet.FillSeries(src, dst)92 span.Fail(err)93 return got, err94 })95 if !ok {96 return97 }98 m.changed = true99 m.selectRect(got)100 m.record(fillAction(m.rec, src, dst))101}102103// fillAction records a fill from src over dst: the range with absolute104// references, how far with relative ones.105func fillAction(r *recorder, src, dst sheet.Rect) macro.Action {106 if r == nil || !r.relative {107 return macro.Call("fill").With("to", dst.String())108 }109 switch {110 case dst.To.Row > src.To.Row:111 return macro.Call("fill").With("rows", dst.To.Row-src.To.Row)112 case dst.From.Row < src.From.Row:113 return macro.Call("fill").With("rows", dst.From.Row-src.From.Row)114 case dst.To.Col > src.To.Col:115 return macro.Call("fill").With("cols", dst.To.Col-src.To.Col)116 }117 return macro.Call("fill").With("cols", dst.From.Col-src.From.Col)118}119120// cancelFill stops a fill handle drag without filling.121func (m *Model) cancelFill() {122 m.mouse.drag, m.mouse.autoscrolling = dragNone, false123 m.mouse.fillTo = m.selection()124}125126// fillLine is the context line while dragging the fill handle.127func (m *Model) fillLine() string {128 if m.mouse.fillTo == m.selection() {129 return m.th.Key.Render("Fill") + m.th.Muted.Render(" drag down, up, right or left to fill a series or copy ") + m.th.KeyHints("Esc", "cancel")130 }131 return m.th.Key.Render("Fill "+m.mouse.fillTo.String()) + m.th.Muted.Render(" release to fill ") + m.th.KeyHints("Esc", "cancel")132}