peterhinch / micropython-micro-gui

A lightweight MicroPython GUI library for display drivers based on framebuf, allows input via pushbuttons. See also micropython-touch.
MIT License
247 stars 37 forks source link

Specific fonts throwing 'ValueError: col is out of range' on buttons #45

Closed AaronMittelmeier closed 6 months ago

AaronMittelmeier commented 6 months ago

When I attempt to use Arial 35 on a button in my base screen class, I am getting an unexpected error message: 'ValueError: col is out of range'. When I change to freesans20 on the exact same code, it runs clean. To demonstrate, change buttonWriter variable - font20 -> arial35 . This was also happening on courier20. Strangely, the listbox, is fine at arial35. What am I missing? Hardware is the 240x240 Waveshare 1.3 Pico LCD ST7789.

import hardware_setup  # Instantiate display, setup color LUT (if present)
from time import sleep
from gui.core.ugui import Screen, ssd

from gui.widgets import Label, Button, CloseButton, Listbox

# The writer
from gui.core.writer import CWriter # Color Writer
import gui.fonts.arial10 as arial10 # Small as shit
import gui.fonts.freesans20 as font20 # Better
import gui.fonts.arial35 as arial35 # Ideal but large

from gui.core.colors import *

from dice_roller import dice

menuWriter = CWriter(ssd, font20, GREEN, BLACK, verbose=False)
buttonWriter = CWriter(ssd, font20, GREEN, BLACK)

def dice_roll(dice_count=1,dice_sides=6):
    rolls_list = dice.throw_dice(dice_count, dice_sides)
    return rolls_list

def cb(listbox, count_and_sides):
    rolls_total = 0
    rolls_list = dice_roll(count_and_sides[0], count_and_sides[1])

    for roll in rolls_list:
        rolls_total += roll

    print('Fob Roll List : ', str(rolls_list))
    print('Fob Roll Total : ', str(rolls_total))

    Label(menuWriter, 180, 4, 'Rolls : ' + str(rolls_list))
    Label(menuWriter, 220, 4, 'Total : ' + str(rolls_total))
    #Button(arial35, 60, 60, text='Text', callback=go_back, args=(' ',))

def go_back(button, arg):
    Screen.change()

def go_to_menu(button, arg):
    Screen.change(BaseScreen)

class d20screen(Screen):
    def __init__(self):
        super().__init__()

        els = (('2d20', cb, ([2, 20],)),
               ('1d20', cb, ([1, 20],))
               )

        Listbox(menuWriter, 2, 2,
                elements = els, dlines=2, select_color=YELLOW, bdcolor=RED, value=0, also=Listbox.ON_LEAVE)
                #bdcolor = RED, fgcolor=RED, fontcolor = YELLOW, select_color=BLUE, value=1)
        CloseButton(menuWriter)

class d6screen(Screen):
    def __init__(self):
        super().__init__()

        els = (('1d6', cb, ([1, 6],)),
               ('2d6', cb, ([2, 6],)),
               ('3d6', cb, ([3, 6],)),
               ('4d6', cb, ([4, 6],)),
               ('5d6', cb, ([5, 6],)),
               ('6d6', cb, ([6, 6],)),
               ('8d6', cb, ([8, 6],)),
               )
        Listbox(menuWriter, 2, 2,
                elements = els, dlines=7, select_color=YELLOW, bdcolor=RED, value=0, also=Listbox.ON_LEAVE)
                #bdcolor = RED, fgcolor=RED, fontcolor = YELLOW, select_color=, value=1)
        CloseButton(menuWriter)

class BaseScreen(Screen):
    def __init__(self):
        def d20screenInitiate(button, virtualDice):
            Screen.change(d20screen)

        def d6screenInitiate(button, virtualDice):
            Screen.change(d6screen)

        super().__init__()

        col = 2
        row = 2
        Label(buttonWriter, row, col, 'Fob20 Handy Roller')
        row = 50
        Button(buttonWriter, row, col, text='d20', callback=d20screenInitiate, args=('d20',))
        col += 60
        Button(buttonWriter, row, col, text='d6', callback=d6screenInitiate, args=('d6',))
        CloseButton(buttonWriter)  # Quit the application       

class TitleScreen(Screen):
    def __init__(self):
        super().__init__()
        wri = CWriter(ssd, arial35, GREEN, BLACK, verbose=False)
        Button(wri, 100, 64, text='Fob 20', callback=go_to_menu, args=(' ',))

def test():
    print('Testing micro-gui...')
    Screen.change(TitleScreen)

test()

And this is the dice_roller function for completeness:

from random import randint

class dice():
    def throw_dice(num_dice=1, side_dice=6):
        print("Rolling " + str(num_dice) + "x " + str(side_dice) + "-sided dice")
        list_of_rolls = []

        for x in range(1, num_dice+1):
            this_roll = randint(1, side_dice)
            list_of_rolls.append(this_roll)

        print('Roll list in function : ' + str(list_of_rolls))

        return list_of_rolls
peterhinch commented 6 months ago

I suggest you specify button dimensions (height and width) for larger fonts. [EDIT] The "col is out of range" error comes from the writer object: it is trying to render a character which extends beyond the boundaries of the screen. Please check your screen layout.

AaronMittelmeier commented 6 months ago

Initially, that's what I thought too, but I played with it for a long time to make sure that wasn't the case. With that in mind, as soon as I added the width parameters, as you advised, it worked as expected. I changed nothing else about the code (ie., those buttons hadn't moved), so what was it about the width parameter that enabled these to successfully render? My suspicion was that the default width of 50 in the button class (+ 10 ) may not be large enough to handle a font with a default size of '35'. After I modified default width to be 100, I could run the same code successfully. Long story short - I think the character boundary was related to button size, not screen size, in my circumstance.

Regardless, thank you again Sir! Your advice led to the resolution of this issue. I have added widths to the corresponding widgets and was successful in making those fonts bigger for these poor old eyes of mine.

peterhinch commented 6 months ago

That is odd, because I did a grep on the whole library and the only place that the "col is out of range" text occurs is here where the comparison is with the screen boundary.

The Button class adapts its width to accommodate the passed text here. In principle the error could occur if the length of the passed text caused the button to extend past the width of the screen, but it doesn't seem to be the case here.

In my own applications I always specify the size of widgets to achieve a desired screen layout.

AaronMittelmeier commented 6 months ago

Well, honestly, having default dimensions are convenient but you're right, for a cleaner app, the way to go is specificity. Anyway, thanks once more. I'm gonna close this one out