Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions OptionsCreator.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def check_random(value: typing.Any):
return value


def player_name_length(text: str):
replaced = text.replace("{player}", "0").replace("{PLAYER}", "0").replace("{number}", "0").replace("{NUMBER}", "0")
return len(replaced.strip())


class TrailingPressedIconButton(ButtonBehavior, RotateBehavior, MDListItemTrailingIcon):
pass

Expand Down Expand Up @@ -163,6 +168,41 @@ def insert_text(self, substring, from_undo=False):
return super().insert_text(re.sub(self.pat, "", substring), from_undo=from_undo)


class ComputedLengthTextField(ResizableTextField):

@duckboycool duckboycool Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not a fan of copying over these methods like this, but I couldn't really see another way to adjust the behvaior. Because it's kivy, temporarily changing the text value itself causes it to re-call the same listeners and die.

length_func: typing.Callable[[str], int]

def set_max_text_length(self) -> None:
if self._max_length_label:
length = self.length_func(self.text)

self._max_length_label.text = ""
self._max_length_label.text = f"{length}/{self._max_length_label.max_text_length}"
self._max_length_label.texture_update()
max_length_rect = self.canvas.before.get_group("max-length-rect")[0]
max_length_rect.texture = None
max_length_rect.texture = self._max_length_label.texture
max_length_rect.size = self._max_length_label.texture_size
max_length_rect.pos = (
(self.x + self.width)
- (self._max_length_label.texture_size[0] + dp(16)),
self.y - dp(18),
)

def _get_has_error(self) -> bool:
if (
self._max_length_label
and self._max_length_label.max_text_length is not None
and self.length_func(self.text) > self._max_length_label.max_text_length
):
has_error = True
else:
if all((self.required, len(self.text) == 0)):
has_error = True
else:
has_error = False
return has_error


class VisualListSetCounter(MDDialog):
button: MDIconButton = ObjectProperty(None)
option: typing.Type[OptionSet] | typing.Type[OptionList] | typing.Type[OptionCounter]
Expand Down Expand Up @@ -258,7 +298,7 @@ class OptionsCreator(ThemedApp):
main_panel: MainLayout
player_options: MainLayout
option_layout: MainLayout
name_input: ResizableTextField
name_input: ComputedLengthTextField
game_label: MDLabel
current_game: str
options: typing.Dict[str, typing.Any]
Expand Down Expand Up @@ -301,7 +341,7 @@ def export_options_background(self, options: dict[str, typing.Any]) -> None:
raise

def export_options(self, button: Widget) -> None:
if 0 < len(self.name_input.text) < 17 and self.current_game:
if 0 < player_name_length(self.name_input.text) <= 16 and self.current_game:
import threading
options = {
"name": self.name_input.text,
Expand Down
25 changes: 25 additions & 0 deletions WebHostLib/static/assets/playerOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ window.addEventListener('load', async () => {
// Handle changes to presets select
document.getElementById('game-options-preset').addEventListener('change', choosePreset);

// Do length check for player name
const nameInput = document.getElementById('player-name');
nameInput.removeAttribute('maxlength');
nameInput.addEventListener('change', checkName);

// Save settings to localStorage when form is submitted
document.getElementById('options-form').addEventListener('submit', (evt) => {
const playerName = document.getElementById('player-name');
Expand Down Expand Up @@ -326,6 +331,26 @@ const applyPresets = (presetName) => {
saveSettings();
};

/**
* Set the player name warning message to display or not depending on length check
* @param evt
*/
const checkName = (evt) => {
const name = evt.target.value;
const warning = document.getElementById('name-warning');
warning.hidden = minNameLength(name) <= 16;
};

/**
* Calculate how long the player name may be if each tag like {player} is replaced by a single char
* @param {string} name The name to be checked
* @returns {number} Computed lower bound on character length of name
*/
const minNameLength = (name) => {
const replaced = name.replaceAll(/{(player|PLAYER|number|NUMBER)}/g, '0');
return replaced.trim().length;
};

const showUserMessage = (text) => {
const userMessage = document.getElementById('user-message');
userMessage.innerText = text;
Expand Down
4 changes: 4 additions & 0 deletions WebHostLib/static/styles/playerOptions/playerOptions.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion WebHostLib/templates/playerOptions/playerOptions.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ <h1>Player Options</h1>
<div id="meta-options">
<div>
<label for="player-name">
Player Name: <span class="interactive" data-tooltip="This is the name you use to connect with your game. This is also known as your 'slot name'.">(?)</span>
Player Name: <span class="interactive" data-tooltip="This is the name you use to connect with your game, also known as your 'slot name'. It has a maximum of 16 characters.">(?)</span>
</label>
<input id="player-name" placeholder="Player" name="name" maxlength="16" />
<div id="name-warning" hidden>Warning: The player name has a max length of 16 characters.</div>
</div>
<div class="js-required">
<label for="game-options-preset">
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/templates/weightedOptions/weightedOptions.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ <h1>Weighted Options</h1>

<p><label for="player-name">Please enter your player name. This will appear in-game as you send and receive
items if you are playing in a MultiWorld.</label><br />
<input id="player-name" placeholder="Player Name" name="name" maxlength="16" />
<input id="player-name" placeholder="Player Name" name="name" />
</p>

<div id="{{ world_name }}-container">
Expand Down
4 changes: 3 additions & 1 deletion data/optionscreator.kv
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#:import player_name_length OptionsCreator.player_name_length
<VisualRange>:
id: this
spacing: 15
Expand Down Expand Up @@ -139,9 +140,10 @@ ContainerLayout:
padding: ["10dp", "30dp", "10dp", 0]
spacing: "10dp"

ResizableTextField:
ComputedLengthTextField:
id: player_name
multiline: False
length_func: player_name_length

MDTextFieldHintText:
text: "Player Name"
Expand Down
Loading