# Released under the MIT License. See LICENSE for details.
#
"""Provides a top level control settings window."""
from __future__ import annotations
from typing import override
import bascenev1 as bs
import bauiv1 as bui
[docs]
class ControlsSettingsWindow(bui.MainWindow):
"""Top level control settings window."""
def __init__(
self,
transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# FIXME: should tidy up here.
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
self._have_selected_child = False
self._r = 'configControllersWindow'
uiscale = bui.app.ui_v1.uiscale
app = bui.app
assert app.classic is not None
spacing = 50.0
button_width = 350.0
width = 1200.0 if uiscale is bui.UIScale.SMALL else 560.0
height = 800 if uiscale is bui.UIScale.SMALL else 400.0
# yoffs = -60 if uiscale is bui.UIScale.SMALL else 0
space_height = spacing * 0.3
buttons_height = 0.0
# FIXME: should create vis settings under platform or
# app-adapter to determine whether to show this stuff; not
# hard-code it.
show_gamepads = False
platform = app.classic.platform
subplatform = app.classic.subplatform
non_vr_windows = platform == 'windows' and (
subplatform != 'oculus' or not app.env.vr
)
if platform in ('linux', 'android', 'mac') or non_vr_windows:
show_gamepads = True
buttons_height += spacing
show_touch = False
if bs.have_touchscreen_input():
show_touch = True
buttons_height += spacing
show_space_1 = False
if show_gamepads or show_touch:
show_space_1 = True
buttons_height += space_height
show_keyboard = False
if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
show_keyboard = True
buttons_height += spacing
show_keyboard_p2 = False if app.env.vr else show_keyboard
if show_keyboard_p2:
buttons_height += spacing
show_space_2 = False
if show_keyboard:
show_space_2 = True
buttons_height += space_height
if bool(True):
show_remote = True
buttons_height += spacing
else:
show_remote = False
# On windows (outside of oculus/vr), show an option to disable
# xinput.
show_xinput_toggle = False
if platform == 'windows' and not app.env.vr:
show_xinput_toggle = True
if show_xinput_toggle:
buttons_height += spacing
assert bui.app.classic is not None
# Do some fancy math to fill all available screen area up to the
# size of our backing container. This lets us fit to the exact
# screen shape at small ui scale.
screensize = bui.get_virtual_screen_size()
scale = (
2.0
if uiscale is bui.UIScale.SMALL
else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
)
# Calc screen size in our local container space and clamp to a
# bit smaller than our container size.
# target_width = min(width - 60, screensize[0] / scale)
target_height = min(height - 70, screensize[1] / scale)
# To get top/left coords, go to the center of our window and
# offset by half the width/height of our target area.
yoffs = 0.5 * height + 0.5 * target_height + 30.0
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
scale=scale,
toolbar_visibility=(
'menu_minimal'
if uiscale is bui.UIScale.SMALL
else 'menu_full'
),
),
transition=transition,
origin_widget=origin_widget,
# We're affected by screen size only at small ui-scale.
refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
)
self._back_button: bui.Widget | None
if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
edit=self._root_widget, on_cancel_call=self.main_window_back
)
self._back_button = None
else:
self._back_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(35, height - 60),
size=(60, 60),
scale=0.8,
text_scale=1.2,
autoselect=True,
label=bui.charstr(bui.SpecialChar.BACK),
button_type='backSmall',
on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
# We need these vars to exist even if the buttons don't.
self._gamepads_button: bui.Widget | None = None
self._touch_button: bui.Widget | None = None
self._keyboard_button: bui.Widget | None = None
self._keyboard_2_button: bui.Widget | None = None
self._idevices_button: bui.Widget | None = None
bui.textwidget(
parent=self._root_widget,
position=(
width * 0.5,
yoffs - (52 if uiscale is bui.UIScale.SMALL else 32),
),
maxwidth=260,
size=(0, 0),
text=bui.Lstr(resource=f'{self._r}.titleText'),
color=bui.app.ui_v1.title_color,
h_align='center',
v_align='center',
)
# Roughly center the rest of our stuff.
v = height * 0.5 + buttons_height * 0.5 - 10
v -= spacing
if show_touch:
self._touch_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=((width - button_width) / 2, v),
size=(button_width, 43),
autoselect=True,
label=bui.Lstr(resource=f'{self._r}.configureTouchText'),
on_activate_call=self._do_touchscreen,
)
bui.widget(
edit=btn,
right_widget=bui.get_special_widget('squad_button'),
)
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._touch_button
)
if self._back_button is not None:
bui.widget(
edit=self._back_button, down_widget=self._touch_button
)
self._have_selected_child = True
v -= spacing
if show_gamepads:
self._gamepads_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=((width - button_width) / 2 - 7, v),
size=(button_width, 43),
autoselect=True,
label=bui.Lstr(resource=f'{self._r}.configureControllersText'),
on_activate_call=self._do_gamepads,
)
bui.widget(
edit=btn,
right_widget=bui.get_special_widget('squad_button'),
)
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._gamepads_button
)
if self._back_button is not None:
bui.widget(
edit=self._back_button,
down_widget=self._gamepads_button,
)
self._have_selected_child = True
v -= spacing
else:
self._gamepads_button = None
if show_space_1:
v -= space_height
if show_keyboard:
self._keyboard_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=((width - button_width) / 2 - 5, v),
size=(button_width, 43),
autoselect=True,
label=bui.Lstr(resource=f'{self._r}.configureKeyboardText'),
on_activate_call=self._config_keyboard,
)
bui.widget(
edit=self._keyboard_button, left_widget=self._keyboard_button
)
bui.widget(
edit=btn,
right_widget=bui.get_special_widget('squad_button'),
)
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._keyboard_button
)
if self._back_button is not None:
bui.widget(
edit=self._back_button,
down_widget=self._keyboard_button,
)
self._have_selected_child = True
v -= spacing
if show_keyboard_p2:
self._keyboard_2_button = bui.buttonwidget(
parent=self._root_widget,
position=((width - button_width) / 2 - 3, v),
size=(button_width, 43),
autoselect=True,
label=bui.Lstr(resource=f'{self._r}.configureKeyboard2Text'),
on_activate_call=self._config_keyboard2,
)
v -= spacing
bui.widget(
edit=self._keyboard_2_button,
left_widget=self._keyboard_2_button,
)
if show_space_2:
v -= space_height
if show_remote:
self._idevices_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=((width - button_width) / 2 - 5, v),
size=(button_width, 43),
autoselect=True,
label=bui.Lstr(resource=f'{self._r}.configureMobileText'),
on_activate_call=self._do_mobile_devices,
)
bui.widget(
edit=self._idevices_button, left_widget=self._idevices_button
)
bui.widget(
edit=btn,
right_widget=bui.get_special_widget('squad_button'),
)
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._idevices_button
)
if self._back_button is not None:
bui.widget(
edit=self._back_button,
down_widget=self._idevices_button,
)
self._have_selected_child = True
v -= spacing
if show_xinput_toggle:
def do_toggle(value: bool) -> None:
bui.screenmessage(
bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
color=(1, 1, 0),
)
bui.getsound('gunCocking').play()
bui.set_low_level_config_value('enablexinput', not value)
xinput_checkbox = bui.checkboxwidget(
parent=self._root_widget,
position=(
width * (0.35 if uiscale is bui.UIScale.SMALL else 0.25),
v + 3,
),
size=(120, 30),
value=(not bui.get_low_level_config_value('enablexinput', 1)),
maxwidth=200,
on_value_change_call=do_toggle,
text=bui.Lstr(resource='disableXInputText'),
autoselect=True,
)
bui.textwidget(
parent=self._root_widget,
position=(width * 0.5, v - 5),
size=(0, 0),
text=bui.Lstr(resource='disableXInputDescriptionText'),
scale=0.5,
h_align='center',
v_align='center',
color=bui.app.ui_v1.infotextcolor,
maxwidth=width * 0.8,
)
bui.widget(
edit=xinput_checkbox,
left_widget=xinput_checkbox,
right_widget=xinput_checkbox,
)
v -= spacing
self._restore_state()
[docs]
@override
def get_main_window_state(self) -> bui.MainWindowState:
# Support recreating our window for back/refresh purposes.
cls = type(self)
return bui.BasicMainWindowState(
create_call=lambda transition, origin_widget: cls(
transition=transition, origin_widget=origin_widget
)
)
[docs]
@override
def on_main_window_close(self) -> None:
self._save_state()
def _set_mac_controller_subsystem(self, val: str) -> None:
cfg = bui.app.config
cfg['Mac Controller Subsystem'] = val
cfg.apply_and_commit()
def _config_keyboard(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# no-op if we're not in control.
if not self.main_window_has_control():
return
self.main_window_replace(
ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#1'))
)
def _config_keyboard2(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# no-op if we're not in control.
if not self.main_window_has_control():
return
self.main_window_replace(
ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#2'))
)
def _do_mobile_devices(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
# no-op if we're not in control.
if not self.main_window_has_control():
return
self.main_window_replace(RemoteAppSettingsWindow())
def _do_gamepads(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
# no-op if we're not in control.
if not self.main_window_has_control():
return
self.main_window_replace(GamepadSelectWindow())
def _do_touchscreen(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
# no-op if we're not in control.
if not self.main_window_has_control():
return
self.main_window_replace(TouchscreenSettingsWindow())
def _save_state(self) -> None:
sel = self._root_widget.get_selected_child()
if sel == self._gamepads_button:
sel_name = 'GamePads'
elif sel == self._touch_button:
sel_name = 'Touch'
elif sel == self._keyboard_button:
sel_name = 'Keyboard'
elif sel == self._keyboard_2_button:
sel_name = 'Keyboard2'
elif sel == self._idevices_button:
sel_name = 'iDevices'
else:
sel_name = 'Back'
assert bui.app.classic is not None
bui.app.ui_v1.window_states[type(self)] = sel_name
def _restore_state(self) -> None:
assert bui.app.classic is not None
sel_name = bui.app.ui_v1.window_states.get(type(self))
if sel_name == 'GamePads':
sel = self._gamepads_button
elif sel_name == 'Touch':
sel = self._touch_button
elif sel_name == 'Keyboard':
sel = self._keyboard_button
elif sel_name == 'Keyboard2':
sel = self._keyboard_2_button
elif sel_name == 'iDevices':
sel = self._idevices_button
elif sel_name == 'Back':
sel = self._back_button
else:
sel = (
self._gamepads_button
if self._gamepads_button is not None
else self._back_button
)
bui.containerwidget(edit=self._root_widget, selected_child=sel)
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum