Skip to content
Feniks Development
  • Start Here
  • Glossary
  • Tools
  • Resources
  • What’s New
  • About
  • Contact
Toggle the button to expand or collapse the Menu

FocusDisplayable

  1. Home>
  2. Feniks Tools>
  3. FocusDisplayable

Easy Blinking

  • EasyBlink Class
  • EasyBlink Examples

Controller Support Expansion

  • What is the Controller Support Expansion?
  • How do I…? + Common Issues
  • Controller Viewport
  • Controller Bar
  • Virtual Cursor
  • Virtual Keyboard
  • StickEvent
  • KeyController and focused_on
  • FocusDisplayable
  • Remapping Controls
  • Controller and Keyboard Icons
  • Configuration Variables
  • Screen Actions and Values
  • Helper Functions and Classes
  • Engine Override Notes

Sound Disabler and Captions Tool

  • Disabling Sounds and Sound Categorization
View Categories
  • Home
  • Feniks Tools
  • Controller Support Expansion
  • FocusDisplayable

FocusDisplayable

9 min read

Controller Support Expansion for Ren’Py includes several features to improve controller and keyboard support in Ren’Py. Pick up the tool from itch.io if you haven’t already:

Contents hide
Examples
Example 1
Example 2
Example 3
Properties
d
padding
hide_on_mouse
linger_on_focused
active_area
xwarper
ywarper
xtime
ytime
recheck_period
displayables

The FocusDisplayable class is a special kind of displayable which will follow the currently focused button/bar/other focusable element. It can be used to draw attention to the focused item by using a cursor to point to it or a frame to highlight it.

Examples

Example 1

The first example is a simple arrow which will point to the left side of the currently focused item.

A main menu screen. The Start button is in a column on the left, highlighted with orange text and a white arrow on its left side pointing to it.
## Declare the FocusDisplayable
image focus_right_arrow = FocusDisplayable(
    Transform("right_arrow", anchor=(1.0, 0.5), pos=(0.0, 0.5)))

## Note: the main menu screen has been slightly simplified for brevity
screen main_menu():
    tag menu
    add "main_menu_background"

    vbox:
        xpos 60 yalign 0.5 spacing 6
        textbutton _("Start") id 'start' action Start() default_focus 10
        textbutton _("Load") id 'load' action ShowMenu("load")
        textbutton _("Preferences") id 'preferences' action ShowMenu("pref_display")
        textbutton _("About") id 'about' action ShowMenu("about")
        textbutton _("Help") id 'help' action ShowMenu("help")
        textbutton _("Quit") id 'quit' action Quit(confirm=not main_menu)

    use key_footer():
        icon_button kind icn.select suffix "small"

    ## Add the FocusDisplayable from earlier
    add 'focus_right_arrow'

Let’s break down the declaration:

image focus_right_arrow = FocusDisplayable(    
    Transform("right_arrow", anchor=(1.0, 0.5), pos=(0.0, 0.5)))

The first thing passed to the FocusDisplayable is a displayable. Namely, the image of the arrow. In particular, it is wrapped in a Transform so we can tell Ren’Py where it will be positioned relative to the button it’s pointing to. If you’re not familiar with the pos and anchor properties, Ren’Py Position Properties – Pos and Anchor will help you with those! Basically, this is saying that the right side of the arrow should be centered along the left side of the button it’s pointing to.

And that’s all! The only thing left to do is add it to the main menu screen where we wanted to use it, which was done with the line add "focus_right_arrow" at the bottom. It’s important that the FocusDisplayable goes at the very bottom of the screen (or close to it) so it appears on top of any buttons it’s highlighting.

Example 2

Now let’s look at an example that has some animation. FocusDisplayable has several properties that can be used to add some movement.

A main menu screen with the Start, Load, Preferences, About, Help, and Quit buttons in a column on the left. Each button is selected starting from the top. A white arrow follows the selected button on the left.
image focus_right_arrow = FocusDisplayable(
    Transform("right_arrow", anchor=(1.0, 0.5), pos=(0.0, 0.5)),
    xtime=0.1, xwarper="ease", ytime=0.1, ywarper="ease", 
    hide_on_mouse=True)

