BUG: Two gridviews, trying to scroll 2nd grid's column to center will scroll the first grid instead

SDK 0.10.1
macOS 10.14.6
in simulator and on hardware

Hey y'all! I'm using several gridviews in our game simultaneously, which slide on and off screen based on user input. They often overlap rapidly, so I need to have all the gridviews created and ready to go at once. There are a couple I'd like to manipulate while they're offscreen, setting their scroll & selection. But I'm running into a bug where trying to scroll one gridview will very often scroll another.

I can't figure out why this is happening, nor can I figure out exactly what determines the order they receive control in our more-complicated game. But I have replicated the bug in a very simple example. Here's a full main.lua which illustrates the problem:

local gfx = playdate.graphics
import 'CoreLibs/ui'

local grid1 = playdate.ui.gridview.new(120, 100)
grid1:setNumberOfColumns(10)
grid1:setNumberOfRows(1)
grid1:setScrollDuration(400)
grid1.scrollCellsToCenter = true
-- section, row, column
grid1:setSelection(1, 1, 1)
grid1:scrollCellToCenter(1, 1, 1, false)

local grid2 = playdate.ui.gridview.new(120, 100)
grid2:setNumberOfColumns(10)
grid2:setNumberOfRows(1)
grid2:setScrollDuration(400)
grid2.scrollCellsToCenter = true

grid2:setSelection(1, 1, 3)
--[[ HERE'S THE BUG: ]]
--[[ This should scroll grid2, but it scrolls grid1 ]]
grid2:scrollCellToCenter(1, 1, 3, false)

gfx.setColor(gfx.kColorBlack)
gfx.setStrokeLocation(gfx.kStrokeInside)

function playdate.update()
  gfx.clear()
  handleInput()
  -- gfx.drawText('test', 0, 0)
  grid1:drawInRect(0, 1, 400, 100)
  grid2:drawInRect(0, 102, 400, 100)
  gfx.drawText('*left/right to move grid1, B/A to move grid2*', 44, 212)
  playdate.timer.updateTimers()
end

function handleInput()
  if playdate.buttonJustPressed('left') then
    grid1:selectPreviousColumn()
  end
  if playdate.buttonJustPressed('right') then
    grid1:selectNextColumn()
  end
  if playdate.buttonJustPressed('B') then
    grid2:selectPreviousColumn()
  end
  if playdate.buttonJustPressed('A') then
    grid2:selectNextColumn()
  end
end

function grid1:drawCell(section, row, column, selected, x, y, width, height)
  gfx.pushContext()
    gfx.drawText('grid1 cell '..column, x + 10, y + 10)
    if selected then
      gfx.drawText('*selected*', x + 10, y + 38)
      gfx.setLineWidth(3)
    end
    gfx.drawRoundRect(x, y, width, height, 4)
  gfx.popContext()
end

function grid2:drawCell(section, row, column, selected, x, y, width, height)
  gfx.pushContext()
    gfx.drawText('grid2 cell '..column, x + 10, y + 10)
    if selected then
      gfx.drawText('*selected*', x + 10, y + 38)
      gfx.setLineWidth(3)
    end
    gfx.drawRoundRect(x, y, width, height, 4)
  gfx.popContext()
end

Here's what I'd expect to see, with grid2 having selected AND scrolled to column 3:

but instead I see:

Selection is assigned on grid2, but grid1 is scrolled instead! Here is it in motion, starting with a fresh boot, then pressing right twice to move the upper grid1, then A A B B to move the lower grid2:

scrolling incorrectly 2

Now, I've been able to paper over this a few times by manually scrolling another gridview while it's not visible, but I've run into one where I just can't get it to work. The game opens on a view of the room, which is a gridview with each cell showing a different portion of the room. When it boots, I've attempted to scroll the offscreen gridview for the Ingredients Shelf to column 3, attempting to skip 2 empty cells so that the first ingredient appears centered. Here's what that looks like:

spellcorked incorrect ingredients

Now, no matter what I do, or how many times I try to scroll that second grid's position, I can't do it, unless I run an attempt every frame after activating the ingredients shelf, in which case it scrolls on the SECOND frame. Weird!!! If I try to force the ingredient selection's update loop to try scrolling every frame once it's activated, look at this:

spellcorked scrolling grid1 while trying to scroll grid2

The first update trying to scroll grid2 actually scrolls grid1 to its 3rd cell (the one with the book), while the second update actually scrolls grid2, moving the coffee bean into position.

I've tried so many things to try and trick this into happening in a different order, but I can't get it to work. It seems to have to do with the order they're created; but I can't make sense of why grid2 ever receives its scroll commands if they're being intercepted in the first place. Except that manually triggering selectNextColumn() or selectPreviousColumn() with button input always seems to reach the correct gridview. So it seems like something within gridview:scrollCellToCenter().

And the main difference I see between gridview:scrollCellToCenter() and the methods to select next or previous column is that those methods used the local function scrollToSelectedCell(), whereas gridview:scrollCellToCenter() doesn't? That could be where the problem is, now that I'm looking about it? Because I think I've encountered this same bug trying to use gridview:scrollToTop(), which also uses self:setScrollPosition()!

BUT, I've also tried using grid2:selectNextColumn() before it comes into view, and for some reason that doesn't seem to work until after it's visible? So, that's all I've figured out so far!

Any help is, of course, much appreciated!

Just to make sure I'm understanding correctly: is the first gif you posted (the "here it is in motion" one) showing any incorrect behavior? Or are you just demonstrating that it does work properly later, just not when you initially set the scroll position as in the image above?

When the first gif starts, it shows the incorrect behavior of scroll applied to grid1. Then, yeah, I was just showing that the gridview works as expected after that, if you use the select next and previous column methods. But for some reason it doesn't work to just try and use :selectNextColumn() twice instead of scrolling/selecting directly ahead of time.

Figured it out! I'll get the fix added to the next release.

The issue was that if you scroll the grid views before you draw them, we don't know what size they will be so we defer the scroll until the view is first drawn. However, we were storing that information in a single variable rather than one assigned to the grid view itself.

I also noticed there was a weird flash where the grid view was drawing a pixel higher than it should be on the first frame, which turned out to be a separate issue, so I got that fixed as well.

Thanks for finding this, and sorry that you had to waste so much time on workarounds!

3 Likes

This is great, @dan! Thank you!

No sweat about time wasted; this is the process! Gridview itself has saved me a massive amount of time on this project. I'm glad I could help improve it! :smiley: