init python in wheelmenu: import math def pos(num_buttons): reference_num_buttons = 32 reference_radius = 0.5 radius = reference_radius * (num_buttons / reference_num_buttons) ** 0.5 angle_step = 2 * math.pi / num_buttons start_angle = angle_step / 2 if num_buttons % 2 else 0 positions = [(min(max(0.5 + math.cos(start_angle + i * angle_step) * radius, 0.0), 1.0), min(max(0.5 + math.sin(start_angle + i * angle_step) * radius, 0.0), 1.0)) for i in range(num_buttons)] return positions def button(displayable, action, condition, state=None, **kwargs): """ displayable - Ren'Py displayable action - Ren'Py screen action condition - Boolean state - None - No change "hidden" - Hidden if condition==False "disabled" - Disabled if condition==False """ if state == "hidden": return None elif state == "disabled": return renpy.store.Button(displayable, action=action, style="wheelmenu_disabled_button", **kwargs) if condition: return renpy.store.Button(displayable, action=action, style="wheelmenu_button", **kwargs) else: return None init python: def create_wheelmenu(elements): # Proxy function; We cannot evaluate in a named store without side effects. buttons = [] for name, (displayable, action, condition) in elements.items(): condition = eval(condition) buttons.append(wheelmenu.button(displayable, action, condition, tooltip=name)) positions = wheelmenu.pos(len(buttons)) return tuple(zip(buttons, positions)) # Nonhashable types cannot be used in a screen, so we use a tuple instead. config.per_frame_screens.append("wheelmenu") label wheelmenu(btns, ret, pos=None): call screen wheelmenu(btns, pos) jump expression ret screen wheelmenu(btns, pos): layer "interface" tag wheelmenu zorder 4 style_prefix "wheelmenu" $ mpos = renpy.get_mouse_pos() default start_pos = pos or mpos use close_button_background(keysym="game_menu") window at wheelmenu_anim: id "wheelmenu" pos start_pos for btn, pos in btns: add btn pos pos add "wheelmenu_genie" align (0.5, 0.5) at transform: subpixel True xysize (48, 48) yzoom (-1 if mpos[0] > start_pos[0] else 1) rotate (math.degrees(math.atan2(mpos[1] - start_pos[1], mpos[0] - start_pos[0])) + 360) % 360 - 180 style wheelmenu_window is empty: background Transform("wheelmenu_gradient", align=(0.5, 0.5), xysize=(200, 200)) maximum (400, 400) anchor (0.5, 0.5) style wheelmenu_button is empty: background Transform("wheelmenu_button", xysize=(48,48)) hover_background At(Transform("wheelmenu_button_opaque", xysize=(48,48)), wheelmenu_hover_anim) xysize (48, 48) anchor (0.5, 0.5) style wheelmenu_disabled_button is wheelmenu_button: background "#ffffff80" xysize (48, 48) anchor (0.5, 0.5) style wheelmenu_window_text is default: anchor (0.5, 0.5) color "#fff" size 10 outlines [(1, "#00000080", 1, 0)] style wheelmenu_button_text is wheelmenu_text transform wheelmenu_anim: on show: zoom 0.0 easein 0.15 zoom 1.0 on hide: easeout 0.15 zoom 0.0 transform wheelmenu_hover_anim(t=2.0, strength=0.2, pause=0.0): matrixcolor BrightnessMatrix(value=0.0) linear t/2 matrixcolor BrightnessMatrix(value=strength) linear t/2 matrixcolor BrightnessMatrix(value=0.0) pause pause repeat # transform tooltip_follow: # events False # function tooltip_func # init python: # def tooltip_func(trans, st, at): # x, y = renpy.get_mouse_pos() # if trans.pos is not (x, y): # cw, ch = trans.child.window_size # xanchor = 1.0 if (x + int(cw)) > (config.screen_width) else 0.0 # yanchor = 1.0 if (y + int(ch)) > (config.screen_height) else 0.0 # xoffset = 18 if xanchor else 0 # yoffset = 24 if yanchor else 0 # trans.pos = (x, y) # trans.anchor = (xanchor, yanchor) # trans.offset = (xoffset, yoffset) # return 0 # if not renpy.android: # config.always_shown_screens.append("tooltip") # config.per_frame_screens.append("tooltip")