We’ve added several new properties to this declaration. Let’s look at what they do.

xtime and ytime are how long the displayable should take to animate from one focus to another, in seconds. Usually this is pretty short, so the displayable isn’t spending several seconds floating across the screen to the next button.

Next, xwarper and ywarper are warpers to use to animate that movement with. By default, this is "linear", so it just moves at a consistent speed. Here we’ve set it to "ease", which starts slow, speeds up, and ends slow again. You can read more about the existing warpers in the Ren’Py documentation here. This can also be a function that takes in a number from 0-1 and returns a float.

Finally, the property hide_on_mouse=True means that if the mouse is used, the FocusDisplayable will disappear. It reappears when the keyboard or a controller is used to focus something. This can be useful if you’re using the FocusDisplayable to draw additional attention to the focused item, but it would be too distracting for a mouse user who already has the cursor to draw their attention.

You can see from the animation above that this means the arrow spends a bit of time animating to the next focused item rather than simply appearing there.

Example 3

Now let’s look at an example with a Frame rather than a fixed image, which is restricted to a particular area.

An audio preferences screen. The highlighted buttons and bars have a pink rectangle around them.
screen pref_audio():
    tag menu
    default f1 = FocusDisplayable(Frame("pink_rect", 5, 5),
        hide_on_mouse=True, padding=(10, 8),
        active_area=(350, 200, config.screen_width-350, config.screen_height-200))

    use preferences_common("pref_audio"):
        side 'c r':
            style_prefix 'pref'
            controller_viewport:
                id 'pref_display_vp'
                vscroll_style "center" 
                ## IMPORTANT!
                focus_displayables f1
                scroll_delay (0.1, 0.1)
                mousewheel True draggable renpy.variant("touch")
                has vbox
                ## Contents omitted for brevity

            vbar value YScrollValue("pref_display_vp") keyboard_focus False

    add f1 ## IMPORTANT!

First, let’s break down the FocusDisplayable declaration. Here it’s declared as a variable rather than an image. Both work well!

default f1 = FocusDisplayable(Frame("pink_rect", 5, 5),
    hide_on_mouse=True, padding=(10, 8),
    active_area=(350, 200, config.screen_width-350, config.screen_height-200))

First is the displayable – in this case, it’s a pink rectangle using Frame so it’s expandable. See How to make resizeable backgrounds in Ren’Py with Frame for more on Frame!

Next, hide_on_mouse=True is as we saw before – it means the displayable doesn’t appear when the mouse is used.

padding=(10, 8) is new. Each button and bar that can be focused has a “hitbox” – a rectangle around its coordinates that defines the area of the button or bar. The padding property lets us define some extra padding around this hitbox so the rectangle isn’t right up against the button/bar area. You can provide it either as an (xpadding, ypadding) pair, or as (left_padding, top_padding, right_padding, bottom_padding). It works the same way as the padding property of frame – see Ren’Py Screen Language Basics – Frames for more on that. So, there is 10 extra pixels of space to the left/right of the buttons and bars, and 8 pixels to the top/bottom.

Lastly, there’s active_area. This declares an (x, y, width, height) tuple defining the area where the FocusDisplayable should appear. In this case, it’s the area where the viewport is. We don’t want the FocusDisplayable appearing over the menu options to the left, or the tabs at the top, just the viewport with the audio sliders and buttons.

The other thing to note is that this screen uses a controller_viewport (see Controller Viewport). As a result, when scrolling the focus coordinates (hitbox) of the focused button can change over time. To make sure the FocusDisplayable is updated as the hitbox coordinates are changing, you should provide the FocusDisplayable to the controller_viewport with the focus_displayables property. In this case we declared the FocusDisplayable as default f1 = FocusDisplayable(...), so the property is set to focus_displayables f1. If we’d instead declared this outside the screen as image f1 = FocusDisplayable(...) then we’d pass it in as a string e.g. focus_displayables "f1".

