note blocks and sticky test
This commit is contained in:
parent
1bc1cdb11f
commit
d829b4972f
11 changed files with 3389 additions and 118 deletions
|
|
@ -1,6 +1,7 @@
|
|||
require "level_editor/object_attributes"
|
||||
require "level_editor/gui"
|
||||
require "level_editor/layout"
|
||||
require "level_editor/note_editor"
|
||||
|
||||
editorView = "room" --can be "off", "room", "world", "asset"
|
||||
selectedColor = "white"
|
||||
|
|
@ -10,6 +11,7 @@ local folderSprite = love.graphics.newImage("art/_ui/folder.png")
|
|||
local cancelSprite = love.graphics.newImage("art/_ui/cancel.png")
|
||||
local confirmSprite = love.graphics.newImage("art/_ui/confirm.png")
|
||||
local clearSavesSprite = love.graphics.newImage("art/_ui/clear_saves.png")
|
||||
local notepadSprite = love.graphics.newImage("art/_debug/notepad.png")
|
||||
|
||||
local selectedFolder = nil
|
||||
local selectedAsset = nil
|
||||
|
|
@ -53,6 +55,12 @@ local boxPaletteOpen = false
|
|||
local boxPaletteScroll = { scroll = 0 }
|
||||
local boxPaletteSections = {}
|
||||
|
||||
-- Note authoring state. `noteMode` is nil normally, "create" while placing a new
|
||||
-- note (click a cell to drop it and finish), or "edit" while retyping an
|
||||
-- existing note (with Save / Discard). `editingNote` is the note being edited.
|
||||
local noteMode = nil
|
||||
local editingNote = nil
|
||||
|
||||
-- Convert a pointer position into a valid room cell. Keeping brush state in
|
||||
-- cell coordinates means a click that jitters within a tile cannot place or
|
||||
-- erase that tile more than once.
|
||||
|
|
@ -241,6 +249,19 @@ local function roomSections()
|
|||
return { search = search, listRect = list, status = status, controls = controls }
|
||||
end
|
||||
|
||||
-- Save / Discard buttons for note edit mode, laid out in the status strip. Runs
|
||||
-- for both drawing and hit-testing so the two never disagree.
|
||||
local function noteEditButtonRects(s)
|
||||
local gap = 8
|
||||
local h = math.min(28, s.status.h)
|
||||
local w = (s.status.w - gap) / 2
|
||||
local y = s.status.y + (s.status.h - h) / 2
|
||||
return {
|
||||
save = { x = s.status.x, y = y, w = w, h = h },
|
||||
discard = { x = s.status.x + w + gap, y = y, w = w, h = h },
|
||||
}
|
||||
end
|
||||
|
||||
-- Position the bottom control cluster inside its section: level-name field, a
|
||||
-- row of colour buttons, then a row of action buttons. Runs for both drawing
|
||||
-- and hit-testing so the two never disagree.
|
||||
|
|
@ -297,7 +318,7 @@ local function layoutRoomControls(c, s)
|
|||
|
||||
placeRow({ "white", "red", "green", "blue" }, 22)
|
||||
placeRow({ "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box" }, 22)
|
||||
placeRow({ "save", "clearSaves", "world", "wire" }, 28)
|
||||
placeRow({ "save", "clearSaves", "world", "wire", "note" }, 28)
|
||||
end
|
||||
|
||||
-- Build the full room-view layout (sections + a scroll list + a back/search
|
||||
|
|
@ -723,6 +744,16 @@ function _initLevelEditor()
|
|||
y = 0,
|
||||
}, "wire")
|
||||
|
||||
_addUIButton(createUIElement{
|
||||
name = "note",
|
||||
sprite = notepadSprite,
|
||||
onClic = { left = function()
|
||||
if editorView == "room" then beginNoteCreate() end
|
||||
end },
|
||||
x = 0,
|
||||
y = 0,
|
||||
}, "note")
|
||||
|
||||
roomNameField = createUIElement{
|
||||
name = "roomName",
|
||||
text = editorRoomName,
|
||||
|
|
@ -783,9 +814,50 @@ end
|
|||
function selectAsset(asset)
|
||||
print("selecting", asset)
|
||||
selectedAsset = asset
|
||||
exitNoteMode() -- picking any real asset closes the note text field
|
||||
print(selectedAsset)
|
||||
end
|
||||
|
||||
-- Close the note text box and return to whatever the sidebar was showing before.
|
||||
function exitNoteMode()
|
||||
noteMode = nil
|
||||
editingNote = nil
|
||||
noteEditorBlur()
|
||||
end
|
||||
|
||||
-- Start authoring a new note: focus a blank text field. The author types, then
|
||||
-- clicks a grid cell to drop the note there (that placement is the "done").
|
||||
function beginNoteCreate()
|
||||
if editorView ~= "room" then return end
|
||||
boxPaletteOpen = false
|
||||
noteMode = "create"
|
||||
editingNote = nil
|
||||
noteEditorSetText("")
|
||||
noteEditorFocus()
|
||||
end
|
||||
|
||||
-- Left-clicking an existing note reopens it for editing, with Save / Discard.
|
||||
function beginNoteEdit(note)
|
||||
noteMode = "edit"
|
||||
editingNote = note
|
||||
noteEditorSetText(note.text)
|
||||
noteEditorFocus()
|
||||
end
|
||||
|
||||
function saveNoteEdit()
|
||||
if editingNote then editingNote.text = noteEditorText() end
|
||||
exitNoteMode()
|
||||
end
|
||||
|
||||
function discardNoteEdit()
|
||||
exitNoteMode()
|
||||
end
|
||||
|
||||
-- Keyboard Escape while the field is focused: discard the edit / cancel placing.
|
||||
function cancelNoteMode()
|
||||
exitNoteMode()
|
||||
end
|
||||
|
||||
function selectQuickAsset(asset)
|
||||
selectAsset(asset)
|
||||
boxPaletteOpen = asset == "box"
|
||||
|
|
@ -922,6 +994,7 @@ function wireView()
|
|||
editorStatus = "Place this room in the world before drawing cross-room wires."
|
||||
return
|
||||
end
|
||||
exitNoteMode()
|
||||
editorView = "wire"
|
||||
activeField = nil
|
||||
roomBrush = nil
|
||||
|
|
@ -935,6 +1008,7 @@ function wireView()
|
|||
end
|
||||
|
||||
function worldView()
|
||||
exitNoteMode()
|
||||
editorView = "world"
|
||||
wireBrush = nil
|
||||
activeField = nil
|
||||
|
|
@ -1169,6 +1243,22 @@ function editorMouseHandler(x, y, button)
|
|||
return
|
||||
end
|
||||
|
||||
-- While authoring a note, the field takes over the list area: a click
|
||||
-- there positions the caret / starts a selection (its rect is set each
|
||||
-- frame by noteEditorDraw). In edit mode, Save / Discard live in the
|
||||
-- status strip.
|
||||
if noteMode then
|
||||
if noteMode == "edit" and button == 1 then
|
||||
local nb = noteEditButtonRects(s)
|
||||
if Layout.hit(nb.save, x, y) then saveNoteEdit(); return end
|
||||
if Layout.hit(nb.discard, x, y) then discardNoteEdit(); return end
|
||||
end
|
||||
if noteEditorMousepressed(x, y) then return end
|
||||
-- The field covers the list area; swallow other clicks there so the
|
||||
-- hidden asset list underneath can't be selected by accident.
|
||||
if Layout.hit(s.listRect, x, y) then return end
|
||||
end
|
||||
|
||||
if s.boxPicker then
|
||||
local asset = s.boxPicker.at(x, y)
|
||||
if asset then
|
||||
|
|
@ -1185,7 +1275,7 @@ function editorMouseHandler(x, y, button)
|
|||
end
|
||||
|
||||
-- control cluster (only the buttons that live there)
|
||||
for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire" }) do
|
||||
for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire", "note" }) do
|
||||
local b = uiButtons[name]
|
||||
if b and b:clicIsInBox(x, y) then
|
||||
b:onClic(button)
|
||||
|
|
@ -1232,6 +1322,20 @@ function editorMouseHandler(x, y, button)
|
|||
else
|
||||
-- click landed on the grid (game area): place the selected asset
|
||||
if editorView == "room" then
|
||||
local cell = button == 1 and roomCellAt(x, y)
|
||||
-- Placing a new note: drop it here carrying the typed text, then finish
|
||||
-- (the drop is the "done"), returning the sidebar to its prior view.
|
||||
if cell and noteMode == "create" then
|
||||
currentRoom:attemptPlaceAtCell("note", nil, cell, selectedColor, noteEditorText())
|
||||
exitNoteMode()
|
||||
return
|
||||
end
|
||||
-- Left-clicking an existing note opens it for editing instead of
|
||||
-- painting over it.
|
||||
if cell then
|
||||
local note = currentRoom:noteAtCell(cell)
|
||||
if note then beginNoteEdit(note); return end
|
||||
end
|
||||
beginRoomBrush(x, y, button)
|
||||
elseif editorView == "wire" then
|
||||
beginWireBrush(x, y, button)
|
||||
|
|
@ -1257,6 +1361,11 @@ function editorMouseHandler(x, y, button)
|
|||
end
|
||||
|
||||
function editorMouseMoved(x, y)
|
||||
if noteEditorActive() and x >= sideBarBox.x then
|
||||
local vx = (x - sideBarBox.x) * sb.w / sideBarBox.w
|
||||
local vy = y * sb.h / sideBarBox.h
|
||||
if noteEditorMousemoved(vx, vy) then return end
|
||||
end
|
||||
if wireBrush then
|
||||
if editorView ~= "wire" then wireBrush = nil; return end
|
||||
local cell = wireCellAt(x, y)
|
||||
|
|
@ -1286,6 +1395,7 @@ function editorMouseMoved(x, y)
|
|||
end
|
||||
|
||||
function editorMouseReleased(_, _, button)
|
||||
noteEditorMousereleased()
|
||||
if roomBrush and roomBrush.button == button then roomBrush = nil end
|
||||
if wireBrush and wireBrush.button == button then wireBrush = nil end
|
||||
end
|
||||
|
|
@ -1355,6 +1465,36 @@ function createUIElement(t)
|
|||
return GUI:new(t)
|
||||
end
|
||||
|
||||
-- Hover readout for note blocks, drawn in screen space over the play area (not
|
||||
-- the sidebar). Mirrors World:drawHoveredRoomName's pointer-anchored tooltip so
|
||||
-- it matches the overworld hover style. Only meaningful while notes are shown.
|
||||
function editorDrawNoteHover()
|
||||
if not (DEBUG and NOTES_VISIBLE) then return end
|
||||
if editorView ~= "room" then return end
|
||||
local mouseX, mouseY = love.mouse.getPosition()
|
||||
if mouseX < 0 or mouseX >= width or mouseY < 0 or mouseY >= height then return end
|
||||
|
||||
local note = currentRoom:noteAtCell(currentRoom:getCellFromMousePos(mouseX, mouseY))
|
||||
if not note or note.text == "" then return end
|
||||
|
||||
local pad = 6
|
||||
local maxW = math.min(280, width - 8)
|
||||
local _, wrapped = font:getWrap(note.text, maxW - pad * 2)
|
||||
local textW = 0
|
||||
for _, line in ipairs(wrapped) do textW = math.max(textW, font:getWidth(line)) end
|
||||
local tooltipW = math.min(textW + pad * 2, maxW)
|
||||
local tooltipH = #wrapped * font:getHeight() + pad * 2
|
||||
|
||||
local tooltipX = math.max(4, math.min(mouseX + 14, width - tooltipW - 4))
|
||||
local tooltipY = math.max(4, math.min(mouseY + 14, height - tooltipH - 4))
|
||||
|
||||
love.graphics.setColor(0, 0, 0, 0.9)
|
||||
love.graphics.rectangle("fill", tooltipX, tooltipY, tooltipW, tooltipH)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.rectangle("line", tooltipX, tooltipY, tooltipW, tooltipH)
|
||||
love.graphics.printf(note.text, tooltipX + pad, tooltipY + pad, tooltipW - pad * 2, "left")
|
||||
end
|
||||
|
||||
function editorDraw()
|
||||
if editorView == "room" then
|
||||
local s = buildRoomLayout()
|
||||
|
|
@ -1373,7 +1513,17 @@ function editorDraw()
|
|||
love.graphics.rectangle("line", s.searchBox.x, s.searchBox.y, s.searchBox.w, s.searchBox.h)
|
||||
love.graphics.printf(boxPaletteOpen and "QUICK PALETTE" or "SEARCH: " .. assetSearchText, s.searchBox.x + 6, s.searchBox.y + 9, s.searchBox.w - 12, "left")
|
||||
|
||||
if s.boxPicker then
|
||||
if noteMode then
|
||||
-- Authoring a note: the text field replaces the asset list. In create
|
||||
-- mode you click a grid cell to drop it; in edit mode Save / Discard sit
|
||||
-- in the status strip below.
|
||||
local labelH = 18
|
||||
local hint = noteMode == "edit" and "EDIT NOTE" or "NEW NOTE (click grid to place)"
|
||||
love.graphics.setColor(gColor.white:set())
|
||||
love.graphics.printf(hint, s.listRect.x, s.listRect.y, s.listRect.w, "left")
|
||||
noteEditorSetRect(s.listRect.x, s.listRect.y + labelH, s.listRect.w, s.listRect.h - labelH)
|
||||
noteEditorDraw()
|
||||
elseif s.boxPicker then
|
||||
drawBoxPicker(s.boxPicker)
|
||||
else
|
||||
-- Scrolling list, clipped to its section so nothing bleeds into others.
|
||||
|
|
@ -1397,7 +1547,7 @@ function editorDraw()
|
|||
-- control cluster
|
||||
love.graphics.setColor(gColor.white:set())
|
||||
love.graphics.printf("LEVEL NAME", s.controls.x, s.labelY, s.controls.w, "center")
|
||||
for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire" }) do
|
||||
for _, name in ipairs({ "roomName", "white", "red", "green", "blue", "quick_player", "quick_static_wall", "quick_switch", "quick_door", "quick_wired_switch", "quick_wired_door", "quick_box", "save", "clearSaves", "world", "wire", "note" }) do
|
||||
local button = uiButtons[name]
|
||||
if button then
|
||||
button:draw()
|
||||
|
|
@ -1406,7 +1556,7 @@ function editorDraw()
|
|||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.print("W", button.x + button.w - 7, button.y - 4)
|
||||
end
|
||||
if asset and selectedAsset == asset then
|
||||
if (asset and selectedAsset == asset) or (name == "note" and noteMode ~= nil) then
|
||||
love.graphics.setColor(1, 0.85, 0.2, 1)
|
||||
love.graphics.setLineWidth(2)
|
||||
love.graphics.rectangle("line", button.x - 1, button.y - 1, button.w + 2, button.h + 2)
|
||||
|
|
@ -1414,8 +1564,17 @@ function editorDraw()
|
|||
end
|
||||
end
|
||||
|
||||
-- status strip between the list and the controls
|
||||
if editorStatus then
|
||||
-- status strip between the list and the controls: Save / Discard while
|
||||
-- editing an existing note, otherwise the ordinary status line.
|
||||
if noteMode == "edit" then
|
||||
local nb = noteEditButtonRects(s)
|
||||
love.graphics.setColor(gColor.white:set())
|
||||
for label, r in pairs({ SAVE = nb.save, DISCARD = nb.discard }) do
|
||||
love.graphics.setLineWidth(2)
|
||||
love.graphics.rectangle("line", r.x, r.y, r.w, r.h)
|
||||
love.graphics.printf(label, r.x, r.y + (r.h - font:getHeight()) / 2, r.w, "center")
|
||||
end
|
||||
elseif editorStatus then
|
||||
Layout.clip(s.status)
|
||||
love.graphics.setColor(gColor.white:set())
|
||||
love.graphics.printf(editorStatus, s.status.x, s.status.y + 4, s.status.w, "center")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue