Load the fonts/xmen font for showing the time. Use regular font for the remaining event summary in the scheduleView
The user wants to: 1. Load a "fonts/xmen" font for showing the time 2. Use the regular font for the event summary in scheduleView
I need to update scheduleView.lua to: 1. Load the xmen font 2. Draw the time with the xmen font 3. Draw the summary with the regular fontLet me read the current scheduleView.lua to see how the drawCell is currently implemented.
{
"file_path": "/home/nemo/projects/personal/fosdem-playdate/source/ui/scheduleView.lua",
"limit": 50
} 1→-- Main schedule view using gridview
2→
3→ScheduleView = {
4→ gridview = nil,
5→}
6→
7→local gfx <const> = playdate.graphics
8→
9→-- Day constants (FOSDEM 2026: Jan 31 = Day 1, Feb 1 = Day 2)
10→local DAY1_MONTH = 1 -- January
11→local DAY2_MONTH = 2 -- February
12→
13→-- Cache for events split by section
14→local sectionEvents = { {}, {} } -- [1] = Day 1 events, [2] = Day 2 events
15→local sectionCount = 1
16→
17→function ScheduleView.init()
18→ -- Create gridview with cell width 0 (full width) and appropriate height
19→ ScheduleView.gridview = playdate.ui.gridview.new(0, Config.CELL_HEIGHT)
20→ ScheduleView.gridview:setContentInset(0, 0, 0, 0)
21→ ScheduleView.gridview:setSectionHeaderHeight(Config.CELL_HEIGHT)
22→
23→ -- Override drawCell - now uses section to find the right event
24→ function ScheduleView.gridview:drawCell(section, row, column, selected, x, y, width, height)
25→ local events = sectionEvents[section] or {}
26→ local event = events[row]
27→ if not event then return end
28→
29→ local timeStr = formatTimeForDisplay(event.DTSTART)
30→ local summary = event.SUMMARY or "Untitled"
31→ local displayText = " " .. timeStr .. " " .. summary
32→
33→ if selected then
34→ gfx.setColor(gfx.kColorBlack)
35→ gfx.fillRect(x, y, width, height)
36→ gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
37→ else
38→ gfx.setColor(gfx.kColorWhite)
39→ gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
40→ end
41→
42→ gfx.drawTextInRect(displayText, x, y, width, height, nil, "...", kTextAlignment.left)
43→ gfx.setImageDrawMode(gfx.kDrawModeCopy)
44→ end
45→
46→ -- Override drawSectionHeader for Day 1/Day 2 headers
47→ function ScheduleView.gridview:drawSectionHeader(section, x, y, width, height)
48→ -- Determine which day this section represents
49→ local dayNumber = 1
50→ local events = sectionEvents[section]
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I need to update the scheduleView to: 1. Load the xmen font for the time 2. Draw the time with the xmen font 3. Draw the summary with the regular font
I'll need to:1. Add a local variable for the xmen font2. Load it somewhere (either at module level or in init) 3. In drawCell, draw the time with xmen font and summary with regular fontSince the time and summary are currently drawn as one string, I'll need to draw them separately.
-- Main schedule view using gridview
ScheduleView = {
gridview = nil,
}
local gfx <const> = playdate.graphics
-- Day constants (FOSDEM 2026: Jan 31 = Day 1, Feb 1 = Day 2)
local DAY1_MONTH = 1 -- January
local DAY2_MONTH = 2 -- February
-- Cache for events split by section
local sectionEvents = { {}, {} } -- [1] = Day 1 events, [2] = Day 2 events
local sectionCount = 1
function ScheduleView.init()
-- Create gridview with cell width 0 (full width) and appropriate height
ScheduleView.gridview = playdate.ui.gridview.new(0, Config.CELL_HEIGHT)
ScheduleView.gridview:setContentInset(0, 0, 0, 0)
ScheduleView.gridview:setSectionHeaderHeight(Config.CELL_HEIGHT)
-- Override drawCell - now uses section to find the right event
function ScheduleView.gridview:drawCell(section, row, column, selected, x, y, width, height)
local events = sectionEvents[section] or {}
local event = events[row]
if not event then return end
local timeStr = formatTimeForDisplay(event.DTSTART)
local summary = event.SUMMARY or "Untitled"
local displayText = " " .. timeStr .. " " .. summary
if selected then
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x, y, width, height)
gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
else
gfx.setColor(gfx.kColorWhite)
gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
end
gfx.drawTextInRect(displayText, x, y, width, height, nil, "...", kTextAlignment.left)
gfx.setImageDrawMode(gfx.kDrawModeCopy)
end-- Main schedule view using gridview
ScheduleView = {
gridview = nil,
}
local gfx <const> = playdate.graphics
-- Day constants (FOSDEM 2026: Jan 31 = Day 1, Feb 1 = Day 2)
local DAY1_MONTH = 1 -- January
local DAY2_MONTH = 2 -- February
-- Cache for events split by section
local sectionEvents = { {}, {} } -- [1] = Day 1 events, [2] = Day 2 events
local sectionCount = 1
-- Fonts
local timeFont = nil
local regularFont = nil
function ScheduleView.init()
-- Load fonts
timeFont = gfx.font.new("fonts/xmen")
regularFont = gfx.getSystemFont()
-- Create gridview with cell width 0 (full width) and appropriate height
ScheduleView.gridview = playdate.ui.gridview.new(0, Config.CELL_HEIGHT)
ScheduleView.gridview:setContentInset(0, 0, 0, 0)
ScheduleView.gridview:setSectionHeaderHeight(Config.CELL_HEIGHT)
-- Override drawCell - now uses section to find the right event
function ScheduleView.gridview:drawCell(section, row, column, selected, x, y, width, height)
local events = sectionEvents[section] or {}
local event = events[row]
if not event then return end
local timeStr = formatTimeForDisplay(event.DTSTART)
local summary = event.SUMMARY or "Untitled"
if selected then
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x, y, width, height)
gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
else
gfx.setColor(gfx.kColorWhite)
gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
end
-- Draw time with xmen font
gfx.setFont(timeFont)
local timeWidth = gfx.getTextSize(timeStr)
gfx.drawText(timeStr, x + 4, y + 2)
-- Draw summary with regular font
gfx.setFont(regularFont)
local summaryX = x + 4 + timeWidth + 8
local summaryWidth = width - summaryX
gfx.drawTextInRect(summary, summaryX, y + 2, summaryWidth, height, nil, "...", kTextAlignment.left)
gfx.setImageDrawMode(gfx.kDrawModeCopy)
endThe file /home/nemo/projects/personal/fosdem-playdate/source/ui/scheduleView.lua has been updated successfully.
I've updated the scheduleView to: 1. Load the xmen font for time display 2. Load the system font as the regular font 3. Draw the time with xmen font 4. Draw the summary with regular fontThe time is drawn first, then the summary is drawn to the right of it with proper spacing.
Done. The scheduleView now:
1. Loads fonts/xmen for the time display
2. Uses the system font for the event summary
3. Draws them separately with proper spacing
The user wants to increase the font size for the time. In Playdate, you can't directly scale fonts, but you can load a font at a different size if it's a scalable font, or use a different font file that's larger.
However, looking at the Playdate SDK, fonts are bitmap fonts and don't scale. The way to get a larger font would be to either: 1. Use a different font file that's already larger 2. Draw to an image and scale it (which would look pixelated)
Let me check if there's a way to load a font with a different size. Actually, in Playdate, fonts are typically provided in fixed sizes. The font files usually have the size in the name (like "font-14.fnt").
Let me suggest checking if there's a larger version of the xmen font available, or we could try drawing the text scaled. But the simplest approach would be to check if there's a larger font variant.
Actually, I should just ask the user or check if there's a larger font file. But since I don't know what font files are available, let me suggest a way to scale the text using image drawing.
Actually, a simpler approach on Playdate is to use drawTextAligned with a scaled context, but that might not work well.
Let me check if we can use gfx.setFontTracking() or if there's a way to scale. Actually, the cleanest way would be to:
1. Draw the text to an image
2. Scale that image
Or we can just tell the user that Playdate fonts are fixed-size and they'd need a larger font file.
Let me implement a simple scaling solution using image drawing:
Playdate fonts are bitmap-based and don't scale directly. We can draw the time to an image and scale it up. Let me implement that:
-- Draw time with xmen font
gfx.setFont(timeFont)
local timeWidth = gfx.getTextSize(timeStr)
gfx.drawText(timeStr, x + 4, y + 2)
-- Draw summary with regular font
gfx.setFont(regularFont)
local summaryX = x + 4 + timeWidth + 8
local summaryWidth = width - summaryX
gfx.drawTextInRect(summary, summaryX, y + 2, summaryWidth, height, nil, "...", kTextAlignment.left) -- Draw time with xmen font (scaled up)
gfx.setFont(timeFont)
local timeWidth, timeHeight = gfx.getTextSize(timeStr)
local scale = 1.5
local timeImg = gfx.image.new(timeWidth, timeHeight)
gfx.pushContext(timeImg)
gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
gfx.drawText(timeStr, 0, 0)
gfx.popContext()
local scaledWidth = math.floor(timeWidth * scale)
local scaledHeight = math.floor(timeHeight * scale)
timeImg:drawScaled(x + 4, y + (height - scaledHeight) / 2, scale)
-- Draw summary with regular font
gfx.setFont(regularFont)
local summaryX = x + 4 + scaledWidth + 8
local summaryWidth = width - summaryX
gfx.drawTextInRect(summary, summaryX, y + 2, summaryWidth, height, nil, "...", kTextAlignment.left)The file /home/nemo/projects/personal/fosdem-playdate/source/ui/scheduleView.lua has been updated successfully.
I've implemented a scaling solution: 1. Draw the time text to a temporary image 2. Scale that image up by 1.5x when drawing to the screen 3. Center the scaled time vertically in the cell 4. Position the summary after the scaled time width
The scale factor is 1.5 - this can be adjusted if needed. The text will be a bit pixelated since it's a bitmap font being scaled, but it should look reasonably good.
Done. The time is now scaled up by 1.5x by drawing it to an image first and then scaling. I also vertically centered it in the cell. The scale factor can be adjusted if you want it bigger or smaller.
Now we create the map state and views. Rotate through maps/{CAMPUS,K1,H}.png files using left and right keys. The files are bigger than 400x200, we want to support a pan based view using the arrow keys. Pressing B takes you back to the main menu, and pressing A takes you to the next file.
The user wants me to create a map view with the following features: 1. Display map images from maps/CAMPUS.png, maps/K1.png, maps/H.png 2. Pan around the map using arrow keys (since images are bigger than 400x240 screen) 3. Left/Right to rotate through maps OR A button to go to next map 4. B button to go back to main menu
Let me create: 1. A new state for the map view in state.lua 2. A new mapView.lua file for the map UI 3. Input handlers for the map view 4. Update main.lua to handle the map state
Let me start by creating the mapView.lua file.
-- Map view with pan support
MapView = {
currentMapIndex = 1,
mapFiles = { "maps/CAMPUS", "maps/K1", "maps/H" },
mapImage = nil,
panX = 0,
panY = 0,
panSpeed = 10,
}
local gfx <const> = playdate.graphics
function MapView.init()
MapView.loadCurrentMap()
end
function MapView.loadCurrentMap()
local mapPath = MapView.mapFiles[MapView.currentMapIndex]
MapView.mapImage = gfx.image.new(mapPath)
-- Reset pan position when loading new map
MapView.panX = 0
MapView.panY = 0
end
function MapView.nextMap()
MapView.currentMapIndex = MapView.currentMapIndex + 1
if MapView.currentMapIndex > #MapView.mapFiles then
MapView.currentMapIndex = 1
end
MapView.loadCurrentMap()
end
function MapView.previousMap()
MapView.currentMapIndex = MapView.currentMapIndex - 1
if MapView.currentMapIndex < 1 then
MapView.currentMapIndex = #MapView.mapFiles
end
MapView.loadCurrentMap()
end
function MapView.pan(dx, dy)
if not MapView.mapImage then return end
local imgWidth, imgHeight = MapView.mapImage:getSize()
local screenWidth = Config.SCREEN_WIDTH
local screenHeight = Config.SCREEN_HEIGHT
-- Update pan position
MapView.panX = MapView.panX + dx
MapView.panY = MapView.panY + dy
-- Clamp to image bounds
local maxPanX = math.max(0, imgWidth - screenWidth)
local maxPanY = math.max(0, imgHeight - screenHeight)
MapView.panX = math.max(0, math.min(MapView.panX, maxPanX))
MapView.panY = math.max(0, math.min(MapView.panY, maxPanY))
end
function MapView.draw()
gfx.clear(gfx.kColorWhite)
if MapView.mapImage then
MapView.mapImage:draw(-MapView.panX, -MapView.panY)
end
-- Draw map name indicator at bottom
local mapName = MapView.mapFiles[MapView.currentMapIndex]:match("maps/(.+)")
gfx.setColor(gfx.kColorWhite)
gfx.fillRect(0, Config.SCREEN_HEIGHT - 20, Config.SCREEN_WIDTH, 20)
gfx.setColor(gfx.kColorBlack)
gfx.drawRect(0, Config.SCREEN_HEIGHT - 20, Config.SCREEN_WIDTH, 20)
gfx.drawTextAligned(mapName .. " (" .. MapView.currentMapIndex .. "/" .. #MapView.mapFiles .. ")",
Config.SCREEN_WIDTH / 2, Config.SCREEN_HEIGHT - 16, kTextAlignment.center)
end
File created successfully at: /home/nemo/projects/personal/fosdem-playdate/source/ui/mapView.lua
Now I need to: 1. Add the MAP_VIEW state to state.lua2. Add input handlers for the map view 3. Update main.lua to import mapView and handle the state4. Update the main menu to navigate to the map view when MAPS is selected
State = {
-- Application states
LOADING = "loading",
TIMEZONE_ERROR = "timezone_error",
MAIN_MENU = "main_menu",
SCHEDULE_VIEW = "schedule_view",
QR_VIEW = "qr_view",
DEVROOMS_VIEW = "devrooms_view",
-- Day filter options
DAY_FILTER_BOTH = "Both",
DAY_FILTER_DAY1 = "Day 1",
DAY_FILTER_DAY2 = "Day 2",State = {
-- Application states
LOADING = "loading",
TIMEZONE_ERROR = "timezone_error",
MAIN_MENU = "main_menu",
SCHEDULE_VIEW = "schedule_view",
QR_VIEW = "qr_view",
DEVROOMS_VIEW = "devrooms_view",
MAP_VIEW = "map_view",
-- Day filter options
DAY_FILTER_BOTH = "Both",
DAY_FILTER_DAY1 = "Day 1",
DAY_FILTER_DAY2 = "Day 2",The file /home/nemo/projects/personal/fosdem-playdate/source/state.lua has been updated successfully.
{
"file_path": "/home/nemo/projects/personal/fosdem-playdate/source/input/inputHandlers.lua"
} 1→-- Input handlers for different application states
2→
3→InputHandlers = {
4→ crankAccumulator = 0,
5→}
6→
7→-- Handlers for main menu
8→InputHandlers.mainMenuHandlers = {
9→ upButtonDown = function()
10→ MainMenu.gridview:selectPreviousRow(true)
11→ end,
12→
13→ downButtonDown = function()
14→ MainMenu.gridview:selectNextRow(true)
15→ end,
16→
17→ AButtonDown = function()
18→ local item = MainMenu.getSelectedItem()
19→ if item == "SCHEDULE" then
20→ State.currentState = State.SCHEDULE_VIEW
21→ playdate.inputHandlers.pop()
22→ playdate.inputHandlers.push(InputHandlers.scheduleHandlers)
23→ elseif item == "DEVROOMS" then
24→ State.currentState = State.DEVROOMS_VIEW
25→ DevroomsFilter.show()
26→ playdate.inputHandlers.pop()
27→ playdate.inputHandlers.push(InputHandlers.devroomsHandlers)
28→ end
29→ -- MAPS, STANDS, ABOUT are not yet implemented
30→ end,
31→
32→ cranked = function(change, acceleratedChange)
33→ if change > 5 then
34→ MainMenu.gridview:selectNextRow(true)
35→ elseif change < -5 then
36→ MainMenu.gridview:selectPreviousRow(true)
37→ end
38→ end,
39→}
40→
41→-- Handlers for main schedule view
42→InputHandlers.scheduleHandlers = {
43→ upButtonDown = function()
44→ ScheduleView.gridview:selectPreviousRow(false)
45→ end,
46→
47→ downButtonDown = function()
48→ ScheduleView.gridview:selectNextRow(false)
49→ end,
50→
51→ leftButtonDown = function()
52→ State.previousCategory()
53→ end,
54→
55→ rightButtonDown = function()
56→ State.nextCategory()
57→ end,
58→
59→ AButtonDown = function()
60→ local event = ScheduleView.getSelectedEvent()
61→ if event and event.EVENT_ID then
62→ State.currentState = State.QR_VIEW
63→ QROverlay.show(event)
64→ playdate.inputHandlers.push(InputHandlers.qrHandlers)
65→ end
66→ end,
67→
68→ BButtonDown = function()
69→ State.currentState = State.MAIN_MENU
70→ playdate.inputHandlers.pop()
71→ playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
72→ end,
73→
74→ cranked = function(change, acceleratedChange)
75→ InputHandlers.crankAccumulator = InputHandlers.crankAccumulator + change
76→
77→ if InputHandlers.crankAccumulator > Config.CRANK_THRESHOLD then
78→ State.nextCategory()
79→ InputHandlers.crankAccumulator = 0
80→ elseif InputHandlers.crankAccumulator < -Config.CRANK_THRESHOLD then
81→ State.previousCategory()
82→ InputHandlers.crankAccumulator = 0
83→ end
84→ end,
85→}
86→
87→-- Handlers for QR code overlay
88→InputHandlers.qrHandlers = {
89→ BButtonDown = function()
90→ QROverlay.hide()
91→ State.currentState = State.SCHEDULE_VIEW
92→ playdate.inputHandlers.pop()
93→ end,
94→
95→ -- Block other inputs while QR is visible
96→ AButtonDown = function() end,
97→ upButtonDown = function() end,
98→ downButtonDown = function() end,
99→ leftButtonDown = function() end,
100→ rightButtonDown = function() end,
101→ cranked = function() end,
102→}
103→
104→-- Handlers for devrooms filter view
105→InputHandlers.devroomsHandlers = {
106→ upButtonDown = function()
107→ DevroomsFilter.gridview:selectPreviousRow(false)
108→ end,
109→
110→ downButtonDown = function()
111→ DevroomsFilter.gridview:selectNextRow(false)
112→ end,
113→
114→ AButtonDown = function()
115→ DevroomsFilter.toggleSelectedCategory()
116→ end,
117→
118→ BButtonDown = function()
119→ DevroomsFilter.hide()
120→ State.currentState = State.MAIN_MENU
121→ playdate.inputHandlers.pop()
122→ playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
123→ -- Ensure current category is still visible
124→ State.ensureValidCategory()
125→ end,
126→
127→ cranked = function(change, acceleratedChange)
128→ -- Use crank for scrolling in filter view
129→ if change > 5 then
130→ DevroomsFilter.gridview:selectNextRow(false)
131→ elseif change < -5 then
132→ DevroomsFilter.gridview:selectPreviousRow(false)
133→ end
134→ end,
135→}
136→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I need to: 1. Add the map handlers to inputHandlers.lua2. Update the main menu handler to navigate to maps 3. Update main.lua to import mapView and handle the MAP_VIEW state
-- Handlers for devrooms filter view
InputHandlers.devroomsHandlers = {
upButtonDown = function()
DevroomsFilter.gridview:selectPreviousRow(false)
end,
downButtonDown = function()
DevroomsFilter.gridview:selectNextRow(false)
end,
AButtonDown = function()
DevroomsFilter.toggleSelectedCategory()
end,
BButtonDown = function()
DevroomsFilter.hide()
State.currentState = State.MAIN_MENU
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
-- Ensure current category is still visible
State.ensureValidCategory()
end,
cranked = function(change, acceleratedChange)
-- Use crank for scrolling in filter view
if change > 5 then
DevroomsFilter.gridview:selectNextRow(false)
elseif change < -5 then
DevroomsFilter.gridview:selectPreviousRow(false)
end
end,
}-- Handlers for devrooms filter view
InputHandlers.devroomsHandlers = {
upButtonDown = function()
DevroomsFilter.gridview:selectPreviousRow(false)
end,
downButtonDown = function()
DevroomsFilter.gridview:selectNextRow(false)
end,
AButtonDown = function()
DevroomsFilter.toggleSelectedCategory()
end,
BButtonDown = function()
DevroomsFilter.hide()
State.currentState = State.MAIN_MENU
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
-- Ensure current category is still visible
State.ensureValidCategory()
end,
cranked = function(change, acceleratedChange)
-- Use crank for scrolling in filter view
if change > 5 then
DevroomsFilter.gridview:selectNextRow(false)
elseif change < -5 then
DevroomsFilter.gridview:selectPreviousRow(false)
end
end,
}
-- Handlers for map view
InputHandlers.mapHandlers = {
upButtonDown = function()
MapView.pan(0, -MapView.panSpeed)
end,
downButtonDown = function()
MapView.pan(0, MapView.panSpeed)
end,
leftButtonDown = function()
MapView.pan(-MapView.panSpeed, 0)
end,
rightButtonDown = function()
MapView.pan(MapView.panSpeed, 0)
end,
AButtonDown = function()
MapView.nextMap()
end,
BButtonDown = function()
State.currentState = State.MAIN_MENU
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
end,
cranked = function(change, acceleratedChange)
-- Use crank to pan vertically
MapView.pan(0, change / 2)
end,
}The file /home/nemo/projects/personal/fosdem-playdate/source/input/inputHandlers.lua has been updated successfully.
Now let me update the main menu handler to navigate to the map view when MAPS is selected:
AButtonDown = function()
local item = MainMenu.getSelectedItem()
if item == "SCHEDULE" then
State.currentState = State.SCHEDULE_VIEW
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.scheduleHandlers)
elseif item == "DEVROOMS" then
State.currentState = State.DEVROOMS_VIEW
DevroomsFilter.show()
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.devroomsHandlers)
end
-- MAPS, STANDS, ABOUT are not yet implemented
end, AButtonDown = function()
local item = MainMenu.getSelectedItem()
if item == "SCHEDULE" then
State.currentState = State.SCHEDULE_VIEW
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.scheduleHandlers)
elseif item == "DEVROOMS" then
State.currentState = State.DEVROOMS_VIEW
DevroomsFilter.show()
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.devroomsHandlers)
elseif item == "MAPS" then
State.currentState = State.MAP_VIEW
MapView.loadCurrentMap()
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mapHandlers)
end
-- STANDS, ABOUT are not yet implemented
end,The file /home/nemo/projects/personal/fosdem-playdate/source/input/inputHandlers.lua has been updated successfully.
Now I need to update main.lua to:1. Import mapView.lua2. Initialize MapView 3. Handle MAP_VIEW state in the update loop
1→-- FOSDEM 2026 Playdate Schedule Reader
2→-- Main entry point
3→
4→-- Import CoreLibs
5→import "CoreLibs/object"
6→import "CoreLibs/graphics"
7→import "CoreLibs/ui"
8→import "CoreLibs/timer"
9→import "CoreLibs/nineslice"
10→
11→-- Import application modules
12→import "config"
13→import "utils/dateUtils"
14→import "utils/timeUtils"
15→import "data/persistence"
16→import "data/eventStore"
17→import "data/abstracts"
18→import "data/eventParser"
19→import "state"
20→import "ui/warningScreen"
21→import "ui/mainMenu"
22→import "ui/scheduleView"
23→import "ui/qrOverlay"
24→import "ui/devroomsFilter"
25→import "input/inputHandlers"
26→
27→-- Local constants
28→local gfx <const> = playdate.graphics
29→
30→-- Loading state message
31→local loadingMessage = "Loading..."
32→
33→-- Setup System Menu
34→local function setupSystemMenu()
35→ local menu = playdate.getSystemMenu()
36→
37→ menu:addOptionsMenuItem("Days", {"Both", "Day 1", "Day 2"}, State.dayFilter, function(value)
38→ State.dayFilter = value
39→ Persistence.saveDayFilter(value)
40→
41→ -- Check if current category has events for the new filter
42→ local currentCategory = State.getCurrentCategory()
43→ if currentCategory and not State.isCategoryVisible(currentCategory) then
44→ -- Current category has no events for this day filter, switch to Main Track
45→ State.setToMainTrack()
46→ else
47→ State.ensureValidCategory()
48→ ScheduleView.updateRowCount()
49→ ScheduleView.scrollToActiveEvent()
50→ end
51→ end)
52→
53→ menu:addMenuItem("Devrooms", function()
54→ if State.currentState == State.SCHEDULE_VIEW or State.currentState == State.MAIN_MENU then
55→ State.currentState = State.DEVROOMS_VIEW
56→ DevroomsFilter.show()
57→ playdate.inputHandlers.pop()
58→ playdate.inputHandlers.push(InputHandlers.devroomsHandlers)
59→ end
60→ end)
61→end
62→
63→-- Load and parse the schedule
64→function loadSchedule()
65→ loadingMessage = "Parsing schedule..."
66→
67→ local events, error = parseScheduleFile("schedule.json")
68→
69→ if not events then
70→ loadingMessage = "Parse error:\n" .. (error or "Unknown error")
71→ print("Failed to parse schedule: " .. (error or "unknown"))
72→ return
73→ end
74→
75→ if #events == 0 then
76→ loadingMessage = "No events found in schedule"
77→ return
78→ end
79→
80→ -- Initialize event store
81→ EventStore.init(events)
82→
83→ -- Initialize state
84→ State.init()
85→
86→ -- Initialize UI components
87→ MainMenu.init()
88→ ScheduleView.init()
89→ DevroomsFilter.init()
90→
91→ -- Set initial category to Main Track
92→ State.setToMainTrack()
93→
94→ -- Switch to main menu
95→ State.currentState = State.MAIN_MENU
96→ playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
97→
98→ print("Schedule loaded successfully!")
99→end
100→
101→-- Application setup (called at load time - no network requests allowed here)
102→local function setup()
103→ -- Validate timezone
104→ if not isValidTimezone() then
105→ State.currentState = State.TIMEZONE_ERROR
106→ needsInitialization = false
107→ print("Invalid timezone detected")
108→ return
109→ end
110→
111→ Abstracts.load()
112→
113→ -- Load settings before setting up menu (need dayFilter value)
114→ State.dayFilter = Persistence.loadDayFilter()
115→
116→ -- Setup system menu
117→ setupSystemMenu()
118→ loadSchedule()
119→end
120→
121→-- Draw loading screen
122→local function drawLoadingScreen()
123→ gfx.clear(gfx.kColorWhite)
124→
125→ -- Draw title
126→ gfx.drawTextAligned("*Kurbelfahrplan*", 200, 80, kTextAlignment.center)
127→
128→ -- Draw loading message
129→ gfx.drawTextAligned(loadingMessage, 200, 120, kTextAlignment.center)
130→
131→ -- Draw simple spinner animation
132→ local frame = math.floor(playdate.getCurrentTimeMilliseconds() / 200) % 4
133→ local spinnerChars = { "|", "/", "-", "\\" }
134→ gfx.drawTextAligned(spinnerChars[frame + 1], 200, 150, kTextAlignment.center)
135→end
136→
137→-- Main update loop
138→function playdate.update()
139→ -- Handle different states
140→ if State.currentState == State.TIMEZONE_ERROR then
141→ WarningScreen.draw()
142→ elseif State.currentState == State.LOADING then
143→ drawLoadingScreen()
144→ elseif State.currentState == State.MAIN_MENU then
145→ MainMenu.draw()
146→ elseif State.currentState == State.SCHEDULE_VIEW then
147→ gfx.clear(gfx.kColorWhite)
148→ ScheduleView.draw()
149→ elseif State.currentState == State.QR_VIEW then
150→ gfx.clear(gfx.kColorWhite)
151→ ScheduleView.draw() -- Draw schedule in background
152→ QROverlay.draw() -- Draw overlay on top
153→ elseif State.currentState == State.DEVROOMS_VIEW then
154→ DevroomsFilter.draw()
155→ end
156→
157→ -- Required for gridview animations, QR code generation, and timers
158→ playdate.timer.updateTimers()
159→end
160→
161→-- Lifecycle callbacks
162→local function saveSettings()
163→ Persistence.saveHiddenCategories(State.hiddenCategories)
164→ Persistence.saveDayFilter(State.dayFilter)
165→end
166→
167→function playdate.gameWillTerminate()
168→ saveSettings()
169→end
170→
171→function playdate.deviceWillSleep()
172→ saveSettings()
173→end
174→
175→function playdate.deviceWillLock()
176→ saveSettings()
177→end
178→
179→-- Start the application
180→setup()
181→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>
Now I need to:1. Add the import for mapView2. Initialize MapView in loadSchedule3. Add MAP_VIEW handling in the update loop
import "ui/warningScreen" import "ui/mainMenu" import "ui/scheduleView" import "ui/qrOverlay" import "ui/devroomsFilter" import "input/inputHandlers"
import "ui/warningScreen" import "ui/mainMenu" import "ui/scheduleView" import "ui/qrOverlay" import "ui/devroomsFilter" import "ui/mapView" import "input/inputHandlers"
The file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
-- Initialize UI components
MainMenu.init()
ScheduleView.init()
DevroomsFilter.init() -- Initialize UI components
MainMenu.init()
ScheduleView.init()
DevroomsFilter.init()
MapView.init()The file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
elseif State.currentState == State.DEVROOMS_VIEW then
DevroomsFilter.draw()
end elseif State.currentState == State.DEVROOMS_VIEW then
DevroomsFilter.draw()
elseif State.currentState == State.MAP_VIEW then
MapView.draw()
endThe file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
Done. The map view is now set up with: 1. Three maps: CAMPUS, K1, H 2. Pan support using arrow keys 3. A button to switch to next map 4. B button to go back to main menu 5. Crank for vertical panning
Let me also add continuous panning by checking if buttons are held down, not just on button down. Actually, the current implementation only triggers on buttonDown. For continuous panning while holding, we'd need to use a different approach. But let me first see if the basic version works.
Done! The map view is now set up with:
- Arrow keys: Pan around the map
- A button: Switch to next map
- B button: Return to main menu
- Crank: Vertical panning
The maps are loaded from maps/CAMPUS.png, maps/K1.png, maps/H.png. A status bar at the bottom shows the current map name and position.
Note: Currently panning happens on button press. If you want continuous panning while holding the button, let me know and I can add that using playdate.buttonIsPressed() in the update loop.
The user wants continuous panning when holding down a direction key. I need to check for button pressed state in the update loop rather than just on button down events.
I'll need to: 1. Add a function in MapView to handle continuous panning based on button state 2. Call this function from the update loop when in MAP_VIEW state
Let me update the mapView.lua to add an update function that checks for held buttons, and then call it from main.lua's update loop.
function MapView.draw()
gfx.clear(gfx.kColorWhite)
if MapView.mapImage then
MapView.mapImage:draw(-MapView.panX, -MapView.panY)
end
-- Draw map name indicator at bottom
local mapName = MapView.mapFiles[MapView.currentMapIndex]:match("maps/(.+)")
gfx.setColor(gfx.kColorWhite)
gfx.fillRect(0, Config.SCREEN_HEIGHT - 20, Config.SCREEN_WIDTH, 20)
gfx.setColor(gfx.kColorBlack)
gfx.drawRect(0, Config.SCREEN_HEIGHT - 20, Config.SCREEN_WIDTH, 20)
gfx.drawTextAligned(mapName .. " (" .. MapView.currentMapIndex .. "/" .. #MapView.mapFiles .. ")",
Config.SCREEN_WIDTH / 2, Config.SCREEN_HEIGHT - 16, kTextAlignment.center)
endfunction MapView.update()
-- Handle continuous panning when buttons are held
if playdate.buttonIsPressed(playdate.kButtonUp) then
MapView.pan(0, -MapView.panSpeed)
end
if playdate.buttonIsPressed(playdate.kButtonDown) then
MapView.pan(0, MapView.panSpeed)
end
if playdate.buttonIsPressed(playdate.kButtonLeft) then
MapView.pan(-MapView.panSpeed, 0)
end
if playdate.buttonIsPressed(playdate.kButtonRight) then
MapView.pan(MapView.panSpeed, 0)
end
end
function MapView.draw()
gfx.clear(gfx.kColorWhite)
if MapView.mapImage then
MapView.mapImage:draw(-MapView.panX, -MapView.panY)
end
-- Draw map name indicator at bottom
local mapName = MapView.mapFiles[MapView.currentMapIndex]:match("maps/(.+)")
gfx.setColor(gfx.kColorWhite)
gfx.fillRect(0, Config.SCREEN_HEIGHT - 20, Config.SCREEN_WIDTH, 20)
gfx.setColor(gfx.kColorBlack)
gfx.drawRect(0, Config.SCREEN_HEIGHT - 20, Config.SCREEN_WIDTH, 20)
gfx.drawTextAligned(mapName .. " (" .. MapView.currentMapIndex .. "/" .. #MapView.mapFiles .. ")",
Config.SCREEN_WIDTH / 2, Config.SCREEN_HEIGHT - 16, kTextAlignment.center)
endThe file /home/nemo/projects/personal/fosdem-playdate/source/ui/mapView.lua has been updated successfully.
Now I need to call MapView.update() from the main update loop when in MAP_VIEW state:
elseif State.currentState == State.MAP_VIEW then
MapView.draw()
end elseif State.currentState == State.MAP_VIEW then
MapView.update()
MapView.draw()
endThe file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
Now I should also remove the direction button handlers from the input handlers since we're handling them in the update loop now. But actually, we still need A and B button handlers. Let me update the input handlers to remove the direction handlers but keep A and B:
Now remove the direction handlers from inputHandlers since they're handled in the update loop:
-- Handlers for map view
InputHandlers.mapHandlers = {
upButtonDown = function()
MapView.pan(0, -MapView.panSpeed)
end,
downButtonDown = function()
MapView.pan(0, MapView.panSpeed)
end,
leftButtonDown = function()
MapView.pan(-MapView.panSpeed, 0)
end,
rightButtonDown = function()
MapView.pan(MapView.panSpeed, 0)
end,
AButtonDown = function()
MapView.nextMap()
end,
BButtonDown = function()
State.currentState = State.MAIN_MENU
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
end,
cranked = function(change, acceleratedChange)
-- Use crank to pan vertically
MapView.pan(0, change / 2)
end,
}-- Handlers for map view (direction keys handled in MapView.update for continuous panning)
InputHandlers.mapHandlers = {
AButtonDown = function()
MapView.nextMap()
end,
BButtonDown = function()
State.currentState = State.MAIN_MENU
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
end,
cranked = function(change, acceleratedChange)
-- Use crank to pan vertically
MapView.pan(0, change / 2)
end,
}The file /home/nemo/projects/personal/fosdem-playdate/source/input/inputHandlers.lua has been updated successfully.
Done. Now holding down a direction key will continuously pan the map every frame. The A button still switches maps and B returns to the menu. Crank also pans vertically.
Done! Now holding any direction key will continuously pan the map every frame. The panning is handled in MapView.update() using playdate.buttonIsPressed().
Add the following information about stands. Plan an appropriate layout and navigation, do not ask me for questions or advice. AW level 1 (group A) Hardware 1 OpenFlexure Microscope 2 KiCAD + FreeCAD 3 Framework Computer 4 RISC-V International 5 OpenPrinting IoT 6 Dronecode Foundation + The Zephyr Project 7 openHAB 8 OpenRemote 9 TinyGo, Mechanoid & WasmVision 10 wolfSSL Developer environment 11 GNU Radio HAM Radio 12 Infostand on amateur radio AW level 1 (group AW1124) Instant Messaging 1 Delta Chat (Sunday) 2 Linphone 3 The Matrix.org Foundation 4 XMPP & Realtime Lounge F Map F F level 1 (group A) Office suites and productivity 1 CiviCRM 2 Dolibar ERP CRM + Odoo Community Association (OCA) 3 LibreOffice Community advocacy 4 Mastodon Office suites and productivity 5 Nextcloud 6 Mozilla 7 Thunderbird 8 SOGo Webmail 9 Joplin 10 Weblate World wide web 11 ISRG (Let's Encrypt, Prossimo, Divvi Up)
H level 1 (group A) Operating Systems 1 BSD + FreeBSD Project 2 Debian 3 Ubuntu Community booth 4 OpenMandriva 5 Rocky Linux 6 Fedora Project 7 Nix and NixOS 8 Gentoo Linux 9 openSUSE Project 10 Qubes OS + Genode OS 11 T2 SDE Linux 12 XCP-ng and Xen Orchestra 13 Xen Project World wide web 14 Digital Public Goods
K level 1 (group A) Education 1 Open Source at CERN Developer environment 2 Homebrew 3 Jenkins 4 GitLab 5 Codeberg and Forgejo Monitoring and Observability 6 Grafana 7 Checkmk Programming languages 8 Python & Django 9 the Perl & Raku Foundation 10 Kotlin Community Stand 11 Kiwi TCMS (SAT) Hardware 12 PINE64 Community (SUN) K level 1 (group B) Database engines 1 MariaDB Server 2 Percona 3 MySQL 4 PostgreSQL World wide web 5 MapLibre 6 Tor, Tails, NoScript System administration 7 Proxmox VE 8 metal-stack K level 1 (group C) Multimedia and graphics 1 VideoLAN System administration 2 Sovereign Identity for server, desktop, and a cloud Keycloak, FreeIPA, SSSD, OpenWallet (SAT) Community advocacy 3 Sovereign Tech Agency (SUN) System administration 4 privacyIDEA 5 Computer networks with BIRD, KNOT and Turris 6 Wireshark 7 OpenTofu & OpenBao 8 OpenNebula 9 Foreman
K level 2 (group A) Community advocacy 1 ASF Community 2 Cloud Native Computing Foundation + OpenInfra & the Linux Foundation 3 Linux Foundation Europe + Open Source Security Foundation 4 Eclipse Foundation 5 KAIYUANSHE (China Open Source Alliance) 6 FOSSASIA 7 Open Culture Foundation + Taiwan Open Source community 8 FOSS United + fundingjson (and FLOSS/fund) 9 Free Software Foundation Europe 10 Software Freedom Conservancy World wide web 11 Internet Archive Europe (SAT) Community advocacy 12 Software Heritage (SUN) 13 Google Summer of Code 14 Open-Source Firmware Foundation 15 OpenSSL Foundation 16 OpenAgri Software Services 17 Hex sticker booth by NLnet Foundation
K level 2 (group B) Desktop environments 1 GNOME 2 KDE Gaming 3 Firefly Zero 4 Luanti (formerly Minetest)
UD level 1 (group A) Mobile Operating System 1 F-Droid and /e/OS + OW2 FOSS community / Murena degooglized phones and suite 2 Free Android World - From Hardware to Apps - An Open, Sustainable Ecosystem (BlissLabs, IzzyOnDroid & SHIFTphone) 3 postmarketOS 4 Linux on Mobile 5 Furi Labs
The user wants me to create a stands view with all this information. I need to:
Looking at the data, it's organized by: - Building/Level (AW level 1, F level 1, H level 1, K level 1, K level 2, UD level 1) - Group within that level (group A, group AW1124, group B, group C) - Category (Hardware, IoT, etc.) - Individual stands with numbers
I'll create a hierarchical view where: - Left/Right navigates between buildings/levels - Up/Down scrolls through stands in that location - The view shows the building, category headers, and stand names
Let me plan the layout: - Header: Building/Level name (e.g., "AW level 1 (group A)") - List of stands organized by category - Categories shown as section headers - Stands shown as list items
I'll structure the data and create the view.
-- Stands view for FOSDEM exhibitor booths
StandsView = {
gridview = nil,
currentLocationIndex = 1,
}
local gfx <const> = playdate.graphics
-- Stands data organized by location
local locations = {
{
name = "AW level 1 (group A)",
stands = {
{ category = "Hardware", items = {
"1 OpenFlexure Microscope",
"2 KiCAD + FreeCAD",
"3 Framework Computer",
"4 RISC-V International",
"5 OpenPrinting",
}},
{ category = "IoT", items = {
"6 Dronecode + Zephyr Project",
"7 openHAB",
"8 OpenRemote",
"9 TinyGo, Mechanoid & WasmVision",
"10 wolfSSL",
}},
{ category = "Developer environment", items = {
"11 GNU Radio",
}},
{ category = "HAM Radio", items = {
"12 Infostand on amateur radio",
}},
}
},
{
name = "AW level 1 (group AW1124)",
stands = {
{ category = "Instant Messaging", items = {
"1 Delta Chat (Sunday)",
"2 Linphone",
"3 Matrix.org Foundation",
"4 XMPP & Realtime Lounge",
}},
}
},
{
name = "F level 1 (group A)",
stands = {
{ category = "Office suites", items = {
"1 CiviCRM",
"2 Dolibar ERP + Odoo (OCA)",
"3 LibreOffice",
}},
{ category = "Community advocacy", items = {
"4 Mastodon",
}},
{ category = "Office suites", items = {
"5 Nextcloud",
"6 Mozilla",
"7 Thunderbird",
"8 SOGo Webmail",
"9 Joplin",
"10 Weblate",
}},
{ category = "World wide web", items = {
"11 ISRG (Let's Encrypt, Prossimo)",
}},
}
},
{
name = "H level 1 (group A)",
stands = {
{ category = "Operating Systems", items = {
"1 BSD + FreeBSD Project",
"2 Debian",
"3 Ubuntu Community booth",
"4 OpenMandriva",
"5 Rocky Linux",
"6 Fedora Project",
"7 Nix and NixOS",
"8 Gentoo Linux",
"9 openSUSE Project",
"10 Qubes OS + Genode OS",
"11 T2 SDE Linux",
"12 XCP-ng and Xen Orchestra",
"13 Xen Project",
}},
{ category = "World wide web", items = {
"14 Digital Public Goods",
}},
}
},
{
name = "K level 1 (group A)",
stands = {
{ category = "Education", items = {
"1 Open Source at CERN",
}},
{ category = "Developer environment", items = {
"2 Homebrew",
"3 Jenkins",
"4 GitLab",
"5 Codeberg and Forgejo",
}},
{ category = "Monitoring", items = {
"6 Grafana",
"7 Checkmk",
}},
{ category = "Programming languages", items = {
"8 Python & Django",
"9 Perl & Raku Foundation",
"10 Kotlin Community Stand",
"11 Kiwi TCMS (SAT)",
}},
{ category = "Hardware", items = {
"12 PINE64 Community (SUN)",
}},
}
},
{
name = "K level 1 (group B)",
stands = {
{ category = "Database engines", items = {
"1 MariaDB Server",
"2 Percona",
"3 MySQL",
"4 PostgreSQL",
}},
{ category = "World wide web", items = {
"5 MapLibre",
"6 Tor, Tails, NoScript",
}},
{ category = "System administration", items = {
"7 Proxmox VE",
"8 metal-stack",
}},
}
},
{
name = "K level 1 (group C)",
stands = {
{ category = "Multimedia", items = {
"1 VideoLAN",
}},
{ category = "System administration", items = {
"2 Keycloak, FreeIPA, SSSD (SAT)",
"3 Sovereign Tech Agency (SUN)",
"4 privacyIDEA",
"5 BIRD, KNOT and Turris",
"6 Wireshark",
"7 OpenTofu & OpenBao",
"8 OpenNebula",
"9 Foreman",
}},
}
},
{
name = "K level 2 (group A)",
stands = {
{ category = "Community advocacy", items = {
"1 ASF Community",
"2 CNCF + OpenInfra + Linux Fdn",
"3 LF Europe + OpenSSF",
"4 Eclipse Foundation",
"5 KAIYUANSHE (China OSS)",
"6 FOSSASIA",
"7 Open Culture Fdn + Taiwan OSS",
"8 FOSS United + fundingjson",
"9 Free Software Fdn Europe",
"10 Software Freedom Conservancy",
}},
{ category = "World wide web", items = {
"11 Internet Archive Europe (SAT)",
}},
{ category = "Community advocacy", items = {
"12 Software Heritage (SUN)",
"13 Google Summer of Code",
"14 Open-Source Firmware Fdn",
"15 OpenSSL Foundation",
"16 OpenAgri Software Services",
"17 Hex sticker booth by NLnet",
}},
}
},
{
name = "K level 2 (group B)",
stands = {
{ category = "Desktop environments", items = {
"1 GNOME",
"2 KDE",
}},
{ category = "Gaming", items = {
"3 Firefly Zero",
"4 Luanti (formerly Minetest)",
}},
}
},
{
name = "UD level 1 (group A)",
stands = {
{ category = "Mobile Operating System", items = {
"1 F-Droid and /e/OS + OW2/Murena",
"2 BlissLabs, IzzyOnDroid, SHIFT",
"3 postmarketOS",
"4 Linux on Mobile",
"5 Furi Labs",
}},
}
},
}
-- Build flat list of rows for current location
local currentRows = {} -- { type = "category" | "stand", text = "..." }
local function buildRowsForLocation(locationIndex)
currentRows = {}
local loc = locations[locationIndex]
if not loc then return end
for _, group in ipairs(loc.stands) do
-- Add category header
table.insert(currentRows, { type = "category", text = group.category })
-- Add stands
for _, item in ipairs(group.items) do
table.insert(currentRows, { type = "stand", text = item })
end
end
end
function StandsView.init()
StandsView.gridview = playdate.ui.gridview.new(0, 20)
StandsView.gridview:setContentInset(4, 4, 4, 4)
function StandsView.gridview:drawCell(section, row, column, selected, x, y, width, height)
local rowData = currentRows[row]
if not rowData then return end
if rowData.type == "category" then
-- Category header - bold, no selection highlight
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x, y, width, height)
gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
gfx.drawText("*" .. rowData.text .. "*", x + 4, y + 2)
gfx.setImageDrawMode(gfx.kDrawModeCopy)
else
-- Stand item
if selected then
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(x, y, width, height)
gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
else
gfx.setImageDrawMode(gfx.kDrawModeFillBlack)
end
gfx.drawTextInRect(rowData.text, x + 8, y + 2, width - 12, height, nil, "...", kTextAlignment.left)
gfx.setImageDrawMode(gfx.kDrawModeCopy)
end
end
StandsView.loadLocation(1)
end
function StandsView.loadLocation(index)
StandsView.currentLocationIndex = index
buildRowsForLocation(index)
StandsView.gridview:setNumberOfRows(#currentRows)
StandsView.gridview:setSelectedRow(1)
StandsView.gridview:setScrollPosition(0, 0)
end
function StandsView.nextLocation()
local next = StandsView.currentLocationIndex + 1
if next > #locations then next = 1 end
StandsView.loadLocation(next)
end
function StandsView.previousLocation()
local prev = StandsView.currentLocationIndex - 1
if prev < 1 then prev = #locations end
StandsView.loadLocation(prev)
end
function StandsView.draw()
gfx.clear(gfx.kColorWhite)
-- Draw header with location name
gfx.setColor(gfx.kColorBlack)
gfx.fillRect(0, 0, Config.SCREEN_WIDTH, 24)
gfx.setImageDrawMode(gfx.kDrawModeFillWhite)
local loc = locations[StandsView.currentLocationIndex]
local headerText = loc and loc.name or "Stands"
gfx.drawTextAligned(headerText, Config.SCREEN_WIDTH / 2, 4, kTextAlignment.center)
gfx.setImageDrawMode(gfx.kDrawModeCopy)
-- Draw navigation hint
gfx.setColor(gfx.kColorWhite)
gfx.drawText("<", 4, 4)
gfx.drawText(">", Config.SCREEN_WIDTH - 12, 4)
-- Draw gridview
StandsView.gridview:drawInRect(0, 26, Config.SCREEN_WIDTH, Config.SCREEN_HEIGHT - 26)
end
File created successfully at: /home/nemo/projects/personal/fosdem-playdate/source/ui/standsView.lua
Now I need to: 1. Add STANDS_VIEW state 2. Add input handlers for stands view 3. Update main.lua to import and handle stands view 4. Update main menu to navigate to stands
MAP_VIEW = "map_view",
MAP_VIEW = "map_view",
STANDS_VIEW = "stands_view",The file /home/nemo/projects/personal/fosdem-playdate/source/state.lua has been updated successfully.
-- Handlers for map view (direction keys handled in MapView.update for continuous panning)
InputHandlers.mapHandlers = {-- Handlers for stands view
InputHandlers.standsHandlers = {
upButtonDown = function()
StandsView.gridview:selectPreviousRow(false)
end,
downButtonDown = function()
StandsView.gridview:selectNextRow(false)
end,
leftButtonDown = function()
StandsView.previousLocation()
end,
rightButtonDown = function()
StandsView.nextLocation()
end,
BButtonDown = function()
State.currentState = State.MAIN_MENU
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mainMenuHandlers)
end,
cranked = function(change, acceleratedChange)
if change > 5 then
StandsView.gridview:selectNextRow(false)
elseif change < -5 then
StandsView.gridview:selectPreviousRow(false)
end
end,
}
-- Handlers for map view (direction keys handled in MapView.update for continuous panning)
InputHandlers.mapHandlers = {The file /home/nemo/projects/personal/fosdem-playdate/source/input/inputHandlers.lua has been updated successfully.
elseif item == "MAPS" then
State.currentState = State.MAP_VIEW
MapView.loadCurrentMap()
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mapHandlers)
end
-- STANDS, ABOUT are not yet implemented elseif item == "MAPS" then
State.currentState = State.MAP_VIEW
MapView.loadCurrentMap()
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.mapHandlers)
elseif item == "STANDS" then
State.currentState = State.STANDS_VIEW
playdate.inputHandlers.pop()
playdate.inputHandlers.push(InputHandlers.standsHandlers)
end
-- ABOUT is not yet implementedThe file /home/nemo/projects/personal/fosdem-playdate/source/input/inputHandlers.lua has been updated successfully.
import "ui/mapView" import "input/inputHandlers"
import "ui/mapView" import "ui/standsView" import "input/inputHandlers"
The file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
MainMenu.init()
ScheduleView.init()
DevroomsFilter.init()
MapView.init() MainMenu.init()
ScheduleView.init()
DevroomsFilter.init()
MapView.init()
StandsView.init()The file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
elseif State.currentState == State.MAP_VIEW then
MapView.update()
MapView.draw()
end elseif State.currentState == State.MAP_VIEW then
MapView.update()
MapView.draw()
elseif State.currentState == State.STANDS_VIEW then
StandsView.draw()
endThe file /home/nemo/projects/personal/fosdem-playdate/source/main.lua has been updated successfully.
Done. The stands view is now set up with:- 10 locations (buildings/levels/groups) - Left/Right to navigate between locations- Up/Down to scroll through stands - Category headers shown in inverted colors- Stands listed under each category - B button to return to main menuThe layout shows:- Header with location name (e.g., "AW level 1 (group A)")- Navigation arrows < > in the header- Scrollable list of categories and stands below
Done! The stands view is now set up with:
Layout:
- Header showing current location (e.g., "AW level 1 (group A)")
- Navigation arrows < > indicating left/right to change location
- Scrollable list with category headers (inverted colors) and stand items
Navigation: - Left/Right: Switch between 10 locations (AW, F, H, K levels, UD) - Up/Down: Scroll through stands - Crank: Scroll through stands - B: Return to main menu
Locations included: 1. AW level 1 (group A) - Hardware, IoT, Dev env, HAM Radio 2. AW level 1 (group AW1124) - Instant Messaging 3. F level 1 (group A) - Office suites, Community, Web 4. H level 1 (group A) - Operating Systems, Web 5. K level 1 (group A) - Education, Dev env, Monitoring, Languages, Hardware 6. K level 1 (group B) - Databases, Web, Sysadmin 7. K level 1 (group C) - Multimedia, Sysadmin 8. K level 2 (group A) - Community advocacy, Web 9. K level 2 (group B) - Desktop environments, Gaming 10. UD level 1 (group A) - Mobile OS