dragTarget depends only on the grid given the drag's anchor, so it joins the other pane geometry; the formula bar's position needs no model at all. runCommand's comment says it is the one place commands run, where a command log would attach. UX.md points at the theme package for roles, key labels, chips and frames.
- commit
- fdd61322ea6db898dd7871f51e5e84d7c5dadc41
- parent
- 62af88b0fb5d0702ec13d4afe6fd8cf956fec5ea
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 8 | ++-- |
| M | internal/ | 4 | +- |
| M | internal/ | 67 | +++--------------------------- |
| M | internal/ | 4 | +- |
| M | internal/ | 55 | +++++++++++++++++++++++++ |
@@ -41,7 +41,7 @@ follow-up task.
4141 4242 ## Visual rules4343 44-- Style only through `theme` roles (`internal/ui/theme.go`). No inline44+- Style only through `theme` roles (`internal/ui/theme`). No inline4545 `lipgloss.NewStyle()` in views. Need a new role? Add it to `theme` with a4646 comment saying what it's for, in both the dark and light variants.4747 - Use the 16 ANSI colors so the user's terminal palette applies. No@@ -53,10 +53,10 @@ follow-up task.
5353 - Numbers right-aligned, text left, booleans and errors centered, one5454 column of padding, text overflowing into empty neighbors: as Sheets.5555 Headers centered. Menu bar titles separated by two spaces. Keys shown56- as `F2`, `Ctrl+Z`, `Del` (see `keyLabel`), drawn as key chips57- (`chip`) in hints, menus, the palette and the shortcuts.56+ as `F2`, `Ctrl+Z`, `Del` (see `theme.KeyLabel`), drawn as key chips57+ (`Theme.Chip`) in hints, menus, the palette and the shortcuts.5858 - Menus, the palette and the shortcuts are overlays: boxes framed with59- light box-drawing lines (`frame`), composited over the grid without59+ light box-drawing lines (`Theme.Frame`), composited over the grid without6060 moving it, with a title in the top border and a position or count in6161 the bottom one. Small questions (quit with unsaved changes) go on the6262 context line as a choice bar, not in a box.@@ -106,7 +106,9 @@ func shortcut(id string) string {
106106 return ""107107 }108108 109-// runCommand runs a registered command by ID.109+// runCommand runs a registered command by ID. It is the one place a110+// command runs, whatever reached it (a key, a menu, the palette, a click111+// on a tab or a chart), so a command log or macro recorder attaches here.110112 func (m *Model) runCommand(id string) tea.Cmd {111113 c, ok := commands[id]112114 if !ok {@@ -63,20 +63,20 @@ type hit struct {
6363 6464 // formulaBarAt returns where the active cell's contents are drawn in READY6565 // mode: the screen line and the x of the first character.66-func (m *Model) formulaBarAt() (line, x int) { return formulaLine, formulaBarTextX() }66+func formulaBarAt() (line, x int) { return formulaLine, formulaBarTextX() }6767 6868 // editLineAt returns where the entry being typed is drawn: in place, in the6969 // formula bar.70-func (m *Model) editLineAt() (line, x int) { return formulaLine, formulaBarTextX() }70+func editLineAt() (line, x int) { return formulaLine, formulaBarTextX() }7171 7272 // hitTest maps a screen position to what's there.7373 func (m *Model) hitTest(x, y int) hit {7474 switch {7575 case y < panelLines:76- if line, tx := m.editLineAt(); y == line && m.editing() {76+ if line, tx := editLineAt(); y == line && m.editing() {7777 return hit{kind: hitEditLine, x: x - tx}7878 }79- if line, tx := m.formulaBarAt(); y == line && x >= tx && m.mode == modeReady {79+ if line, tx := formulaBarAt(); y == line && x >= tx && m.mode == modeReady {8080 return hit{kind: hitFormulaBar, x: x - tx}8181 }8282 return hit{kind: hitPanel}@@ -121,61 +121,6 @@ func (m *Model) hitTest(x, y int) hit {
121121 return hit{}122122 }123123 124-// dragTarget maps a position during a drag to a cell, clamping to the125-// visible grid, and reports which way to autoscroll when it's outside.126-// Dragging from the scrolling area into the frozen panes scrolls back127-// toward them, as in Sheets, until the two meet.128-func (m *Model) dragTarget(x, y int) (a sheet.Addr, dc, dr int) {129- rows := m.screenRows()130- fr, fc := m.frozen()131- anchor := m.dragAnchor()132- firstRow := m.visibleRow(fr)133- scrolledDown := m.top > firstRow134- var last int135- for _, r := range rows {136- if r != divider {137- last = r138- }139- }140- switch row, ok := m.rowAt(y); {141- case ok && (row >= fr || !scrolledDown || anchor.Row < fr):142- a.Row = row143- case y >= gridTop+len(rows):144- a.Row, dr = last, 1145- case y >= gridTop && !scrolledDown: // the divider, with nothing scrolled146- a.Row = m.top147- case scrolledDown && anchor.Row >= fr:148- a.Row, dr = m.top, -1149- default: // above the grid150- a.Row = rows[0]151- if scrolledDown {152- dr = -1153- }154- }155- scrolledRight := m.left > fc156- cols := m.visibleCols(m.left)157- right := m.colStart(m.left + cols)158- switch col, _, ok := m.colSpan(x); {159- case x >= right:160- a.Col, dc = m.left+cols-1, 1161- case ok && (col >= fc || !scrolledRight || anchor.Col < fc):162- a.Col = col163- case x >= rowHdrW && !scrolledRight:164- a.Col = m.left165- case scrolledRight && anchor.Col >= fc:166- a.Col, dc = m.left, -1167- default: // over the row numbers168- a.Col = 0169- if fc == 0 {170- a.Col = m.left171- }172- if scrolledRight || fc == 0 && m.left > 0 {173- dc = -1174- }175- }176- return clampAddr(a), dc, dr177-}178-179124 // dragAnchor is where the drag in progress started.180125 func (m *Model) dragAnchor() sheet.Addr {181126 if m.mouse.drag == dragPoint {@@ -326,7 +271,7 @@ func (m *Model) dragTo(x, y int) tea.Cmd {
326271 case dragTab:327272 return nil // handleMotion tracks the tab under the mouse328273 }329- a, dc, dr := m.dragTarget(x, y)274+ a, dc, dr := m.dragTarget(x, y, m.dragAnchor())330275 switch m.mouse.drag {331276 case dragPoint:332277 m.point.at, m.point.anchored = a, a != m.point.anchor || m.point.anchored@@ -359,7 +304,7 @@ func (m *Model) handleAutoscroll() tea.Cmd {
359304 m.mouse.autoscrolling = false360305 return nil361306 }362- _, dc, dr := m.dragTarget(m.mouse.x, m.mouse.y)307+ _, dc, dr := m.dragTarget(m.mouse.x, m.mouse.y, m.dragAnchor())363308 if dc == 0 && dr == 0 {364309 m.mouse.autoscrolling = false365310 return nil@@ -52,13 +52,13 @@ func TestClickInsertsReferenceIntoFormula(t *testing.T) {
5252 func TestClickFormulaBarEdits(t *testing.T) {5353 m := newModel()5454 press(t, m, "=1+2", "<enter>", "<up>")55- line0, x0 := m.formulaBarAt()55+ line0, x0 := formulaBarAt()5656 click(m, x0+2, line0, 0)5757 if m.mode != modeEdit || m.line.pos != 2 {5858 t.Fatalf("mode %v caret %d", m.mode, m.line.pos)5959 }6060 // Clicking in the edit line moves the caret.61- line1, x1 := m.editLineAt()61+ line1, x1 := editLineAt()6262 click(m, x1+4, line1, 0)6363 if m.line.pos != 4 {6464 t.Errorf("caret %d", m.line.pos)@@ -261,3 +261,58 @@ func (g *grid) cellPos(a sheet.Addr) (x, y int) {
261261 func (g *grid) clampBox(x, y, w, h int) (int, int) {262262 return max(min(x, g.width-w), 0), max(min(y, g.height-h), 0)263263 }264+265+// dragTarget maps a position during a drag to a cell, clamping to the266+// visible grid, and reports which way to autoscroll when it's outside.267+// Dragging from the scrolling area into the frozen panes scrolls back268+// toward them, as in Sheets, until the two meet; anchor is where the269+// drag started.270+func (g *grid) dragTarget(x, y int, anchor sheet.Addr) (a sheet.Addr, dc, dr int) {271+ rows := g.screenRows()272+ fr, fc := g.frozen()273+ firstRow := g.visibleRow(fr)274+ scrolledDown := g.top > firstRow275+ var last int276+ for _, r := range rows {277+ if r != divider {278+ last = r279+ }280+ }281+ switch row, ok := g.rowAt(y); {282+ case ok && (row >= fr || !scrolledDown || anchor.Row < fr):283+ a.Row = row284+ case y >= gridTop+len(rows):285+ a.Row, dr = last, 1286+ case y >= gridTop && !scrolledDown: // the divider, with nothing scrolled287+ a.Row = g.top288+ case scrolledDown && anchor.Row >= fr:289+ a.Row, dr = g.top, -1290+ default: // above the grid291+ a.Row = rows[0]292+ if scrolledDown {293+ dr = -1294+ }295+ }296+ scrolledRight := g.left > fc297+ cols := g.visibleCols(g.left)298+ right := g.colStart(g.left + cols)299+ switch col, _, ok := g.colSpan(x); {300+ case x >= right:301+ a.Col, dc = g.left+cols-1, 1302+ case ok && (col >= fc || !scrolledRight || anchor.Col < fc):303+ a.Col = col304+ case x >= rowHdrW && !scrolledRight:305+ a.Col = g.left306+ case scrolledRight && anchor.Col >= fc:307+ a.Col, dc = g.left, -1308+ default: // over the row numbers309+ a.Col = 0310+ if fc == 0 {311+ a.Col = g.left312+ }313+ if scrolledRight || fc == 0 && g.left > 0 {314+ dc = -1315+ }316+ }317+ return clampAddr(a), dc, dr318+}