1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-23 05:58:14 +00:00

Move paint/edit menu bar into common module

I want to add a menu bar to the edit runner too, so let's make this code
a little more reusable first.
This commit is contained in:
Jonathan Coates 2025-06-25 22:13:50 +01:00
parent 531eacfac7
commit 341d1c7bc2
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
3 changed files with 216 additions and 241 deletions

View File

@ -0,0 +1,125 @@
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A simple menu bar.
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
This provides a shared implementation of the menu bar used by the `edit` and
`paint` programs. This draws a menu bar at the bottom of the string, with a list
of options.
@local
]]
local expect = require "cc.expect".expect
--[[- Create a new menu bar.
This should be called every time the menu is displayed.
@tparam { string... } items The menu items to display.
@return The menu.
]]
local function create(items)
expect(1, items, "table")
return {
items = items,
selected = 1,
}
end
--[[- Draw the menu bar at the bottom of the screen.
This should be called when first displaying the menu, and if the whole screen is
redrawn (e.g. after a [`term_resize`]).
@param menu The menu bar to draw.
]]
local function draw(menu)
expect(1, menu, "table")
local _, height = term.getSize()
term.setCursorPos(1, height)
term.clearLine()
local active_colour = term.isColour() and colours.yellow or colours.white
term.setTextColour(colours.white)
for k, v in pairs(menu.items) do
if menu.selected == k then
term.setTextColour(active_colour)
term.write("[")
term.setTextColour(colours.white)
term.write(v)
term.setTextColour(active_colour)
term.write("]")
term.setTextColour(colours.white)
else
term.write(" " .. v .. " ")
end
end
end
--[[- Process an event.
@param menu The menu bar to update.
@tparam string The event name.
@param ... Additional arguments to the event.
@treturn nil|boolean|string Either:
- If no action was taken, return `nil`.
- If the menu was closed, return `false`.
- If an item was selected, return the item as a string.
]]
local function handle_event(menu, event, ...)
expect(1, menu, "table")
if event == "key" then
local key = ...
if key == keys.right then
-- Move right
menu.selected = menu.selected + 1
if menu.selected > #menu.items then menu.selected = 1 end
draw(menu)
elseif key == keys.left and menu.selected > 1 then
-- Move left
menu.selected = menu.selected - 1
if menu.selected < 1 then menu.selected = #menu.items end
draw(menu)
elseif key == keys.enter or key == keys.numPadEnter then
-- Select an option
return menu.items[menu.selected]
elseif key == keys.leftCtrl or keys == keys.rightCtrl or keys == keys.rightAlt then
-- Cancel the menu
return false
end
elseif event == "char" then
-- Select menu items
local char = (...):lower()
for _, item in pairs(menu.items) do
if item:sub(1, 1):lower() == char then return item end
end
elseif event == "mouse_click" then
local _, x, y = ...
local _, height = term.getSize()
if y ~= height then return false end -- Exit the menu
local item_start = 1
for _, item in ipairs(menu.items) do
local item_end = item_start + #item + 2
if x >= item_start and x < item_end then return item end
item_start = item_end
end
end
return nil
end
return { create = create, draw = draw, handle_event = handle_event }

View File

