ducalex/retro-go

Add additional scrolling modes to the launcher (stick, center, page, etc)

IndiePix opened this issue · 9 comments

Is your feature request related to a problem? Please describe.
I'm trying to solve a little visual situation on the game selection list, which is not really important for the functionality of the list itself but would make it look a lot cleaner imo.

The problem I found is that the selection cursor is always in the middle, even on top and bottom positions. That make the list cut from the top when the selector is inferior to the number of lines / 2 and cut from the bottom when the selector is above lines / 2. When the cursor is moving on the middle of the list is fine like it is (cursor in the middle and list moving around it), but when approachs the first positions of the list or the last positions, the list will shrink until the cursor ends in the middle with a blank space either on top if it's the first position or bottom if the cursor is in the last position.

Describe the solution you'd like
This image will describe perfectly what happens in the current state of the launcher (on the left of the image), and the desired display of the list when the selector is on the first element of the list (right of the image):
listbeforeafter

Describe alternatives you've considered
I think this shrinking of the list comes from this line because it tries always to maintain the cursor on the center:

top += ((gui.height - top) - (lines * line_height)) / 2;

I'm not really sure, but I think the condition would be something like (sorry for the pseudocode):

if (cursor position < middle of the displayed list && first element of the whole list is visible) {
//Move the cursor to the top so the list would not shrink into the end top position.
}

if (cursor position > middle of the displayed list && last element of the whole list is visible ){
//Move the cursor to the bottom so the list would not shrink into the end bottom position.
}

I'm not really sure if you got the idea or even the issue I'm stating here, as it is a little detail that doesn't add much to the whole launcher GUI but I think it would look a lot cleaner that way as the list will start with a full list from top to bottom instead of a cropped in the middle starting in the middle list.

I will try to do it myself, but I have first to understand first how the code is structured, pointers etc... So I need help to understand how to to put those conditions on the draw list function.

I agree having the cursor move instead of the whole list might feel more natural to some people. I'd be happy to have it as an option. In the future it could become the default or even only way, if people truly prefer it.

I should have time to have a look in a few days (if you haven't already figured it out by then :)).

Edit: Bulk of the changes needed would probably be in gui.c / mainly gui_draw_list, as you found out already.

I love that retro-go is always about options :)

The first type of list can be the classical page by page scrolling: The cursor scrolls through page 1 and then when arrives to the end element of page 1 render the next page 2 ... till last page and then again page 1. This kind of scrolling is really good to do PAGE_SCROLLING, as it will always render the same page like a book no matter the position of the cursor. Also, with page scrolling we can add up an alphabetical page scrolling mode pretty easily which renders pages alphabetically: A,B,C,D,E....

The second type would be like the one it is implemented right now: The list scrolls around the cursor which is static in the middle of the list. Here is where we can add the new condition: The cursor will scroll up and break its static position to the first element of the list whenever this first element is seen on the screen, so we prevent like this that the first element moving towards the cursor and eating up all the elements until it leaves a blank space. The same process can be made with the last position of the list, whenever the screen renders the last position of the whole list, instead of moving that last element towards the cursor shrinking all the last positions and leaving the blank space on the bottom, the cursor will move towards the last position so it will maintain the list intact without any shrinking.

Those conditions will also meet when both first element and last element of the list are displayed in the same page, in this case the cursor will scroll freely across the static full list.

So if I understand correctly you're suggesting three modes total:

  • Sticky center: Current mode, the cursor is always in the center and the list scrolls underneath
  • Sticky edges: The cursor can move freely on the current view, but if it tries to go beyond the edges then the list starts scrolling underneath
  • Paging: The page changes when the cursor goes beyond the last element on screen and the cursor is reset to the first element of the new page

I've added the paging mode as it was pretty straightforward but I'll let you look into adding the other scrolling behavior(s) if you wish!

I've suggested two, but your model is totally fine too.
What I was trying to do is to solve the problem of sticky center shrinking the list when the cursor is on the first positions (shrinks the list from top to the center) or to the last positions (shrinks the list from the bottom to the center). To solve that it is pretty straightforward, when the first element of the whole list of games is shown in the screen, that means that if the cursor moves towards that firsts element, the list will start to shrink.

So the solution is simple, lets imagine a list of 50 elements:
1. Moves freely if cursor moves from the first position to the middle of the the first displayed list.
When the first element of the list is shown and the cursor moves towards that first element: move the cursor freely towards the first position. This will avoid the list shrinking from the first element towards the center cursor, instead the cursor moves.