Finally, note the add f1 at the bottom of the screen as before, so the FocusDisplayable appears on top of everything.

Properties

Now that you’ve seen several examples of the FocusDisplayable in use, let’s look at all the properties available.

d

A displayable which will follow the currently focused displayable.

e.g. d="my_pointer"

d=Transform("bouncy_arrow", anchor=(0.0, 1.0), pos=(1.0, 0.0))

d=Frame("my_rect", 10, 10)

padding

Padding which will be added around the “hitbox” of the currently focused displayable. (0, 0) by default (no padding). Positive numbers increase the hitbox size away from the center, and negative numbers shrink it towards the center. You can provide padding either as an (xpadding, ypadding) tuple or as (left_padding, top_padding, right_padding, bottom_padding).

e.g. padding=(10, 8)

padding=(5, 5, 5, 5)

hide_on_mouse

If True, the FocusDisplayable will be hidden while the mouse is used. This is in real-time – it will reappear as soon as the keyboard or controller are used to focus something and disappear as soon as the mouse is moved. False by default.

e.g. hide_on_mouse=True

linger_on_focused

If True, the FocusDisplayable will stay on the last-focused item until a new one is focused, at which point it will animate over to the newly focused item. This is only relevant when hide_on_mouse is False, because the keyboard and controllers can only go from one focused item to the next/there is no such thing as hovering over something that doesn’t have focus. False by default.

e.g. linger_on_focused True

active_area

An (x, y, width, height) tuple where the FocusDisplayable should be active in. If buttons or bars etc. are focused outside of this area, the FocusDisplayable will not highlight them. A tuple like (100, 200, 400, 500) means that the area starts at (100, 200) and is 400 pixels wide and 500 pixels tall.

e.g. active_area=(100, 200, 400, 500)

This will take floats, integers, absolutes, and position as seen elsewhere in Ren’Py; see Position in the Ren’Py docs.

xwarper

The warper to use for scrolling animations in the x direction. Can be a string like "linear", in which case it must be the name of one of the built-in warpers (see https://www.renpy.org/doc/html/transforms.html#warpers). Otherwise, it can be a callable which will be passed a value between 0.0 and 1.0 and is expected to return a float. Default value is "linear".

e.g. xwarper="easein"

ywarper

The warper to use for scrolling animations in the y direction. Takes the same arguments as xwarper above.

e.g. ywarper="linear"

xtime

How long it takes to animate to the new position in the x direction, in seconds. A float. Set to 0.0 (the default) for no animation.

e.g. xtime=0.2

ytime

How long it takes to animate to the new position in the y direction, in seconds. A float. Set to 0.0 (the default) for no animation.

e.g. ytime=0.1

recheck_period

How long to wait before checking for a focus position change, in seconds. Default is 0.25. If you’re passing your FocusDisplayable to any controller_viewport that use it (see the focus_displayables property of Controller Viewport), you usually won’t need to adjust this.

e.g. recheck_period=0.1

displayables

Optionally, the FocusDisplayable can change based on the mouse property of the focused item. See the mouse property docs on Ren’Py. If provided, this is a dictionary of event name : Displayable pairs which correspond to cursors which should be used for the provided event. If the d property is not provided, this dictionary must have at least the "default" key in it.

The events are as for the built-in Hardware Mouse Cursor as seen in the Ren’Py documentation: https://www.renpy.org/doc/html/mouse.html#hardware-mouse-cursor

e.g.

image focus_rect = FocusDisplayable(
    displayables=dict(
        default=Frame("pink", 5, 5),
        pressed_default=Frame("red", 5, 5),
        investigate=Frame("yellow", 5, 5),
    )
)
Updated on February 9, 2025
KeyController and focused_onRemapping Controls

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© Copyright – Feniks with OceanWP
Close Menu