@ -54,19 +54,19 @@ else
end
-- Menus
local bMenu = false
local nMenuItem = 1
local tMenuItems = {}
local menu = require "cc.internal.menu"
local current_menu
local menu_items = {}
if not bReadOnly then
table.insert(tMenuItems, "Save")
table.insert(menu_items, "Save")
end
if shell.openTab then
table.insert(tMenuItems, "Run")
table.insert(menu_items, "Run")
end
if peripheral.find("printer") then
table.insert(tMenuItems, "Print")
table.insert(menu_items, "Print")
end
table.insert(tMenuItems, "Exit")
table.insert(menu_items, "Exit")
local status_ok, status_text
local function set_status(text, ok)
@ -214,7 +214,7 @@ end
local function recomplete()
local sLine = tLines[y]
if not bMenu and not bReadOnly and x == #sLine + 1 then
if not bReadOnly and x == #sLine + 1 then
tCompletions = complete(sLine)
if tCompletions and #tCompletions > 0 then
nCompletion = 1
@ -281,22 +281,9 @@ local function redrawMenu()
term.write(y)
term.setCursorPos(1, h)
if bMenu then
if current_menu then
-- Draw menu
term.setTextColour(textColour)
for nItem, sItem in pairs(tMenuItems) do
if nItem == nMenuItem then
term.setTextColour(highlightColour)
term.write("[")
term.setTextColour(textColour)
term.write(sItem)
term.setTextColour(highlightColour)
term.write("]")
term.setTextColour(textColour)
else
term.write(" " .. sItem .. " ")
end
end
menu.draw(current_menu)
else
-- Draw status
term.setTextColour(status_ok and highlightColour or errorColour)
@ -306,6 +293,7 @@ local function redrawMenu()
-- Reset cursor
term.setCursorPos(x - scrollX, y - scrollY)
term.setCursorBlink(not current_menu)
end
local tMenuFuncs = {
@ -383,7 +371,8 @@ local tMenuFuncs = {
end
end
bMenu = false
local old_menu = current_menu
current_menu = nil
term.redirect(printerTerminal)
local ok, error = pcall(function()
term.scroll()
@ -401,7 +390,7 @@ local tMenuFuncs = {
redrawMenu()
sleep(0.5)
end
bMenu = true
current_menu = old_menu
if nPage > 1 then
set_status("Printed " .. nPage .. " Pages")
@ -442,15 +431,6 @@ local tMenuFuncs = {
end,
}
local function doMenuItem(_n)
tMenuFuncs[tMenuItems[_n]]()
if bMenu then
bMenu = false
term.setCursorBlink(true)
end
redrawMenu()
end
local function setCursor(newX, newY)
local _, oldY = x, y
x, y = newX, newY
@ -513,13 +493,29 @@ local function acceptCompletion()
end
end
local function handleMenuEvent(event)
assert(current_menu)
local result = menu.handle_event(current_menu, table.unpack(event, 1, event.n))
if result == false then
current_menu = nil
redrawMenu()
elseif result ~= nil then
tMenuFuncs[result]()
current_menu = nil
redrawMenu()
end
end
-- Handle input
while bRunning do
local sEvent, param, param2, param3 = os.pullEvent()
if sEvent == "key" then
if param == keys.up then
-- Up
if not bMenu then
local event = table.pack(os.pullEvent())
if event[1] == "key" then
if current_menu then
handleMenuEvent(event)
else
local key = event[2]
if key == keys.up then
if nCompletion then
-- Cycle completions
nCompletion = nCompletion - 1
@ -535,12 +531,8 @@ while bRunning do
y - 1
)
end
end
elseif param == keys.down then
-- Down
if not bMenu then
-- Move cursor down
elseif key == keys.down then
if nCompletion then
-- Cycle completions
nCompletion = nCompletion + 1
@ -556,11 +548,8 @@ while bRunning do
y + 1
)
end
end
elseif param == keys.tab then
-- Tab
if not bMenu and not bReadOnly then
elseif key == keys.tab and not bReadOnly then
if nCompletion and x == #tLines[y] + 1 then
-- Accept autocomplete
acceptCompletion()
@ -570,11 +559,8 @@ while bRunning do
tLines[y] = string.sub(sLine, 1, x - 1) .. " " .. string.sub(sLine, x)
setCursor(x + 4, y)
end
end
elseif param == keys.pageUp then
-- Page Up
if not bMenu then
elseif key == keys.pageUp then
-- Move up a page
local newY
if y - (h - 1) >= 1 then
@ -586,11 +572,7 @@ while bRunning do
math.min(x, #tLines[newY] + 1),
newY
)
end
elseif param == keys.pageDown then
-- Page Down
if not bMenu then
elseif key == keys.pageDown then
-- Move down a page
local newY
if y + (h - 1) <= #tLines then
@ -600,48 +582,29 @@ while bRunning do
end
local newX = math.min(x, #tLines[newY] + 1)
setCursor(newX, newY)
end
elseif param == keys.home then
-- Home
if not bMenu then
elseif key == keys.home then
-- Move cursor to the beginning
if x > 1 then
setCursor(1, y)
end
end
elseif param == keys["end"] then
-- End
if not bMenu then
elseif key == keys["end"] then
-- Move cursor to the end
local nLimit = #tLines[y] + 1
if x < nLimit then
setCursor(nLimit, y)
end
end
elseif param == keys.left then
-- Left
if not bMenu then
elseif key == keys.left then
if x > 1 then
-- Move cursor left
setCursor(x - 1, y)
elseif x == 1 and y > 1 then
setCursor(#tLines[y - 1] + 1, y - 1)
end
else
-- Move menu left
nMenuItem = nMenuItem - 1
if nMenuItem < 1 then
nMenuItem = #tMenuItems
end
redrawMenu()
end
elseif param == keys.right then
-- Right
if not bMenu then
elseif key == keys.right then
local nLimit = #tLines[y] + 1
if x < nLimit then
-- Move cursor right
@ -653,18 +616,8 @@ while bRunning do
-- Go to next line
setCursor(1, y + 1)
end
else
-- Move menu right
nMenuItem = nMenuItem + 1
if nMenuItem > #tMenuItems then
nMenuItem = 1
end
redrawMenu()
end
elseif param == keys.delete then
-- Delete
if not bMenu and not bReadOnly then
elseif key == keys.delete and not bReadOnly then
local nLimit = #tLines[y] + 1
if x < nLimit then
local sLine = tLines[y]
@ -677,11 +630,8 @@ while bRunning do
recomplete()
redrawText()
end
end
elseif param == keys.backspace then
-- Backspace
if not bMenu and not bReadOnly then
elseif key == keys.backspace and not bReadOnly then
if x > 1 then
-- Remove character
local sLine = tLines[y]
@ -700,11 +650,8 @@ while bRunning do
setCursor(sPrevLen + 1, y - 1)
redrawText()
end
end
elseif param == keys.enter or param == keys.numPadEnter then
-- Enter/Numpad Enter
if not bMenu and not bReadOnly then
elseif (key == keys.enter or key == keys.numPadEnter) and not bReadOnly then
-- Newline
local sLine = tLines[y]
local _, spaces = string.find(sLine, "^[ ]+")
@ -716,96 +663,56 @@ while bRunning do
setCursor(spaces + 1, y + 1)
redrawText()
elseif bMenu then
-- Menu selection
doMenuItem(nMenuItem)
end
elseif param == keys.leftCtrl or param == keys.rightCtrl then
-- Menu toggle
bMenu = not bMenu
if bMenu then
term.setCursorBlink(false)
else
term.setCursorBlink(true)
end
redrawMenu()
elseif param == keys.rightAlt then
if bMenu then
bMenu = false
term.setCursorBlink(true)
elseif key == keys.leftCtrl or key == keys.rightCtrl then
current_menu = menu.create(menu_items)
redrawMenu()
end
end
elseif sEvent == "char" then
if not bMenu and not bReadOnly then
elseif event[1] == "char" then
if current_menu then
handleMenuEvent(event)
elseif not bReadOnly then
-- Input text
local sLine = tLines[y]
tLines[y] = string.sub(sLine, 1, x - 1) .. param .. string.sub(sLine, x)
tLines[y] = string.sub(sLine, 1, x - 1) .. event[2] .. string.sub(sLine, x)
setCursor(x + 1, y)
elseif bMenu then
-- Select menu items
for n, sMenuItem in ipairs(tMenuItems) do
if string.lower(string.sub(sMenuItem, 1, 1)) == string.lower(param) then
doMenuItem(n)
break
end
end
end
elseif sEvent == "paste" then
if not bReadOnly then
-- Close menu if open
if bMenu then
bMenu = false
term.setCursorBlink(true)
redrawMenu()
end
-- Input text
local sLine = tLines[y]
tLines[y] = string.sub(sLine, 1, x - 1) .. param .. string.sub(sLine, x)
setCursor(x + #param, y)
elseif event[1] == "paste" and not bReadOnly then
-- Close menu if open
if current_menu then
current_menu = nil
redrawMenu()
end
elseif sEvent == "mouse_click" then
local cx, cy = param2, param3
if not bMenu then
if param == 1 then
-- Input text
local text = event[2]
local sLine = tLines[y]
tLines[y] = string.sub(sLine, 1, x - 1) .. text .. string.sub(sLine, x)
setCursor(x + #text, y)
elseif event[1] == "mouse_click" then
local button, cx, cy = event[2], event[3], event[4]
if current_menu then
handleMenuEvent(event)
else
if button == 1 then
-- Left click
if cy < h then
local newY = math.min(math.max(scrollY + cy, 1), #tLines)
local newX = math.min(math.max(scrollX + cx, 1), #tLines[newY] + 1)
setCursor(newX, newY)
else
bMenu = true
current_menu = menu.create(menu_items)
redrawMenu()
end
end
else
if cy == h then
local nMenuPosEnd = 1
local nMenuPosStart = 1
for n, sMenuItem in ipairs(tMenuItems) do
nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
if cx > nMenuPosStart and cx < nMenuPosEnd then
doMenuItem(n)
end
nMenuPosEnd = nMenuPosEnd + 1
nMenuPosStart = nMenuPosEnd
end
else
bMenu = false
term.setCursorBlink(true)
redrawMenu()
end
end
elseif sEvent == "mouse_scroll" then
if not bMenu then
if param == -1 then
elseif event[1] == "mouse_scroll" then
if not current_menu then
local direction = event[2]
if direction == -1 then
-- Scroll up
if scrollY > 0 then
-- Move cursor up
@ -813,7 +720,7 @@ while bRunning do
redrawText()
end
elseif param == 1 then
elseif direction == 1 then
-- Scroll down
local nMaxScroll = #tLines - (h - 1)
if scrollY < nMaxScroll then
@ -825,7 +732,7 @@ while bRunning do
end
end
elseif sEvent == "term_resize" then
elseif event[1] == "term_resize" then
w, h = term.getSize()
setCursor(x, y)
redrawMenu()

View File

@ -20,6 +20,7 @@ local canvasColour = colours.black
local canvas = {}
-- The menu options
local menu = require "cc.internal.menu"
local mChoices = { "Save", "Exit" }
-- The message displayed in the footer bar
@ -299,10 +300,7 @@ local menu_choices = {
end
return false
end,
Exit = function()
require "cc.internal.event".discard_char() -- Consume stray "char" events from pressing Ctrl then E separately.
return true
end,
Exit = function() return true end,
}
--[[
@ -310,80 +308,25 @@ local menu_choices = {
returns: true if the program is to be exited; false otherwise
]]
local function accessMenu()
-- Selected menu option
local selection = 1
local current_menu = menu.create(mChoices)
term.setBackgroundColour(colours.black)
menu.draw(current_menu)
while true do
-- Draw the menu
term.setCursorPos(1, h)
term.clearLine()
term.setTextColour(colours.white)
for k, v in pairs(mChoices) do
if selection == k then
term.setTextColour(colours.yellow)
term.write("[")
term.setTextColour(colours.white)
term.write(v)
term.setTextColour(colours.yellow)
term.write("]")
term.setTextColour(colours.white)
else
term.write(" " .. v .. " ")
end
-- Handle input in the menu
local event = table.pack(os.pullEvent())
local result = menu.handle_event(current_menu, table.unpack(event, 1, event.n))
if result == false then
return false
elseif result ~= nil then
return menu_choices[result]()
end
-- Handle input in the menu
local id, param1, param2, param3 = os.pullEvent()
if id == "key" then
local key = param1
-- Handle menu shortcuts.
for _, menu_item in ipairs(mChoices) do
local k = keys[menu_item:sub(1, 1):lower()]
if k and k == key then
return menu_choices[menu_item]()
end
end
if key == keys.right then
-- Move right
selection = selection + 1
if selection > #mChoices then
selection = 1
end
elseif key == keys.left and selection > 1 then
-- Move left
selection = selection - 1
if selection < 1 then
selection = #mChoices
end
elseif key == keys.enter or key == keys.numPadEnter then
-- Select an option
return menu_choices[mChoices[selection]]()
elseif key == keys.leftCtrl or keys == keys.rightCtrl then
-- Cancel the menu
return false
end
elseif id == "mouse_click" then
local cx, cy = param2, param3
if cy ~= h then return false end -- Exit the menu
local nMenuPosEnd = 1
local nMenuPosStart = 1
for _, sMenuItem in ipairs(mChoices) do
nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
if cx > nMenuPosStart and cx < nMenuPosEnd then
return menu_choices[sMenuItem]()
end
nMenuPosEnd = nMenuPosEnd + 1
nMenuPosStart = nMenuPosEnd
end
elseif id == "term_resize" then
if event[1] == "term_resize" then
termResize()
menu.draw(current_menu)
end
end
end