2. When the displayed list doesn't have the first position neither the last one (50th position), scroll sticky center cursor.
When the cursor moves away from the first position freely and leaves behind the first position, instead of waiting until the edge of the list to start sticky scrolling on the edge, scroll sticking the cursor on the center.

3. Moves freely the cursor if it is moving towards the last position.
Finally, when the last element of the list (the 50th) appears on the screen and the cursor moves towards that position, the cursor will break the sticky center position and start freely scrolling towards the last positions. That way the list would not shrink when the cursor approach the last position of the list.

I tried my best to explain the behaviour I think is the most optimal for a natural cursor on the center scrolling overall. It is really similar to the sticky edges, that only the cursor moves freely when goes to the start or the end of the list so the list doesn't shrink towards the cursor.

I'm sorry if it was too dense, the paging mode is exactly like I said and the sticky edges is actually a nice option, I didn't thought about it. Anyways with the code you've added it would be pretty simple to implement what I've explained, it's just taking the sticky center and add a condition like the sticky edges does.

The three modes are good options, I'd just correct that part of the sticky center mode so it doesn't glitch on those situations.

Oh okay I think I understand what you're suggesting. I guess what you're describing would be a true "sticky" center and the sticky center I described should be called "fixed" center!

The mode you're describing (and the sticky edge I described) will definitely need more changes to the code because we'll need to track the list offset and the cursor separately.

I don't think it will be very difficult but I personally do not have the time at the moment. If you do the work I'll be happy to merge, otherwise I may come back to it myself later!

Actually I think we can achieve something close to what you describe without tracking cursor/offset separately.

It's a bit buggy (especially on short lists) but I've pushed a demo to a temp branch so you can test 955a106

I made time ago an odroid-go breadboard setup for testing this launcher as I don't have any odroid gos right now, I will make it again to see the behaviour of the scrolling and experiment with the code, it's been months since I didn't check this launcher.

Do you mind to explain me briefly this two lines?

Line 1 (475 on gui.c)
if (list->cursor > list->length - (lines / 2)

Line2 (4777 on gui.c)
line_offset = list->length - lines;

List->cursor points directly to the cursor position I guess, but is list->length refering to the length of the list on the display or absolute length?

Also, is line_offset where the list "pivots" around when the cursor is moving? For example:
line_offset = 0 means that the position of the list will be static on the highest position? I think I don't understand clearly how it works.

Once I understand those bits of code I guess I can work on different types of scrolling and experiment. Maybe sticky edge scrolling is easier than center because the condition looks simpler (the list is always pushed full and the cursor always on the edge), but I will test all of this soon.

  • list->length is the absolute length of list->items
  • list->cursor is the index of the selected item in list->items
  • lines is the number of displayed lines/items
  • line_offset is the index of the first item displayed

Admittedly some of those variables could have better names.

The conditions:

// If the cursor is somewhere in the first half page then we have a fixed offset of 0, this is what prevents emptiness when we reach the top of the list
if (list->cursor < lines / 2)
    line_offset = 0;

// If cursor is somewhere in the last half page then we have an offset fixed to the top of the last page, this prevents emptiness when we reach the bottom of the list
else if (list->cursor > list->length - (lines / 2))
    line_offset = list->length - lines;

// Otherwise we stick around the center
else
    line_offset = list->cursor - (lines / 2);

I think you'll just have to experiment and find what is most natural scrolling that you're trying to achieve.

Ah ok, thanks for the clarification, now it makes sense to me. Those three conditions look good to me, as they are checking if the cursor is either on the absolute first half page or the absolute last half page, solving that way the shrinking effect while maintaining the fixed center scrolling in between.

I would also try adding a pre-condition previous to entering on those two/three to deal with short lists that doesn't fill up an entire page or just fill up a page:

// list->length will not fill more than one visible page, so it will be always equal to visible lines
if (list->length == lines) 
    // line_offset same as PAGING MODE, in which the cursor moves freely in an static page. Maybe is it better line_offset = 0?
    line_offset = (list->cursor / lines) * lines;  
// The rest of conditions when there's more than lines
else ....

The condition avoids the cursor scrolling the page as there's no need to scroll when list->length isn't bigger than the max visible lines on display. In this case, the cursor will move freely around the list and return to the first position if it passes the last element without moving the displayed page or vice-versa. This will ensure that list->cursor < lines / 2 and list->cursor > list->length - (lines / 2) will not bug the list offset on those short list situations were the cursor can meet those two conditions and move the line offset erratically.

I can't think of another condition in which the sticky center mode will misbehave, what do you think? Did you encounter any bugs while scrolling apart from short lists?

It would be interesting to add Edge scrolling too, will work on that after solving scrolling center mode.