From ba34490aaed5f7c242c2b0453130fefc07d0d5d0 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 29 Jun 2026 04:03:38 -0400 Subject: restructured project --- internal/tui/update.go | 296 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 internal/tui/update.go (limited to 'internal/tui/update.go') diff --git a/internal/tui/update.go b/internal/tui/update.go new file mode 100644 index 0000000..87e0917 --- /dev/null +++ b/internal/tui/update.go @@ -0,0 +1,296 @@ +package tui + +import ( + "fmt" + + "tui-ascii-mapper/internal/model" + "tui-ascii-mapper/internal/tools" + + tea "github.com/charmbracelet/bubbletea" +) + +func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + + case mapLoadedMsg: + if msg.err != nil { + m.dialogMsg = fmt.Sprintf("Load error: %v", msg.err) + m.mode = model.ModeNormal + m.dialog = model.DialogNone + return m, nil + } + m.map_ = msg.data + m.rootMap = msg.data + m.undo = &model.UndoStack{} + m.undo.Push(m.map_) + m.cursor = model.Point{X: 0, Y: 0} + m.offset = model.Point{X: 0, Y: 0} + m.dialogMsg = fmt.Sprintf("Loaded %s", msg.data.Filename) + m.mode = model.ModeNormal + m.dialog = model.DialogNone + m.dirty = false + m.undoPosAtSave = m.undo.Pos() + return m, nil + + case tea.MouseMsg: + return m.handleMouse(msg) + + case tea.KeyMsg: + return m.handleKey(msg) + } + + if m.mode == model.ModeDialog || m.mode == model.ModeTextEdit { + var cmd tea.Cmd + m.ti, cmd = m.ti.Update(msg) + if cmd != nil { + return m, cmd + } + m.textInput, cmd = m.textInput.Update(msg) + if cmd != nil { + return m, cmd + } + } + return m, nil +} + +func (m *AppModel) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) { + x, y := msg.X, msg.Y + + if m.colorPicker != nil && m.colorPicker.Active { + return m.handleColorPickerMouse(msg) + } + + isFilePicker := m.dialog == model.DialogFileSave || m.dialog == model.DialogFileOpen || m.dialog == model.DialogSaveAs || m.dialog == model.DialogOpenMap + if isFilePicker && m.filePicker != nil && m.filePicker.PopupY > 0 { + return m.handleFilePickerMouse(msg) + } + + isPress := msg.Button == tea.MouseButtonLeft && msg.Action == tea.MouseActionPress + isRelease := msg.Action == tea.MouseActionRelease + isRightPress := msg.Button == tea.MouseButtonRight && msg.Action == tea.MouseActionPress + + gridAreaW := m.width - sidebarW + gridH := m.height - 1 + if m.height > 6 { + gridH -= 2 + } + if gridH < 1 { + gridH = 1 + } + if gridAreaW < 1 { + gridAreaW = 1 + } + + if isPress && y == 0 { + m.handleToolbarClick(x) + if m.quitting { + return m, tea.Quit + } + return m, nil + } + + gridY := y - 1 + onSidebar := gridAreaW > 0 && x >= gridAreaW + + if isPress && onSidebar && gridY >= 0 { + m.handleSidebarClick(x, y) + return m, nil + } + + gx := x + m.offset.X + gy := gridY + m.offset.Y + inGrid := gridY >= 0 && gridY < gridH && x < gridAreaW + + if isPress { + m.mouseDown = true + m.lastPaint = model.Point{X: -1, Y: -1} + if inGrid { + p := model.Point{X: gx, Y: gy} + switch m.tool { + case model.ToolLine: + m.mouseStart = p + m.mode = model.ModeLinePreview + m.linePreview = nil + case model.ToolRect: + m.mouseStart = p + m.mode = model.ModeRectPreview + m.rectPreview = nil + case model.ToolCircle: + m.mouseStart = p + m.mode = model.ModeCirclePreview + m.circlePreview = nil + case model.ToolSelect: + m.cursor = p + m.clampCursor() + default: + return m.mouseDraw(p.X, p.Y) + } + } + return m, nil + } + + if isRelease { + if m.mouseDown { + m.mouseRelease(gx, gy) + } + m.mouseDown = false + m.lastPaint = model.Point{X: -1, Y: -1} + return m, nil + } + + if isRightPress { + if inGrid { + if m.tool == model.ToolText { + idx := tools.FindTextLabelAt(m.curMap(), model.Point{X: gx, Y: gy}) + if idx >= 0 { + m.dirty = true + m.undo.Push(m.curMap()) + tools.RemoveTextLabel(m.curMap(), m.curMap().TextLabels[idx].Start) + m.movingLabel = -1 + } + } else { + m.dirty = true + m.undo.Push(m.curMap()) + m.applyBrush(model.Point{X: gx, Y: gy}, true) + m.lastPaint = model.Point{X: gx, Y: gy} + } + } + return m, nil + } + + if m.mouseDown && inGrid && (gx != m.lastPaint.X || gy != m.lastPaint.Y) { + if m.movingLabel >= 0 && m.tool == model.ToolText { + m.cursor = model.Point{X: gx, Y: gy} + m.clampCursor() + } else if m.tool == model.ToolLine { + m.linePreview = tools.ThickenPoints(tools.BresenhamLine(m.mouseStart, model.Point{X: gx, Y: gy}), m.brushWidth) + m.lastPaint = model.Point{X: gx, Y: gy} + } else if m.tool == model.ToolRect { + m.rectPreview = tools.ThickenPoints(tools.DrawRect(m.mouseStart, model.Point{X: gx, Y: gy}, m.fillShapes), m.brushWidth) + m.lastPaint = model.Point{X: gx, Y: gy} + } else if m.tool == model.ToolCircle { + m.circlePreview = tools.ThickenPoints(tools.DrawCircle(m.mouseStart, model.Point{X: gx, Y: gy}, m.fillShapes), m.brushWidth) + m.lastPaint = model.Point{X: gx, Y: gy} + } else if m.tool != model.ToolText { + m.mouseDraw(gx, gy) + } + } + + switch msg.Button { + case tea.MouseButtonWheelUp: + if m.offset.Y > 0 { + m.offset.Y-- + } + case tea.MouseButtonWheelDown: + maxY := m.curMap().Height - gridH + if maxY < 0 { + maxY = 0 + } + if m.offset.Y < maxY { + m.offset.Y++ + } + } + + return m, nil +} + +func (m *AppModel) mouseDraw(gx, gy int) (tea.Model, tea.Cmd) { + p := model.Point{X: gx, Y: gy} + switch m.tool { + case model.ToolBrush: + m.dirty = true + m.undo.Push(m.curMap()) + m.applyBrush(p) + m.lastPaint = p + case model.ToolErase: + m.dirty = true + m.undo.Push(m.curMap()) + m.applyBrush(p, true) + m.lastPaint = p + case model.ToolFill: + m.dirty = true + m.undo.Push(m.curMap()) + m.fillAt(p) + m.lastPaint = p + case model.ToolText: + idx := tools.FindTextLabelAt(m.curMap(), p) + if idx >= 0 { + m.movingLabel = idx + tl := m.curMap().TextLabels[idx] + m.dragLabelOrigin = tl.Start + m.dragMouseOrigin = p + m.lastPaint = p + } + } + m.cursor = p + m.clampCursor() + return m, nil +} + +func (m *AppModel) mouseRelease(gx, gy int) { + p := model.Point{X: gx, Y: gy} + switch m.tool { + case model.ToolLine: + m.dirty = true + m.undo.Push(m.curMap()) + m.finishShape(m.mouseStart, p, model.ToolLine) + m.linePreview = nil + m.mode = model.ModeNormal + case model.ToolRect: + m.dirty = true + m.undo.Push(m.curMap()) + m.finishShape(m.mouseStart, p, model.ToolRect) + m.rectPreview = nil + m.mode = model.ModeNormal + case model.ToolCircle: + m.dirty = true + m.undo.Push(m.curMap()) + m.finishShape(m.mouseStart, p, model.ToolCircle) + m.circlePreview = nil + m.mode = model.ModeNormal + case model.ToolText: + if m.movingLabel >= 0 && m.movingLabel < len(m.curMap().TextLabels) { + old := m.curMap().TextLabels[m.movingLabel].Start + newPos := m.labelDragPos() + if old != newPos && m.curMap().InBounds(newPos) { + m.dirty = true + m.undo.Push(m.curMap()) + tools.MoveTextLabel(m.curMap(), old, newPos) + m.movingLabel = -1 + } else { + m.startTextEditText(m.movingLabel) + } + } else if m.cursor == p && m.curMap().InBounds(p) { + m.startTextEdit(p) + } + m.dragLabelOrigin = model.Point{X: 0, Y: 0} + m.dragMouseOrigin = model.Point{X: 0, Y: 0} + } +} + +func (m *AppModel) finishShape(a, b model.Point, tool model.Tool) { + palette := m.curPalette() + terrain := m.selected + if terrain < 0 || terrain >= len(palette) { + return + } + var pts []model.Point + switch tool { + case model.ToolLine: + pts = tools.BresenhamLine(a, b) + case model.ToolRect: + pts = tools.DrawRect(a, b, m.fillShapes) + case model.ToolCircle: + pts = tools.DrawCircle(a, b, m.fillShapes) + } + tools.ApplyPoints(m.curMap(), pts, terrain, palette) + if m.brushWidth > 1 { + for _, pt := range pts { + tools.Brush(m.curMap(), pt, terrain, m.brushWidth, palette) + } + } +} -- cgit v1.2.3