From a899f3637e6eee1e953c01bd85daee3b6d40fdd9 Mon Sep 17 00:00:00 2001 From: Face <69168154+face-hh@users.noreply.github.com> Date: Wed, 23 Jul 2025 18:09:16 +0300 Subject: [PATCH] input (radio/password), max/min length, pattern --- Assets/Icons/radio.svg | 1 + Assets/Icons/radio.svg.import | 37 ++++ Assets/Icons/radio_checked.svg | 1 + Assets/Icons/radio_checked.svg.import | 37 ++++ Assets/Icons/radio_checked_grayscale.svg | 1 + .../Icons/radio_checked_grayscale.svg.import | 37 ++++ Assets/Icons/radio_disabled.svg | 1 + Assets/Icons/radio_disabled.svg.import | 37 ++++ README.md | 3 +- Scenes/Styles/BrowserText.tres | 10 +- Scenes/Tags/input.tscn | 14 +- Scenes/Tags/input.tscn8601746517.tmp | 45 +++++ Scenes/Tags/new_button_group.tres | 3 + Scripts/Tags/input.gd | 80 +++++++- Scripts/main.gd | 174 ++++++++---------- 15 files changed, 376 insertions(+), 105 deletions(-) create mode 100644 Assets/Icons/radio.svg create mode 100644 Assets/Icons/radio.svg.import create mode 100644 Assets/Icons/radio_checked.svg create mode 100644 Assets/Icons/radio_checked.svg.import create mode 100644 Assets/Icons/radio_checked_grayscale.svg create mode 100644 Assets/Icons/radio_checked_grayscale.svg.import create mode 100644 Assets/Icons/radio_disabled.svg create mode 100644 Assets/Icons/radio_disabled.svg.import create mode 100644 Scenes/Tags/input.tscn8601746517.tmp create mode 100644 Scenes/Tags/new_button_group.tres diff --git a/Assets/Icons/radio.svg b/Assets/Icons/radio.svg new file mode 100644 index 0000000..ef3498f --- /dev/null +++ b/Assets/Icons/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Assets/Icons/radio.svg.import b/Assets/Icons/radio.svg.import new file mode 100644 index 0000000..5c8702d --- /dev/null +++ b/Assets/Icons/radio.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2v8ee23580c3" +path="res://.godot/imported/radio.svg-320eafa791feaf0d1d926ec0d2ba3722.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Icons/radio.svg" +dest_files=["res://.godot/imported/radio.svg-320eafa791feaf0d1d926ec0d2ba3722.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/Assets/Icons/radio_checked.svg b/Assets/Icons/radio_checked.svg new file mode 100644 index 0000000..e4eed8b --- /dev/null +++ b/Assets/Icons/radio_checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Assets/Icons/radio_checked.svg.import b/Assets/Icons/radio_checked.svg.import new file mode 100644 index 0000000..27c6799 --- /dev/null +++ b/Assets/Icons/radio_checked.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bj7bpovnm25ml" +path="res://.godot/imported/radio_checked.svg-81396277cd631c074cae607c415d3a5c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Icons/radio_checked.svg" +dest_files=["res://.godot/imported/radio_checked.svg-81396277cd631c074cae607c415d3a5c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/Assets/Icons/radio_checked_grayscale.svg b/Assets/Icons/radio_checked_grayscale.svg new file mode 100644 index 0000000..370699c --- /dev/null +++ b/Assets/Icons/radio_checked_grayscale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Assets/Icons/radio_checked_grayscale.svg.import b/Assets/Icons/radio_checked_grayscale.svg.import new file mode 100644 index 0000000..10da2cd --- /dev/null +++ b/Assets/Icons/radio_checked_grayscale.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y1xqfcd8ro5t" +path="res://.godot/imported/radio_checked_grayscale.svg-6ce9eb51f7fcbe2c1034ed12866d95cf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Icons/radio_checked_grayscale.svg" +dest_files=["res://.godot/imported/radio_checked_grayscale.svg-6ce9eb51f7fcbe2c1034ed12866d95cf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/Assets/Icons/radio_disabled.svg b/Assets/Icons/radio_disabled.svg new file mode 100644 index 0000000..03d3b5d --- /dev/null +++ b/Assets/Icons/radio_disabled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Assets/Icons/radio_disabled.svg.import b/Assets/Icons/radio_disabled.svg.import new file mode 100644 index 0000000..d84a95c --- /dev/null +++ b/Assets/Icons/radio_disabled.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bs414mrvwdmcn" +path="res://.godot/imported/radio_disabled.svg-04a182ffa1f439025aa6457482c84d92.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Icons/radio_disabled.svg" +dest_files=["res://.godot/imported/radio_disabled.svg-04a182ffa1f439025aa6457482c84d92.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/README.md b/README.md index e8ce9e3..bf51435 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ TODO: 5. **Store** tab containers so switching tabs won't erase previous tab. 6. **GIF** support 7. **Video** support via [GDE GoZen](https://github.com/VoylinsGamedevJourney/gde_gozen) -8. **More input types** (password, email, number, etc.) +8. **More input types** (url, tel, date, time, etc.) +9. **Required** attribute for inputs Issues: 1. **< br />** counts as 1 element in **WebsiteContainer**, therefore despite being (0,0) in size, it counts as double in spacing. \ No newline at end of file diff --git a/Scenes/Styles/BrowserText.tres b/Scenes/Styles/BrowserText.tres index 5a24c0f..3df892f 100644 --- a/Scenes/Styles/BrowserText.tres +++ b/Scenes/Styles/BrowserText.tres @@ -1,9 +1,13 @@ -[gd_resource type="Theme" load_steps=17 format=3 uid="uid://bn6rbmdy60lhr"] +[gd_resource type="Theme" load_steps=21 format=3 uid="uid://bn6rbmdy60lhr"] [ext_resource type="Texture2D" uid="uid://dn4dxn8hkrd64" path="res://Assets/Icons/checkbox.svg" id="1_75mhk"] [ext_resource type="Texture2D" uid="uid://b6ucuyluuw43" path="res://Assets/Icons/checkbox_pressed_grayscale.svg" id="2_2abar"] [ext_resource type="Texture2D" uid="uid://c5scqw224r2e5" path="res://Assets/Icons/checkbox_pressed.svg" id="2_3k2jb"] +[ext_resource type="Texture2D" uid="uid://b2v8ee23580c3" path="res://Assets/Icons/radio.svg" id="3_3weog"] [ext_resource type="Texture2D" uid="uid://bap17ryrkcyey" path="res://Assets/Icons/checkbox_disabled.svg" id="4_c32on"] +[ext_resource type="Texture2D" uid="uid://y1xqfcd8ro5t" path="res://Assets/Icons/radio_checked_grayscale.svg" id="4_n77xs"] +[ext_resource type="Texture2D" uid="uid://bj7bpovnm25ml" path="res://Assets/Icons/radio_checked.svg" id="4_sesrd"] +[ext_resource type="Texture2D" uid="uid://bs414mrvwdmcn" path="res://Assets/Icons/radio_disabled.svg" id="6_r011l"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_c32on"] @@ -77,6 +81,10 @@ Button/styles/hover = SubResource("StyleBoxFlat_7g0pl") Button/styles/normal = SubResource("StyleBoxFlat_c32on") CheckBox/icons/checked = ExtResource("2_3k2jb") CheckBox/icons/checked_disabled = ExtResource("2_2abar") +CheckBox/icons/radio_checked = ExtResource("4_sesrd") +CheckBox/icons/radio_checked_disabled = ExtResource("4_n77xs") +CheckBox/icons/radio_unchecked = ExtResource("3_3weog") +CheckBox/icons/radio_unchecked_disabled = ExtResource("6_r011l") CheckBox/icons/unchecked = ExtResource("1_75mhk") CheckBox/icons/unchecked_disabled = ExtResource("4_c32on") CheckBox/styles/focus = SubResource("StyleBoxEmpty_75mhk") diff --git a/Scenes/Tags/input.tscn b/Scenes/Tags/input.tscn index d15529c..6bbb823 100644 --- a/Scenes/Tags/input.tscn +++ b/Scenes/Tags/input.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=3 format=3 uid="uid://c7yay102a3b4c"] +[gd_scene load_steps=4 format=3 uid="uid://c7yay102a3b4c"] [ext_resource type="Script" uid="uid://kv6ebscarj2e" path="res://Scripts/Tags/input.gd" id="1_input"] [ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"] +[ext_resource type="ButtonGroup" uid="uid://dgvdef1uht5ht" path="res://Scenes/Tags/new_button_group.tres" id="3_a88g6"] [node name="Input" type="Control"] layout_mode = 3 @@ -21,6 +22,7 @@ layout_mode = 1 offset_right = 400.0 offset_bottom = 35.0 theme = ExtResource("2_theme") +text = "test" placeholder_text = "Enter text..." caret_blink = true @@ -32,3 +34,13 @@ offset_bottom = 31.0 theme = ExtResource("2_theme") theme_override_constants/icon_max_width = 24 flat = true + +[node name="RadioButton" type="CheckBox" parent="."] +visible = false +layout_mode = 0 +offset_right = 31.0 +offset_bottom = 31.0 +theme = ExtResource("2_theme") +theme_override_constants/icon_max_width = 24 +button_group = ExtResource("3_a88g6") +flat = true diff --git a/Scenes/Tags/input.tscn8601746517.tmp b/Scenes/Tags/input.tscn8601746517.tmp new file mode 100644 index 0000000..e9f0b22 --- /dev/null +++ b/Scenes/Tags/input.tscn8601746517.tmp @@ -0,0 +1,45 @@ +[gd_scene load_steps=4 format=3 uid="uid://c7yay102a3b4c"] + +[ext_resource type="Script" uid="uid://kv6ebscarj2e" path="res://Scripts/Tags/input.gd" id="1_input"] +[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_theme"] +[ext_resource type="ButtonGroup" uid="uid://dgvdef1uht5ht" path="res://Scenes/Tags/new_button_group.tres" id="3_a88g6"] + +[node name="Input" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 400.0 +offset_bottom = 31.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 0 +script = ExtResource("1_input") + +[node name="LineEdit" type="LineEdit" parent="."] +visible = false +layout_mode = 1 +offset_right = 400.0 +offset_bottom = 35.0 +theme = ExtResource("2_theme") +placeholder_text = "Enter text..." +caret_blink = true + +[node name="CheckBox" type="CheckBox" parent="."] +visible = false +layout_mode = 0 +offset_right = 31.0 +offset_bottom = 31.0 +theme = ExtResource("2_theme") +theme_override_constants/icon_max_width = 24 +flat = true + +[node name="RadioButton" type="CheckBox" parent="."] +layout_mode = 0 +offset_right = 31.0 +offset_bottom = 31.0 +theme = ExtResource("2_theme") +theme_override_constants/icon_max_width = 24 +button_group = ExtResource("3_a88g6") +flat = true diff --git a/Scenes/Tags/new_button_group.tres b/Scenes/Tags/new_button_group.tres new file mode 100644 index 0000000..2443df7 --- /dev/null +++ b/Scenes/Tags/new_button_group.tres @@ -0,0 +1,3 @@ +[gd_resource type="ButtonGroup" format=3 uid="uid://dgvdef1uht5ht"] + +[resource] diff --git a/Scripts/Tags/input.gd b/Scripts/Tags/input.gd index 76bd305..4409849 100644 --- a/Scripts/Tags/input.gd +++ b/Scripts/Tags/input.gd @@ -1,23 +1,93 @@ extends Control +static var button_groups: Dictionary = {} func init(element: HTMLParser.HTMLElement) -> void: var line_edit: LineEdit = $LineEdit var check_box: CheckBox = $CheckBox + var radio_button: CheckBox = $RadioButton var input_type = element.get_attribute("type").to_lower() var placeholder = element.get_attribute("placeholder") var value = element.get_attribute("value") + var group = element.get_attribute("group") + var minlength = element.get_attribute("minlength") + var maxlength = element.get_attribute("maxlength") + var pattern = element.get_attribute("pattern") + + # Hide all inputs initially + line_edit.visible = false + check_box.visible = false + radio_button.visible = false match input_type: "checkbox": - line_edit.visible = false check_box.visible = true if value and value == "true": check_box.button_pressed = true custom_minimum_size = check_box.size + "radio": + radio_button.visible = true + radio_button.toggle_mode = true + if value and value == "true": radio_button.button_pressed = true + custom_minimum_size = radio_button.size + + if group.length() > 0: + if not button_groups.has(group): + button_groups[group] = ButtonGroup.new() + radio_button.button_group = button_groups[group] + "password": + line_edit.visible = true + line_edit.secret = true + custom_minimum_size = line_edit.size + setup_text_input(line_edit, placeholder, value, minlength, maxlength, pattern) _: # Default to text input line_edit.visible = true - check_box.visible = false + line_edit.secret = false custom_minimum_size = line_edit.size - - if placeholder: line_edit.placeholder_text = placeholder - if value: line_edit.text = value + setup_text_input(line_edit, placeholder, value, minlength, maxlength, pattern) + +func setup_text_input(line_edit: LineEdit, placeholder: String, value: String, minlength: String, maxlength: String, pattern: String) -> void: + if placeholder: line_edit.placeholder_text = placeholder + if value: line_edit.text = value + + line_edit.max_length = maxlength.to_int() + + if minlength.length() > 0 or pattern.length() > 0: + line_edit.text_changed.connect(_on_text_changed.bind(minlength, pattern)) + +func _on_text_changed(new_text: String, minlength: String, pattern: String) -> void: + var line_edit = get_node("LineEdit") as LineEdit + var is_valid = true + + # Check minimum length + if minlength.length() > 0 and minlength.is_valid_int(): + var min_len = minlength.to_int() + if new_text.length() < min_len and new_text.length() > 0: + is_valid = false + + # Check pattern (regex) + if pattern.length() > 0 and new_text.length() > 0: + var regex = RegEx.new() + if regex.compile(pattern) == OK: + if not regex.search(new_text): + is_valid = false + + if is_valid: + # Reset to default styles + line_edit.remove_theme_stylebox_override("normal") + line_edit.remove_theme_stylebox_override("focus") + line_edit.modulate = Color.WHITE + else: + var normal_style = create_red_border_style_from_theme(line_edit, "normal") + var focus_style = create_red_border_style_from_theme(line_edit, "focus") + + line_edit.add_theme_stylebox_override("normal", normal_style) + line_edit.add_theme_stylebox_override("focus", focus_style) + line_edit.modulate = Color.WHITE + +func create_red_border_style_from_theme(line_edit: LineEdit, style_name: String) -> StyleBoxFlat: + var original_style: StyleBoxFlat = line_edit.get_theme_stylebox(style_name) + var style: StyleBoxFlat = original_style.duplicate() + + style.border_color = Color.RED + + return style diff --git a/Scripts/main.gd b/Scripts/main.gd index beb86b3..f2fa57e 100644 --- a/Scripts/main.gd +++ b/Scripts/main.gd @@ -74,15 +74,28 @@ both spaces and line breaks -