Compare commits

...

3 Commits

Author SHA1 Message Date
Nekojimi aacfc4d78e Add WIP camera movement 2025-04-27 14:38:35 +01:00
Nekojimi d138db341d Disable physics interpolation on bullets and conveyors. 2025-04-27 14:36:23 +01:00
Nekojimi dcc991a7d9 Add icon font plugin. 2025-04-27 14:34:17 +01:00
67 changed files with 56778 additions and 20 deletions

View File

@ -0,0 +1,16 @@
@tool
extends RichTextLabel
@export_multiline
var text_with_icons: String:
set(value):
if !is_node_ready(): await ready
text_with_icons = value
bbcode_enabled = true
text = IconsFonts.parse_text(value)
get: return text_with_icons
func _ready():
bbcode_enabled = true
text = IconsFonts.parse_text(text_with_icons)

View File

@ -0,0 +1 @@
uid://5px8jipd26f7

View File

@ -0,0 +1,385 @@
[gd_scene load_steps=28 format=3 uid="uid://casymgu2sqx81"]
[ext_resource type="FontFile" uid="uid://be10y8fgoiayy" path="res://addons/icons-fonts/icons_fonts/emojis/NotoColorEmoji.ttf" id="1_lglyu"]
[ext_resource type="Script" uid="uid://sfoyl3fbn8so" path="res://addons/icons-fonts/nodes/FontIcon.gd" id="2_fd237"]
[ext_resource type="Script" uid="uid://dpyof2t0wn8k1" path="res://addons/icons-fonts/resources/FontIconSetting.gd" id="3_jajcj"]
[ext_resource type="Script" uid="uid://pwyh8365is28" path="res://addons/icons-fonts/nodes/FontIconButton.gd" id="4_5ca3b"]
[ext_resource type="Script" uid="uid://c4n2aki4724e4" path="res://addons/icons-fonts/nodes/FontIconCheckButton.gd" id="5_qfxwr"]
[ext_resource type="Script" uid="uid://5px8jipd26f7" path="res://addons/icons-fonts/examples/rich_text_label_with_icons.gd" id="6_3361y"]
[sub_resource type="LabelSettings" id="LabelSettings_fw4y0"]
font = ExtResource("1_lglyu")
font_size = 64
shadow_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_lqfro"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "image-outline"
icon_size = 128
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_28mvn"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "image-outline"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="LabelSettings" id="LabelSettings_6nmg6"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yfigq"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="Resource" id="Resource_nwary"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "toggle-switch-variant"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_v03hs"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "toggle-switch-variant-off"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_7vk3f"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "image-outline"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="LabelSettings" id="LabelSettings_mmhoi"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_onf1e"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="Resource" id="Resource_a0w4j"]
script = ExtResource("3_jajcj")
icon_font = "Emojis"
icon_name = "framed_picture"
icon_size = 64
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_pqb8k"]
script = ExtResource("3_jajcj")
icon_font = "Emojis"
icon_name = "framed_picture"
icon_size = 32
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="LabelSettings" id="LabelSettings_dtieq"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_r4h4a"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="Resource" id="Resource_bo5k2"]
script = ExtResource("3_jajcj")
icon_font = "Emojis"
icon_name = "thumbs_up"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_6kb3v"]
script = ExtResource("3_jajcj")
icon_font = "Emojis"
icon_name = "thumbs_down"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_r0p3s"]
script = ExtResource("3_jajcj")
icon_font = "Emojis"
icon_name = "framed_picture"
icon_size = 32
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_b80wx"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "cards-heart"
icon_size = 26
icon_color = Color(0.963688, 0.444703, 0.363128, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_uhuk0"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "cards-heart-outline"
icon_size = 26
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="Resource" id="Resource_b1txr"]
script = ExtResource("3_jajcj")
icon_font = "MaterialIcons"
icon_name = "image-outline"
icon_size = 16
icon_color = Color(1, 1, 1, 1)
outline_color = Color(1, 1, 1, 1)
outline_size = 0
shadow_color = Color(1, 1, 1, 1)
shadow_size = 0
shadow_offset = Vector2(0, 0)
[sub_resource type="LabelSettings" id="LabelSettings_y3ob5"]
[node name="TestIconsFonts" type="BoxContainer"]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 105.0
grow_horizontal = 2
size_flags_horizontal = 3
size_flags_vertical = 0
[node name="MaterialIconsExamples" type="BoxContainer" parent="."]
layout_mode = 2
vertical = true
[node name="Label" type="Label" parent="MaterialIconsExamples"]
layout_mode = 2
text = "Material Icons"
horizontal_alignment = 1
[node name="BoxContainer" type="BoxContainer" parent="MaterialIconsExamples"]
layout_mode = 2
[node name="FontIcon" type="Label" parent="MaterialIconsExamples/BoxContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "󰥶"
label_settings = SubResource("LabelSettings_fw4y0")
script = ExtResource("2_fd237")
icon_settings = SubResource("Resource_lqfro")
[node name="FontIconButton" type="PanelContainer" parent="MaterialIconsExamples/BoxContainer"]
layout_mode = 2
script = ExtResource("4_5ca3b")
layout_alignment = null
icon_settings = SubResource("Resource_28mvn")
label_text = "Jakiś Tekst"
label_settings = SubResource("LabelSettings_6nmg6")
button_margin = 0
[node name="FontIconCheckButton" type="PanelContainer" parent="MaterialIconsExamples/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_yfigq")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_nwary")
off_icon_settings = SubResource("Resource_v03hs")
layout_order = "Label-Icon-Toggle"
layout_alignment = null
icon_settings = SubResource("Resource_7vk3f")
label_text = "Toogle"
label_settings = SubResource("LabelSettings_mmhoi")
button_margin = 0
toggle_mode = true
[node name="FontIconCheckButton2" type="PanelContainer" parent="MaterialIconsExamples/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_onf1e")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_nwary")
off_icon_settings = SubResource("Resource_v03hs")
layout_order = "Label-Toggle"
icon_settings = SubResource("Resource_7vk3f")
label_text = "Toogle"
label_settings = SubResource("LabelSettings_mmhoi")
button_margin = 0
toggle_mode = true
[node name="FontIconCheckButton3" type="PanelContainer" parent="MaterialIconsExamples/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_onf1e")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_nwary")
off_icon_settings = SubResource("Resource_v03hs")
layout_order = "Toggle"
icon_settings = SubResource("Resource_7vk3f")
label_text = "Toogle"
label_settings = SubResource("LabelSettings_mmhoi")
button_margin = 0
toggle_mode = true
[node name="EmojisExamples" type="BoxContainer" parent="."]
layout_mode = 2
vertical = true
[node name="Label2" type="Label" parent="EmojisExamples"]
layout_mode = 2
text = "Emoji"
horizontal_alignment = 1
[node name="BoxContainer" type="BoxContainer" parent="EmojisExamples"]
layout_mode = 2
[node name="FontIcon2" type="Label" parent="EmojisExamples/BoxContainer"]
layout_mode = 2
text = "🖼️"
label_settings = SubResource("LabelSettings_fw4y0")
script = ExtResource("2_fd237")
icon_settings = SubResource("Resource_a0w4j")
[node name="FontIconButton2" type="PanelContainer" parent="EmojisExamples/BoxContainer"]
layout_mode = 2
script = ExtResource("4_5ca3b")
layout_vertical = true
layout_alignment = null
icon_settings = SubResource("Resource_pqb8k")
label_text = "Jakiś Tekst"
label_settings = SubResource("LabelSettings_dtieq")
button_margin = 6
[node name="FontIconCheckButton2" type="PanelContainer" parent="EmojisExamples/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_r4h4a")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_bo5k2")
off_icon_settings = SubResource("Resource_6kb3v")
layout_order = "Label-Icon-Toggle"
layout_alignment = null
icon_settings = SubResource("Resource_r0p3s")
label_text = "Toogle"
label_settings = SubResource("LabelSettings_mmhoi")
button_margin = 0
toggle_mode = true
[node name="FontIconCheckButton3" type="PanelContainer" parent="EmojisExamples/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_onf1e")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_bo5k2")
off_icon_settings = SubResource("Resource_6kb3v")
layout_order = "Label-Toggle"
icon_settings = SubResource("Resource_r0p3s")
label_text = "Toogle"
label_settings = SubResource("LabelSettings_mmhoi")
button_margin = 0
toggle_mode = true
[node name="FontIconCheckButton4" type="PanelContainer" parent="EmojisExamples/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_onf1e")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_bo5k2")
off_icon_settings = SubResource("Resource_6kb3v")
layout_order = "Toggle"
icon_settings = SubResource("Resource_r0p3s")
label_text = "Toogle"
label_settings = SubResource("LabelSettings_mmhoi")
button_margin = 0
toggle_mode = true
[node name="RichTextLabelWithIcons" type="RichTextLabel" parent="."]
layout_mode = 2
size_flags_horizontal = 3
bbcode_enabled = true
text = "This text can have icons [font=res://addons/icons-fonts/icons_fonts/MaterialIcons/material_design_icons.ttf]󰋩[/font],
but also emojis [font=res://addons/icons-fonts/icons_fonts/emojis/NotoColorEmoji.ttf]🖼️[/font]"
fit_content = true
script = ExtResource("6_3361y")
text_with_icons = "This text can have icons [mi:image],
but also emojis :framed_picture:"
[node name="FontIconCheckButton" type="PanelContainer" parent="."]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_onf1e")
script = ExtResource("5_qfxwr")
on_icon_settings = SubResource("Resource_b80wx")
off_icon_settings = SubResource("Resource_uhuk0")
layout_order = "Toggle"
layout_alignment = 0
icon_settings = SubResource("Resource_b1txr")
label_settings = SubResource("LabelSettings_y3ob5")
toggle_mode = true

View File

@ -0,0 +1,140 @@
[gd_scene load_steps=5 format=3 uid="uid://bni8w3a3pcbwn"]
[ext_resource type="Script" uid="uid://cq6lkta6wqfww" path="res://addons/icons-fonts/icon_finder/icon_finder.gd" id="1_nv02s"]
[ext_resource type="FontFile" uid="uid://bbfeoo2kuf30n" path="res://addons/icons-fonts/icons_fonts/MaterialIcons/material_design_icons.ttf" id="2_8e28h"]
[ext_resource type="Script" uid="uid://8ujwc2fokr54" path="res://addons/icons-fonts/icon_finder/IconsFontsRender.gd" id="3_hobrt"]
[ext_resource type="FontFile" uid="uid://be10y8fgoiayy" path="res://addons/icons-fonts/icons_fonts/emojis/NotoColorEmoji.ttf" id="3_sggto"]
[node name="IconFinder" type="Panel" node_paths=PackedStringArray("icons_renderers", "icons_renderers_tabs", "notify_label", "search_line_edit", "size_slider", "size_label", "scroll_container", "fonts_dropdown")]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_nv02s")
icons_renderers = [NodePath("MarginContainer/VBoxContainer/ScrollContainer/Tabs/MaterialIcons"), NodePath("MarginContainer/VBoxContainer/ScrollContainer/Tabs/Emojis")]
icons_renderers_tabs = NodePath("MarginContainer/VBoxContainer/ScrollContainer/Tabs")
notify_label = NodePath("MarginContainer/VBoxContainer/Notify")
search_line_edit = NodePath("MarginContainer/VBoxContainer/HBoxContainer/Search")
size_slider = NodePath("MarginContainer/VBoxContainer/SliderContainer/HSlider")
size_label = NodePath("MarginContainer/VBoxContainer/SliderContainer/LabelSize")
scroll_container = NodePath("MarginContainer/VBoxContainer/ScrollContainer")
fonts_dropdown = NodePath("MarginContainer/VBoxContainer/HBoxContainer/FontsDropdown")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Font"
[node name="FontsDropdown" type="OptionButton" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
selected = 0
item_count = 2
popup/item_0/text = "MaterialIcons"
popup/item_1/text = "Emojis"
popup/item_1/id = 1
[node name="Search" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Search"
caret_blink = true
caret_blink_interval = 0.5
[node name="Notify" type="Label" parent="MarginContainer/VBoxContainer"]
visible = false
layout_mode = 2
text = "Icon-x was copied to clipboard."
[node name="SliderContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/SliderContainer"]
layout_mode = 2
text = "Size preview"
[node name="HSlider" type="HSlider" parent="MarginContainer/VBoxContainer/SliderContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
min_value = 16.0
max_value = 128.0
value = 16.0
[node name="LabelSize" type="Label" parent="MarginContainer/VBoxContainer/SliderContainer"]
layout_mode = 2
text = "16"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
[node name="Tabs" type="TabContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
current_tab = 0
tabs_visible = false
[node name="MaterialIcons" type="RichTextLabel" parent="MarginContainer/VBoxContainer/ScrollContainer/Tabs" node_paths=PackedStringArray("size_slider", "search_line_edit")]
clip_contents = false
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
tooltip_text = "click on icon to copy its name to clipboard"
theme_override_fonts/normal_font = ExtResource("2_8e28h")
theme_override_font_sizes/normal_font_size = 16
bbcode_enabled = true
fit_content = true
scroll_active = false
autowrap_mode = 0
meta_underlined = false
threaded = true
script = ExtResource("3_hobrt")
size_slider = NodePath("../../../SliderContainer/HSlider")
search_line_edit = NodePath("../../../HBoxContainer/Search")
metadata/_tab_index = 0
[node name="Emojis" type="RichTextLabel" parent="MarginContainer/VBoxContainer/ScrollContainer/Tabs" node_paths=PackedStringArray("size_slider", "search_line_edit")]
visible = false
clip_contents = false
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
tooltip_text = "click on icon to copy its name to clipboard"
theme_override_fonts/normal_font = ExtResource("3_sggto")
theme_override_font_sizes/normal_font_size = 16
bbcode_enabled = true
text = "Loading"
fit_content = true
scroll_active = false
autowrap_mode = 0
meta_underlined = false
threaded = true
script = ExtResource("3_hobrt")
icon_font = "Emojis"
size_slider = NodePath("../../../SliderContainer/HSlider")
search_line_edit = NodePath("../../../HBoxContainer/Search")
metadata/_tab_index = 1

View File

@ -0,0 +1,13 @@
[gd_scene load_steps=3 format=3 uid="uid://qsslm1ensgyo"]
[ext_resource type="Script" uid="uid://b0p1q2uqyvfmb" path="res://addons/icons-fonts/icon_finder/icon_finder_window.gd" id="1_nvv0t"]
[ext_resource type="PackedScene" uid="uid://bni8w3a3pcbwn" path="res://addons/icons-fonts/icon_finder/IconFinder.tscn" id="2_aqufi"]
[node name="IconFinderWindow" type="Window"]
title = "Icon Finder"
initial_position = 2
size = Vector2i(775, 400)
wrap_controls = true
script = ExtResource("1_nvv0t")
[node name="IconFinder" parent="." instance=ExtResource("2_aqufi")]

View File

@ -0,0 +1,82 @@
@tool
class_name IconsFontsRender
extends RichTextLabel
@export_enum("MaterialIcons", "Emojis")
var icon_font := "MaterialIcons"
@export var start_size := 1065
@export var size_slider: Slider
@export var search_line_edit : LineEdit
func set_icons_size(value:int):
IconsFonts.preview_size = value
set("theme_override_font_sizes/normal_font_size", value)
func get_font_data() -> Dictionary:
var data := {}
match icon_font:
"MaterialIcons": data = IconsFonts.material_icons
"Emojis": data = IconsFonts.emojis
_: text = "Unsupported IconsFont %s" % icon_font
return data
func get_icon(key:String) -> String:
match icon_font:
"MaterialIcons": return IconsFonts.get_icon_char("MaterialIcons", key)
"Emojis": return str(IconsFonts.emojis[key])
return ""
func _ready() -> void:
visibility_changed.connect(_on_visibility_changed)
func _on_visibility_changed():
if visible:
set_icons_size(IconsFonts.preview_size)
update_table(search_line_edit.text)
func setup():
set_meta_underline(false)
set_icons_size(IconsFonts.preview_size)
func update_table(filter := ""):
var table = "[table={columns}, {inline_align}]"
var columns := int(size.x / IconsFonts.preview_size) + 1
if columns <= 10:
# size.x on start gives me 8 and slider.value is 16, so columns equals 1
# so I add new fallback var start_size = 1056,
# which is size.x after when it works
columns = int(start_size / IconsFonts.preview_size) + 1
table = table.format({
"columns": columns,
"inline_align": INLINE_ALIGNMENT_CENTER
})
var data := get_font_data()
if !data: return
var cells := columns
for key: String in data:
if filter and filter.to_lower() not in key: continue
cells -= 1
if cells <= 0: cells = columns
var link := "[url={link}]{icon}[/url]"
var icon := get_icon(key)
link = link.format({"link": key, "icon": icon})
var cell := "[cell]{link}[/cell]"
table += cell.format({"link": link})
cells = abs(cells)
while cells > columns:
cells -= 1
if cells > 0:
for c in cells:
table += "[cell] [/cell]"
table += "[/table]"
parse_bbcode(table)

View File

@ -0,0 +1 @@
uid://8ujwc2fokr54

View File

@ -0,0 +1,78 @@
@tool
extends Control
@export var icons_renderers: Array[IconsFontsRender]
@export var tooltip := "click on icon to copy its name to clipboard"
@export
@onready var icons_renderers_tabs: TabContainer
@export
@onready var notify_label: Label
@export
@onready var search_line_edit: LineEdit
@export
@onready var size_slider: HSlider
@export
@onready var size_label: Label
@export
@onready var scroll_container: ScrollContainer
@export
@onready var fonts_dropdown: OptionButton
var icons_renderer: IconsFontsRender
func _ready():
notify_label.hide()
search_line_edit.text_changed.connect(update_table)
size_slider.value_changed.connect(update_icons_size)
fonts_dropdown.item_selected.connect(on_font_changed)
size_slider.value = IconsFonts.preview_size
for renderer: IconsFontsRender in icons_renderers:
renderer.tooltip_text = tooltip
func setup():
for renderer: IconsFontsRender in icons_renderers:
if !renderer.is_node_ready(): await ready
renderer.setup()
renderer.meta_clicked.connect(_on_meta)
icons_renderer = icons_renderers[0]
func update_icons_size(value: int):
size_label.text = str(value)
if icons_renderer:
icons_renderer.set_icons_size(value)
update_table(search_line_edit.text)
IconsFonts.preview_size = value
func on_font_changed(font_id: int):
icons_renderers_tabs.current_tab = font_id
icons_renderer = icons_renderers[font_id]
func update_table(filter := ""):
if not icons_renderer: return
icons_renderer.update_table(filter)
func _on_meta(link: String):
DisplayServer.clipboard_set(link)
notify_label.text = "Copied to Clipboard: " + link
notify_label.show()
var t := get_tree().create_tween()
t.tween_property(
notify_label, "modulate",
Color.GREEN, 1
)
t.chain().tween_property(
notify_label, "modulate",
Color.TRANSPARENT, 1
)
await t.finished
notify_label.hide()

View File

@ -0,0 +1 @@
uid://cq6lkta6wqfww

View File

@ -0,0 +1,5 @@
@tool
extends Window
func _ready() -> void:
close_requested.connect(hide)

View File

@ -0,0 +1 @@
uid://b0p1q2uqyvfmb

View File

@ -0,0 +1,104 @@
@tool
# @singleton IconsFonts
extends EditorPlugin
const plugin_dir := "res://addons/icons-fonts/"
const icons_db := plugin_dir + "icons_fonts/IconsFonts.gd"
const inspector_plugin_path := plugin_dir + "inspector/font_icon_inspector.gd"
const sub_dir := "icon_finder/"
const icon_finder_dir := plugin_dir + sub_dir
const icon_finder_window_scene := icon_finder_dir + "IconFinderWindow.tscn"
const icon_finder_scene := icon_finder_dir + "IconFinder.tscn"
var command_palette := get_editor_interface().get_command_palette()
var editor_interface := get_editor_interface().get_base_control()
var icon_finder_window: Window
var icon_finder: Control
var popup_size := Vector2i(775, 400)
var inspector_plugin : EditorInspectorPlugin
var commands := [
[
"Icon Finder Window",
sub_dir + "icon_finder_window",
show_icon_finder_window
],
[
"Icon Finder Dock",
sub_dir + "icon_finder_dock",
add_to_dock
],
# todo uncomment when docs are ready!
# [
# "IconsFonts Help",
# sub_dir + "icon_help",
# help
# ],
]
var is_docked: bool
var icon_finder_loaded: PackedScene
var icon_finder_window_loaded: PackedScene
func _enter_tree():
add_autoload_singleton("IconsFonts", icons_db)
# await IconsFonts.ready
icon_finder_loaded = load(icon_finder_scene)
icon_finder_window_loaded = load(icon_finder_window_scene)
is_docked = ProjectSettings.get_setting(
"application/addons/icon_finder/is_docked", true)
if is_docked: await add_to_dock()
for command: Array in commands:
add_tool_menu_item(command[0], command[2])
command_palette.add_command(command[0], command[1], command[2])
inspector_plugin = preload(inspector_plugin_path).new()
add_inspector_plugin(inspector_plugin)
func help():
# todo update when docs are ready!
# OS.shell_open("https://rakugoteam.github.io/material-icons-docs/")
pass
func add_to_dock():
if icon_finder_window:
editor_interface.remove_child.call_deferred(icon_finder_window)
await get_tree().create_timer(0.5).timeout
icon_finder = icon_finder_loaded.instantiate()
add_control_to_bottom_panel(icon_finder, "Icons Finder")
if !icon_finder.is_node_ready(): await ready
await icon_finder.setup()
func show_icon_finder_window():
if icon_finder:
remove_control_from_bottom_panel(icon_finder)
if !icon_finder_window:
icon_finder_window = icon_finder_window_loaded.instantiate()
editor_interface.add_child.call_deferred(icon_finder_window)
if !icon_finder_window.is_node_ready(): await ready
icon_finder_window.theme = editor_interface.theme
icon_finder_window.popup_centered(popup_size)
func _exit_tree():
if inspector_plugin:
remove_inspector_plugin(inspector_plugin)
for command: Array in commands:
remove_tool_menu_item(command[0])
command_palette.remove_command(command[0])
remove_autoload_singleton("IconsFonts")
if icon_finder:
remove_control_from_bottom_panel(icon_finder)
icon_finder.queue_free()
if icon_finder_window:
editor_interface.remove_child.call_deferred(icon_finder_window)
icon_finder_window.queue_free()

View File

@ -0,0 +1 @@
uid://mihuo4o0cpte

View File

@ -0,0 +1,172 @@
@tool
# @singleton IconsFonts
extends Node
const docked_setting_path := "application/addons/icon_finder/is_docked"
const prev_size_setting_path := "application/addons/icon_finder/preview_size"
## Material Icons
const material_icons_json := "res://addons/icons-fonts/icons_fonts/MaterialIcons/icons.json"
const material_icons_font := "res://addons/icons-fonts/icons_fonts/MaterialIcons/material_design_icons.ttf"
## Emojis
const emojis_json := "res://addons/icons-fonts/icons_fonts/emojis/emojis.json"
const emojis_font := "res://addons/icons-fonts/icons_fonts/emojis/NotoColorEmoji.ttf"
signal font_loaded(font_name: String)
var material_icons := {}
var emojis := {}
static var is_docked: bool:
set(value):
ProjectSettings.set_setting(docked_setting_path, value)
get:
return ProjectSettings.get_setting(docked_setting_path, true)
static var preview_size: int:
set(value):
ProjectSettings.set_setting(prev_size_setting_path, value)
get:
return ProjectSettings.get_setting(prev_size_setting_path, 24)
func _ready():
var json: JSON
var content: String
if Engine.is_editor_hint():
json = load(material_icons_json)
init_material_icons_dict(json.data)
else:
json = JSON.new()
content = get_file_content(material_icons_json)
if json.parse(content) == OK:
init_material_icons_dict(json.data)
if Engine.is_editor_hint():
json = load(emojis_json)
init_emoji_dictionaries(json.data)
else:
json = JSON.new()
content = get_file_content(emojis_json)
if json.parse(content) == OK:
init_emoji_dictionaries(json.data)
func get_file_content(path: String) -> String:
var file := FileAccess.open(path, FileAccess.READ)
var content := ""
if file.get_error() == OK:
content = file.get_as_text()
file.close()
return content
func init_material_icons_dict(data: Dictionary):
material_icons = data
for id in data:
var hex = material_icons[id]
material_icons[id] = ("0x" + hex).hex_to_int()
# prints(id, material_icons[id])
prints("FontsIcons: MaterialIcons loaded")
font_loaded.emit("MaterialIcons")
func init_emoji_dictionaries(dict: Dictionary):
for emoji in dict:
var keys = dict[emoji]
for key in keys:
emojis[key] = emoji
prints("FontsIcons: Emojis loaded")
font_loaded.emit("Emojis")
func get_icon_code(font: String, id: String) -> int:
if "," in id:
id = id.split(",")[0]
match font:
"MaterialIcons":
if id in material_icons:
return material_icons[id]
push_warning("Icon '%s' in font %s not found." % [id, font])
return 0
func get_emoji_unicode(id: String) -> String:
if id in emojis:
# prints(id, emojis[id])
return emojis[id]
push_warning("Emoji %s not found." % id)
return ""
func get_icon_char(font: String, id: String) -> String:
match font:
"MaterialIcons":
return char(get_icon_code(font, id))
"Emojis":
return get_emoji_unicode(id)
return ""
## will parse text using:
## - parse_material_icons()
## - parse_emojis()
## - parse_game_icons()
func parse_text(text: String) -> String:
text = parse_material_icons(text)
text = parse_emojis(text)
# todo add game-icons parse
return text
## will replace [mi:icon_name] with [font=MaterialIcons]icon_char[/font]
func parse_material_icons(text: String) -> String:
var regex = RegEx.new()
regex.compile("\\[mi:(.*?)\\]")
var x = regex.search(text)
while x != null:
var icon = x.get_string(1)
var char = get_icon_char("MaterialIcons", icon)
var r = "[font={font}]{char}[/font]"
r = r.format({"font": material_icons_font, "char": char})
if icon.split(",").size() > 1:
var size = icon.split(",")[1]
var s = "[font_size={size}]{r}[/font_size]"
r = s.format({"size": size, "r": r})
text = text.replace(x.get_string(), r)
x = regex.search(text)
return text
func get_emoji_bbcode(id: String, size := 0) -> String:
var emoji := get_icon_char("Emojis", id)
if !emoji: return ""
var bbcode := "[font=%s]%s[/font]" % [emojis_font, emoji]
if size <= 0: return bbcode
return "[font_size=%s]%s[/font_size]" % [size, bbcode]
## will replace :emoji_name: with [font=Emojis]emoji_char[/font]
func parse_emojis(text: String):
var re = RegEx.new()
re.compile(":[\\w\\d]+(,\\s*\\d+)?:")
var result = re.search(text)
while result != null:
var temp := result.get_string()
temp = temp.replace(":", "")
var emoji := temp
var size := 0
if "," in temp:
var splited := temp.split(",")
emoji = splited[0]
size = int(splited[1].replace(" ", ""))
var replacement := get_emoji_bbcode(emoji, size)
text = text.replace(result.get_string(), replacement)
result = re.search(text)
return text

View File

@ -0,0 +1 @@
uid://be1kqgxxr4eyy

View File

@ -0,0 +1,20 @@
Pictogrammers Free License
--------------------------
This icon collection is released as free, open source, and GPL friendly by
the [Pictogrammers](http://pictogrammers.com/) icon group. You may use it
for commercial projects, open source projects, or anything really.
# Icons: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
Some of the icons are redistributed under the Apache 2.0 license. All other
icons are either redistributed under their respective licenses or are
distributed under the Apache 2.0 license.
# Fonts: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
All web and desktop fonts are distributed under the Apache 2.0 license. Web
and desktop fonts contain some icons that are redistributed under the Apache
2.0 license. All other icons are either redistributed under their respective
licenses or are distributed under the Apache 2.0 license.
# Code: MIT (https://opensource.org/licenses/MIT)
The MIT license applies to all non-font and non-icon files.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bbfeoo2kuf30n"
path="res://.godot/imported/material_design_icons.ttf-2d3d78b67944bff81f1808ffd996cd10.fontdata"
[deps]
source_file="res://addons/icons-fonts/icons_fonts/MaterialIcons/material_design_icons.ttf"
dest_files=["res://.godot/imported/material_design_icons.ttf-2d3d78b67944bff81f1808ffd996cd10.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
import re, json
import requests
# URLs of the files to download
css_url = "https://github.com/Templarian/MaterialDesign-Webfont/raw/master/css/materialdesignicons.css"
ttf_url = "https://github.com/Templarian/MaterialDesign-Webfont/raw/master/fonts/materialdesignicons-webfont.ttf"
# Function to download a file given its URL and save it with a specified name
def download_file(url, file_name):
response = requests.get(url)
with open(file_name, 'wb') as file:
file.write(response.content)
# Download the CSS file
download_file(css_url, 'materialdesignicons.css')
print("CSS file downloaded.")
# Download the TTF font file
download_file(ttf_url, 'material_design_icons.ttf')
print("TTF font file downloaded.")
# Specify the path to your text file
file_path = 'materialdesignicons.css'
# Open the file in read mode
with open(file_path, 'r') as file:
# Read the entire file content as a single string
file_content = file.read()
# Define your regex pattern
pattern = re.compile(r'\.mdi-([a-zA-Z0-9-]+)::before \{\n\s+.*?content: "([^"]*)";\n\}',
re.DOTALL
)
# Find all matches using the regex pattern
matches = re.findall(pattern, file_content)
# Create an empty dictionary to store the results
icons_dict = {}
# Now, 'matches' contains a list of tuples where each tuple contains the matched parts
for match in matches:
mdi_class, content_hex = match
content_unicode = content_hex.replace("\\", "")
icons_dict[mdi_class] = content_unicode
print(f"icon: {mdi_class}, unicode: {content_unicode}")
# Save the dictionary to a JSON file
with open('icons.json', 'w') as json_file:
json.dump(icons_dict, json_file, indent=2)
print("Icons saved to 'icons.json'")

View File

@ -0,0 +1,35 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://be10y8fgoiayy"
path="res://.godot/imported/NotoColorEmoji.ttf-7420805f2f15fd3d077a2cb64d7e2acd.fontdata"
[deps]
source_file="res://addons/icons-fonts/icons_fonts/emojis/NotoColorEmoji.ttf"
dest_files=["res://.godot/imported/NotoColorEmoji.ttf-7420805f2f15fd3d077a2cb64d7e2acd.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
disable_embedded_bitmaps=true
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
keep_rounding_remainders=true
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,483 @@
"""
Base on https://github.com/carpedm20/emoji/blob/master/utils/get_codes_from_unicode_emoji_data_files.py
Extract the full list of emoji and names from the Unicode emoji data files
https://unicode.org/Public/emoji/{v}/emoji-test.txt and
https://www.unicode.org/Public/{v}/ucd/emoji/emoji-variation-sequences.txt
and apply as much formatting as possible so the codes can be dropped into the
emoji registry file.
"""
import sys, os
import re, bs4
import unicodedata
import requests
import xml.etree.ElementTree as ET
import logging
import emoji as emoji_pkg
import json
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
def get_text_from_url(url: str) -> str:
"""Get text from url"""
return requests.get(url).text
def get_emoji_from_url(version: float) -> list:
"""Get splitlines of emojis list from unicode.org"""
url = f"https://unicode.org/Public/emoji/{version}/emoji-test.txt"
return get_text_from_url(url).splitlines()
def get_emoji_variation_sequence_from_url(version: str) -> list:
"""Get splitlines of emoji variation sequences from unicode.org"""
url = f"https://www.unicode.org/Public/{version}/ucd/emoji/emoji-variation-sequences.txt"
return get_text_from_url(url).splitlines()
def get_emojiterra_from_url(url: str) -> dict:
html = get_text_from_url(url)
soup = bs4.BeautifulSoup(html, "html.parser")
emojis = {}
data = soup.find_all('li')
data = [i for i in data if 'href' not in i.attrs and 'data-e' in i.attrs and i['data-e'].strip()]
for i in data:
code = i['data-e']
emojis[code] = i['title'].strip()
assert len(data) > 100, f"emojiterra data from {url} has only {len(data)} entries"
return emojis
def get_cheat_sheet(url: str) -> dict:
"""
Returns a dict of emoji to short-names:
E.g. {'👴': ':old_man:', '👵': ':old_woman:', ... }
"""
html = get_text_from_url(url)
soup = bs4.BeautifulSoup(html, "html.parser")
emojis = {}
items = soup.find(class_='ecs-list').find_all(class_='_item')
pattern = re.compile(r'U\+([0-9A-F]+)')
for i in items:
unicode_text = i.find(class_='unicode').text
code_points = pattern.findall(unicode_text)
code = ''.join(chr(int(x,16)) for x in code_points)
emojis[code] = i.find(class_='shortcode').text
# Remove some unwanted and some weird entries from the cheat sheet
filtered = {}
for emj, short_code in emojis.items():
if short_code.startswith(':flag_'):
# Skip flags from cheat-sheet, because we already have very similar aliases for the flags
continue
if '' in short_code:
# Strange emoji with ⊛ in the short-code
continue
if emj == '\U0001F93E\U0000200D\U00002640\U0000FE0F':
# The short-code for this emoji is wrong
continue
if emj == '\U0001F468\U0000200D\U0001F468\U0000200D\U0001F467':
# The short-code for this emoji is wrong
continue
if short_code.startswith('::'):
# Do not allow short-codes to have double :: at the start
short_code = short_code[1:]
if short_code.endswith('::'):
# Do not allow short-codes to have double :: at the end
short_code = short_code[:-1]
filtered[emj] = short_code
assert len(filtered) > 100, f"emoji-cheat-sheet data from {url} has only {len(filtered)} entries"
return filtered
def get_emoji_from_youtube(url: str) -> dict:
"""Get emoji alias from Youtube
Returns a dict of emoji to list of short-names:
E.g. {'💁': [':person_tipping_hand:', ':information_desk_person:'], '😉': [':winking_face:', ':wink:']}
"""
data = requests.get(url).json()
output = {}
for obj in data:
if 'shortcuts' not in obj or 'emojiId' not in obj:
continue
shortcuts = [x for x in obj['shortcuts'] if x.startswith(':') and x.endswith(':')]
if shortcuts:
output[obj['emojiId']] = shortcuts
assert len(output) > 100, f"youtube data from {url} has only {len(output)} entries"
return output
def extract_emojis(emojis_lines: list, sequences_lines: list) -> dict:
"""Extract emojis line by line to dict"""
output = {}
for line in emojis_lines:
if not line == "" and not line.startswith("#"):
emoji_status = line.split(";")[1].strip().split(" ")[0]
codes = line.split(";")[0].strip().split(" ")
separated_line = line.split(" # ")[-1].strip().split(" ")
separated_name = separated_line[2:]
version_str = separated_line[1]
emoji_name = (
"_".join(separated_name)
.removeprefix("flag:_")
.replace(":", "")
.replace(",", "")
.replace("\u201c", "")
.replace("\u201d", "")
.replace("\u229b", "")
.strip()
.replace(" ", "_")
.replace("_-_", "-")
)
emoji_code = "".join(
[
"\\U0000" + code if len(code) == 4 else "\\U000" + code
for code in codes
]
)
version = float(version_str.replace("E", "").strip())
if emoji_code in output:
raise Exception("Duplicate emoji: " +
emoji_name + " " + emoji_code)
output[emoji_code] = {
"en": emoji_name,
"status": emoji_status.replace("-", "_"),
"version": version
}
# Walk through the emoji-variation-sequences.txt
for line in sequences_lines:
if not line == "" and not line.startswith("#"):
# No variant
normal_codes = line.split(";")[0].strip().split(" ")
normal_code = "".join(
[
"\\U0000" + code if len(code) == 4 else "\\U000" + code
for code in normal_codes
]
)
if normal_code in output:
output[normal_code]["variant"] = True
# Text variant U+FE0E
text_codes = re.sub(r'\s*FE0E\s*$', '',
line.split(";")[0]).strip().split(" ")
text_code = "".join(
[
"\\U0000" + code if len(code) == 4 else "\\U000" + code
for code in text_codes
]
)
if text_code in output:
output[text_code]["variant"] = True
# Emoji variant U+FE0F
emoji_codes = re.sub(r'\s*FE0F\s*$', '', line.split(";")[0]).strip().split(" ")
emoji_code = "".join(
[
"\\U0000" + code if len(code) == 4 else "\\U000" + code
for code in emoji_codes
]
)
if emoji_code in output:
output[emoji_code]["variant"] = True
return output
def adapt_emoji_name(text: str, lang: str, emj: str) -> str:
# Use NFKC-form (single character instead of character + diacritic)
# Unicode.org files should be formatted like this anyway, but emojiterra is not consistent
text = unicodedata.normalize('NFKC', text)
# Remove white space
text = "_".join(text.split(" "))
emoji_name = ":" + (
text
.lower()
.removeprefix("flag:_")
.replace(":", "")
.replace(",", "")
.replace('"', "")
.replace("\u201e", "")
.replace("\u201f", "")
.replace("\u202f", "")
.replace("\u229b", "")
.replace("\u2013", "-")
.replace(",_", ",")
.strip()
.replace(" ", "_")
.replace("_-_", "-")
) + ":"
emoji_name = (emoji_name
.replace("____", "_")
.replace("___", "_")
.replace("__", "_")
.replace("--", "-"))
return emoji_name
def add_unicode_annotations(data, lang, url):
xml = get_text_from_url(url)
tree = ET.fromstring(xml)
annotations = tree.find('annotations')
for annotation in annotations:
if annotation.get('type') == 'tts':
emj = annotation.get('cp')
text = annotation.text.strip()
emoji_name = adapt_emoji_name(text, lang, emj)
if emj in data and data[emj] != emoji_name:
print(
f"# {lang}: CHANGED {data[emj]} TO {emoji_name} \t\t(Original: {text})")
data[emj] = emoji_name
def extract_names(github_tag, github_lang, lang, emoji_terra={}):
"""Copies emoji.EMOJI_DATA[emj][lang] and adds the names from the Unicode CLDR xml
Find latest tag at https://cldr.unicode.org/index/downloads or
https://github.com/unicode-org/cldr/tree/main/common/annotations
"""
data = get_UNICODE_EMOJI(lang)
add_unicode_annotations(data, lang, f"https://github.com/unicode-org/cldr/raw/{github_tag}/common/annotations/{github_lang}.xml")
add_unicode_annotations(data, lang, f"https://github.com/unicode-org/cldr/raw/{github_tag}/common/annotationsDerived/{github_lang}.xml")
# Add names from emojiterra if there is no unicode annotation
for emj, name in emoji_terra.items():
if emj in emoji_pkg.EMOJI_DATA and emj not in data:
emoji_name = adapt_emoji_name(name, lang, emj)
data[emj] = emoji_name
# There are some emoji with two code sequences for the same emoji, one that ends with \uFE0F and one that does not.
# The one that ends with \uFE0F is the "new" emoji, that is RGI.
# The Unicode translation data sometimes only has one of the two code sequences and is missing the other one.
# In that case we want to use the existing translation for both code sequences.
missing_translation = {}
for emj in data:
if emj.endswith('\uFE0F') and emj[0:-1] not in data and emj[0:-1] in emoji_pkg.EMOJI_DATA:
# the emoji NOT ending in \uFE0F exists in EMOJI_DATA but is has no translation
# e.g. ':pirate_flag:' -> '\U0001F3F4\u200D\u2620\uFE0F' or '\U0001F3F4\u200D\u2620'
missing_translation[emj[0:-1]] = data[emj]
with_emoji_type = f"{emj}\uFE0F"
if not emj.endswith('\uFE0F') and with_emoji_type not in data and with_emoji_type in emoji_pkg.EMOJI_DATA:
# the emoji ending in \uFE0F exists in EMOJI_DATA but is has no translation
# e.g. ':face_in_clouds:' -> '\U0001F636\u200D\U0001F32B\uFE0F' or '\U0001F636\u200D\U0001F32B'
missing_translation[with_emoji_type] = data[emj]
# Find emoji that contain \uFE0F inside the sequence (not just as a suffix)
# e.g. ':eye_in_speech_bubble:' -> '\U0001F441\uFE0F\u200D\U0001F5E8\uFE0F'
for emj in emoji_pkg.EMOJI_DATA:
if emj in data:
continue
emj_no_variant = emj.replace('\uFE0F', '')
if emj_no_variant != emj and emj_no_variant in data:
# the emoji with \uFE0F has no translation, but the emoji without all \uFE0F has a translation
data[emj] = data[emj_no_variant]
data.update(missing_translation)
return data
def get_emoji_from_github_api(url: str) -> dict:
"""Get emoji alias from GitHub API
"""
data = requests.get(url).json()
pattern = re.compile(r"unicode/([0-9a-fA-F-]+)\.[a-z]+")
output = {}
for name, img in data.items():
m = pattern.search(img)
if m:
emj = "".join(chr(int(h, 16)) for h in m.group(1).split('-'))
output[name] = emj
else:
pass # Special GitHub emoji that is not part of Unicode
assert len(output) > 100, f"data from github API has only {len(output)} entries"
return output
GITHUB_REMOVED_CHARS = re.compile("\u200D|\uFE0F|\uFE0E", re.IGNORECASE)
def find_github_aliases(emj, github_dict, v, emj_no_variant=None):
aliases = set()
# Strip ZWJ \u200D, text_type \uFE0E and emoji_type \uFE0F
# because the GitHub API does not include these
emj_clean = GITHUB_REMOVED_CHARS.sub("", emj)
for gh_alias in github_dict:
if emj == github_dict[gh_alias]:
aliases.add(gh_alias)
elif 'variant' in v and emj_no_variant == github_dict[gh_alias]:
aliases.add(gh_alias)
elif emj_clean == github_dict[gh_alias]:
aliases.add(gh_alias)
return aliases
def ascii(s):
# return escaped Code points \U000AB123
return s.encode("unicode-escape").decode()
if __name__ == "__main__":
logging.info(' Downloading...\n')
# Find the latest version at https://www.unicode.org/reports/tr51/#emoji_data
emoji_source = get_emoji_from_url(15.1)
emoji_sequences_source = get_emoji_variation_sequence_from_url('15.1.0')
emojis = extract_emojis(emoji_source, emoji_sequences_source)
# Find latest release tag at https://cldr.unicode.org/index/downloads
github_tag = 'release-44-1'
languages = {}
github_alias_dict = get_emoji_from_github_api('https://api.github.com/emojis')
cheat_sheet_dict = get_cheat_sheet('https://www.webfx.com/tools/emoji-cheat-sheet/')
youtube_dict = get_emoji_from_youtube('https://www.gstatic.com/youtube/img/emojis/emojis-png-7.json')
logging.info(' Combining...\n')
used_github_aliases = set()
escapedToUnicodeMap = {escaped: escaped.encode().decode('unicode-escape') for escaped in emojis} # maps: "\\U0001F4A4" to "\U0001F4A4"
all_existing_aliases_and_en = set(item for emj_data in emoji_pkg.EMOJI_DATA.values() for item in emj_data.get('alias', []))
all_existing_aliases_and_en.update(emj_data['en'] for emj_data in emoji_pkg.EMOJI_DATA.values())
f = 0
c = 0
new_aliases = []
emojis_data = {}
logging.info(' Print EMOJI_DATA...\n')
for code, v in sorted(emojis.items(), key=lambda item: item[1]["en"]):
language_str = ''
emj = escapedToUnicodeMap[code]
alternative = re.sub(r"\\U0000FE0[EF]$", "", code)
emj_no_variant = escapedToUnicodeMap[alternative]
# add names in other languages
for lang in languages:
if emj in languages[lang]:
language_str += ",\n '%s': '%s'" % (
lang, languages[lang][emj])
elif 'variant' in v:
# the language annotation uses the normal emoji (no variant), while the emoji-test.txt uses the emoji or text variant
if emj_no_variant in languages[lang]:
language_str += ",\n '%s': '%s'" % (
lang, languages[lang][emj_no_variant])
# Add existing alias from EMOJI_DATA
aliases = set()
if emj in emoji_pkg.EMOJI_DATA and 'alias' in emoji_pkg.EMOJI_DATA[emj]:
aliases.update(a[1:-1] for a in emoji_pkg.EMOJI_DATA[emj]['alias'])
old_aliases = set(aliases)
if emj_no_variant in emoji_pkg.EMOJI_DATA and 'alias' in emoji_pkg.EMOJI_DATA[emj_no_variant]:
aliases.update(a[1:-1] for a in emoji_pkg.EMOJI_DATA[emj_no_variant]['alias'])
# Add alias from GitHub API
github_aliases = find_github_aliases(emj, github_alias_dict, v, emj_no_variant)
aliases.update(shortcut for shortcut in github_aliases if shortcut not in all_existing_aliases_and_en)
used_github_aliases.update(github_aliases)
# Add alias from cheat sheet
if emj in cheat_sheet_dict and cheat_sheet_dict[emj] not in all_existing_aliases_and_en:
aliases.add(cheat_sheet_dict[emj][1:-1])
if emj_no_variant in cheat_sheet_dict and cheat_sheet_dict[emj_no_variant] not in all_existing_aliases_and_en:
aliases.add(cheat_sheet_dict[emj_no_variant][1:-1])
# Add alias from youtube
if emj in youtube_dict:
aliases.update(shortcut[1:-1] for shortcut in youtube_dict[emj] if shortcut not in all_existing_aliases_and_en)
if emj_no_variant in youtube_dict:
aliases.update(shortcut[1:-1] for shortcut in youtube_dict[emj_no_variant] if shortcut not in all_existing_aliases_and_en)
# Remove if alias is same as 'en'-name
if v["en"] in aliases:
aliases.remove(v["en"])
# Store new aliases to print them at the end after the dict of dicts
if emj in emoji_pkg.EMOJI_DATA:
if 'alias' in emoji_pkg.EMOJI_DATA[emj]:
diff = aliases.difference(a[1:-1] for a in emoji_pkg.EMOJI_DATA[emj]['alias'])
else:
diff = aliases
for a in diff:
new_aliases.append(f"# alias NEW {a} FOR {emj} CODE {code}")
# Keep the order of existing aliases intact
if emj in emoji_pkg.EMOJI_DATA and 'alias' in emoji_pkg.EMOJI_DATA[emj]:
aliases = [a[1:-1] for a in emoji_pkg.EMOJI_DATA[emj]['alias']] + [a for a in aliases if f":{a}:" not in emoji_pkg.EMOJI_DATA[emj]['alias']]
if any("flag_for_" in a for a in aliases):
# Put the :flag_for_COUNTRY: alias as the first entry so that it gets picked by demojize()
# This ensures compatibility because in the past there was only the :flag_for_COUNTRY: alias
aliases = [a for a in aliases if "flag_for_" in a]
aliases += [a for a in aliases if "flag_for_" not in a]
# emoji to dict
emojis_data[emj] = [v["en"].lower()]
for a in aliases:
emojis_data[emj].append(a)
if v["status"] == "fully_qualified":
f += 1
elif v["status"] == "component":
c += 1
with open("emojis.json", "w") as outfile:
json.dump(emojis_data, outfile, sort_keys=True, indent=2)
logging.debug(f" # Total count of emojis: {len(emojis)}")
logging.debug(f" # fully_qualified: {f}")
logging.debug(f" # component: {c}\n")
logging.debug("\n".join(new_aliases))
# Check if all aliases from GitHub API were used
for github_alias in github_alias_dict:
if github_alias not in used_github_aliases:
logging.debug(f"# Unused Github alias: {github_alias} {github_alias_dict[github_alias]} {ascii(github_alias_dict[github_alias])}")
logging.info('\n\n Done.')

View File

@ -0,0 +1,3 @@
emoji>=2.8.0
requests>=2.31.0
beautifulsoup4>=4.12.2

View File

@ -0,0 +1,13 @@
@tool
extends EditorInspectorPlugin
func _can_handle(object: Object):
return object is FontIconSettings
func _parse_begin(object: Object) -> void:
var ref: FontIconSettings = object
var preview := FontIcon.new()
preview.icon_settings = ref
preview.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
preview.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
add_custom_control(preview)

View File

@ -0,0 +1 @@
uid://cqr75ldm2km24

View File

@ -0,0 +1,27 @@
@tool
@icon("res://addons/icons-fonts/nodes/FontIcon.svg")
# todo add description and docs links when ready
class_name FontIcon
extends Label
@export var icon_settings := FontIconSettings.new()
func _ready():
_on_icon_settings_changed()
Utils.connect_if_possible(
icon_settings, "changed", _on_icon_settings_changed)
func _on_icon_settings_changed():
if !label_settings:
label_settings = LabelSettings.new()
icon_settings.update_label_settings(label_settings)
text = IconsFonts.get_icon_char(
icon_settings.icon_font,
icon_settings.icon_name
)
func _validate_property(property : Dictionary) -> void:
if property.name in [&"text", &"label_settings"]:
property.usage &= ~PROPERTY_USAGE_EDITOR

View File

@ -0,0 +1 @@
uid://sfoyl3fbn8so

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg4"
sodipodi:docname="MaterialIcon.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
inkscape:export-filename="/home/jeremi360/DevProjects/Godot/Godot-Material-Icons/icon.png"
inkscape:export-xdpi="256"
inkscape:export-ydpi="256"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="33.125"
inkscape:cx="12.030189"
inkscape:cy="12.015094"
inkscape:window-width="1920"
inkscape:window-height="1002"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
inkscape:showpageshadow="0"
inkscape:deskcolor="#505050" />
<path
d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M13.96,12.29L11.21,15.83L9.25,13.47L6.5,17H17.5L13.96,12.29Z"
id="path2"
style="fill:#8eef97;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drissyo4imc7c"
path="res://.godot/imported/FontIcon.svg-1bab4063a82703b60d9abf5769a35adc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/icons-fonts/nodes/FontIcon.svg"
dest_files=["res://.godot/imported/FontIcon.svg-1bab4063a82703b60d9abf5769a35adc.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

View File

@ -0,0 +1,133 @@
@tool
@icon("res://addons/icons-fonts/nodes/FontIconButton.svg")
# todo add description and docs links when ready
class_name FontIconButton
extends ButtonContainer
@export_group("Layout", "layout_")
@export_enum("Label-Icon", "Icon-Label", "Icon")
var layout_order := "Label-Icon":
set(value):
layout_order = value
if !is_node_ready(): await ready
_set_order(value)
@export var layout_vertical := false:
set(value):
layout_vertical = value
if !is_node_ready(): await ready
_box.vertical = value
@export var layout_alignment := BoxContainer.ALIGNMENT_CENTER:
set(value):
layout_alignment = value
if !is_node_ready(): await ready
_box.alignment = value
@export_group("Icon", "icon_")
@export var icon_settings := FontIconSettings.new():
set(value):
icon_settings = value
if !is_node_ready(): await ready
_font_icon.icon_settings = value
@export_group("Label", "label_")
@export var label_text := "":
set(value):
label_text = value
if !is_node_ready(): await ready
_label.text = label_text
@export var label_settings := LabelSettings.new():
set(value):
label_settings = value
if !is_node_ready(): await ready
_label.label_settings = label_settings
@export var button_margin := 5:
set(value):
button_margin = value
if !is_node_ready(): await ready
var margins := [
"margin_top", "margin_left",
"margin_bottom", "margin_right"
]
for margin in margins:
_margins.add_theme_constant_override(margin, button_margin)
var _font_icon: FontIcon
var _label: Label
var _box: BoxContainer
var _margins: MarginContainer
func _get_lay_dict() -> Dictionary:
return {
"Label": _label,
"Icon": _font_icon,
}
func _add_icon(_icon_settings: FontIconSettings) -> FontIcon:
var empty_style := StyleBoxEmpty.new()
var _icon = FontIcon.new()
_icon.add_theme_stylebox_override("normal", empty_style)
Utils.connect_if_possible(_icon_settings, "changed",
func(): update_icon(_icon_settings, _icon))
return _icon
func _ready():
super._ready()
for ch: Control in get_children():
ch.queue_free()
ready.connect(func(): self.layout_order = layout_order)
var empty_style := StyleBoxEmpty.new()
_box = BoxContainer.new()
_font_icon = _add_icon(icon_settings)
_label = Label.new()
_label.add_theme_stylebox_override("normal", empty_style)
_margins = MarginContainer.new()
_margins.add_child(_box)
add_child(_margins)
Utils.connect_if_possible(
label_settings, "changed",
func():
if label_settings != _label.label_settings:
_label.label_settings = label_settings
)
func update_icon(new_icon_settings: FontIconSettings, font_icon: FontIcon):
if new_icon_settings != font_icon.icon_settings:
font_icon.icon_settings = new_icon_settings
Utils.connect_if_possible(
new_icon_settings, "changed",
font_icon._on_icon_settings_changed)
func _clear_box():
if _box.get_child_count() == 0: return
for ch: Control in _box.get_children():
_box.remove_child(ch)
func _set_order(order:String):
_clear_box()
await get_tree().create_timer(0.2).timeout
_apply_layout(_crate_layout(order))
func _crate_layout(order:String) -> Array[Control]:
var layout: Array[Control] = []
var order_split := order.split("-")
var dict := _get_lay_dict()
for con in order_split:
layout.append(dict[con])
return layout
func _apply_layout(layout: Array[Control]):
for control: Control in layout:
if control.get_parent() == _box: continue
_box.add_child(control)
if control is FontIcon:
control.size_flags_horizontal\
= Control.SIZE_SHRINK_CENTER

View File

@ -0,0 +1 @@
uid://pwyh8365is28

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg4"
sodipodi:docname="MaterialButton.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="33.125"
inkscape:cx="6.0830189"
inkscape:cy="12.015094"
inkscape:window-width="1920"
inkscape:window-height="1002"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
inkscape:showpageshadow="0"
inkscape:deskcolor="#505050" />
<path
d="M10.76,8.69A0.76,0.76 0 0,0 10,9.45V20.9C10,21.32 10.34,21.66 10.76,21.66C10.95,21.66 11.11,21.6 11.24,21.5L13.15,19.95L14.81,23.57C14.94,23.84 15.21,24 15.5,24C15.61,24 15.72,24 15.83,23.92L18.59,22.64C18.97,22.46 19.15,22 18.95,21.63L17.28,18L19.69,17.55C19.85,17.5 20,17.43 20.12,17.29C20.39,16.97 20.35,16.5 20,16.21L11.26,8.86L11.25,8.87C11.12,8.76 10.95,8.69 10.76,8.69M15,10V8H20V10H15M13.83,4.76L16.66,1.93L18.07,3.34L15.24,6.17L13.83,4.76M10,0H12V5H10V0M3.93,14.66L6.76,11.83L8.17,13.24L5.34,16.07L3.93,14.66M3.93,3.34L5.34,1.93L8.17,4.76L6.76,6.17L3.93,3.34M7,10H2V8H7V10"
id="path2"
style="fill:#8eef97;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dc2hiifebeghw"
path="res://.godot/imported/FontIconButton.svg-bebb139a7209cbe8285b2004832e8f79.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/icons-fonts/nodes/FontIconButton.svg"
dest_files=["res://.godot/imported/FontIconButton.svg-bebb139a7209cbe8285b2004832e8f79.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

View File

@ -0,0 +1,67 @@
@tool
@icon("res://addons/icons-fonts/nodes/FontIconButton.svg")
# todo add description and docs links when ready
class_name FontIconCheckButton
extends FontIconButton
@export var on_icon_settings := FontIconSettings.new():
set(value):
on_icon_settings = value
if !is_node_ready(): await ready
_toggle_icon_on.icon_settings = value
@export var off_icon_settings := FontIconSettings.new():
set(value):
off_icon_settings = value
if !is_node_ready(): await ready
_toggle_icon_off.icon_settings = value
var _toggle_icon_on: FontIcon
var _toggle_icon_off: FontIcon
var _toggle_icon_box: BoxContainer
func _ready():
toggle_mode = true
if "Toggle" not in layout_order:
layout_order = "Label-Icon-Toggle"
super._ready()
_toggle_icon_box = BoxContainer.new()
_toggle_icon_on = _add_icon(on_icon_settings)
_toggle_icon_on.visible = button_pressed
_toggle_icon_box.add_child(_toggle_icon_on)
_toggle_icon_off = _add_icon(off_icon_settings)
_toggle_icon_off.visible = !button_pressed
_toggle_icon_box.add_child(_toggle_icon_off)
func _on_on_icon_changed():
update_icon(on_icon_settings, _toggle_icon_on)
func _on_off_icon_changed():
update_icon(off_icon_settings, _toggle_icon_on)
func _togglef(main_button: ButtonContainer, value: bool):
if disabled: return
if main_button == self: return
_toggle_icon_on.visible = value
_toggle_icon_off.visible = !value
super._togglef(main_button, value)
func _get_lay_dict() -> Dictionary:
return {
"Label": _label,
"Icon": _font_icon,
"Toggle": _toggle_icon_box
}
func _validate_property(property : Dictionary) -> void:
if property.name == &"layout_order":
property.hint_string = ",".join([
"Label-Icon-Toggle", "Label-Toggle-Icon",
"Toggle-Label-Icon", "Toggle-Icon-Label",
"Icon-Label-Toggle", "Icon-Toggle-Label",
"Label-Toggle", "Toggle-Label", "Toggle"
])

View File

@ -0,0 +1 @@
uid://c4n2aki4724e4

View File

@ -0,0 +1,7 @@
[plugin]
name="Godot Icons Fonts"
description="Makes easy to find and use icons from popular icon-fonts in your Godot project."
author="Jeremi Biernacki"
version="1.2.4"
script="icons_fonts.gd"

View File

@ -0,0 +1,67 @@
@tool
@icon("res://addons/icons-fonts/resources/FontIconSettings.svg")
class_name FontIconSettings
extends Resource
@export_enum("MaterialIcons", "Emojis")
var icon_font := "MaterialIcons"
## Name of Icon to display
@export var icon_name := "image-outline":
set(value):
icon_name = value
emit_changed()
## Size of the icon in range 16-128
@export_range(16, 128, 1)
var icon_size := 16:
set(value):
icon_size = value
emit_changed()
@export var icon_color := Color.WHITE:
set(value):
icon_color = value
emit_changed()
@export_group("Outline", "outline_")
@export var outline_color := Color.WHITE:
set(value):
outline_color = value
emit_changed()
@export var outline_size := 0:
set(value):
outline_size = value
emit_changed()
@export_group("Shadow", "shadow_")
@export var shadow_color := Color.WHITE:
set(value):
shadow_color = value
emit_changed()
@export var shadow_size := 0:
set(value):
shadow_size = value
emit_changed()
@export var shadow_offset := Vector2.ZERO:
set(value):
shadow_offset = value
emit_changed()
func update_label_settings(label_settings: LabelSettings) -> void:
match icon_font:
"MaterialIcons":
label_settings.font = load(IconsFonts.material_icons_font)
"Emojis":
label_settings.font = load(IconsFonts.emojis_font)
label_settings.font_size = icon_size
label_settings.font_color = icon_color
label_settings.outline_color = outline_color
label_settings.outline_size = outline_size
label_settings.shadow_color = shadow_color
label_settings.shadow_offset = shadow_offset
label_settings.shadow_size = shadow_size

View File

@ -0,0 +1 @@
uid://dpyof2t0wn8k1

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e8eaed"
version="1.1"
id="svg1"
sodipodi:docname="FontIconSettings.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="33.708333"
inkscape:cx="11.985167"
inkscape:cy="12"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"
id="path1"
style="fill:#8eef97;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://87qmm7ao6o7k"
path="res://.godot/imported/FontIconSettings.svg-52a3d7fa565f0354cb3aa78380db99bf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/icons-fonts/resources/FontIconSettings.svg"
dest_files=["res://.godot/imported/FontIconSettings.svg-52a3d7fa565f0354cb3aa78380db99bf.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

View File

@ -0,0 +1,9 @@
@tool
extends ButtonContainer
@onready var label: Label = $Label
func _on_toggled(value: bool) -> void:
if value: label.text = "Pressed"
else: label.text = "UnPressed"

View File

@ -0,0 +1 @@
uid://b31livnmee5ew

View File

@ -0,0 +1,86 @@
[gd_scene load_steps=3 format=3 uid="uid://bmpgup35ifpet"]
[ext_resource type="Script" path="res://addons/rakugo-nodes/examples/button_container_example.gd" id="1_dy71b"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g1pbs"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.1, 0.1, 0.1, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[node name="Example" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="BoxContainer2" type="BoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -129.5
offset_top = -20.0
offset_right = 129.5
offset_bottom = 20.0
grow_horizontal = 2
grow_vertical = 2
vertical = true
[node name="Label" type="Label" parent="BoxContainer2"]
layout_mode = 2
text = "RadioMode Test"
[node name="BoxContainer" type="BoxContainer" parent="BoxContainer2"]
layout_mode = 2
[node name="ButtonContainer" type="PanelContainer" parent="BoxContainer2/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_g1pbs")
script = ExtResource("1_dy71b")
toggle_mode = true
radio_mode = true
button_pressed = true
button_group = &"example"
[node name="Label" type="Label" parent="BoxContainer2/BoxContainer/ButtonContainer"]
layout_mode = 2
text = "Pressed"
[node name="ButtonContainer2" type="PanelContainer" parent="BoxContainer2/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_g1pbs")
script = ExtResource("1_dy71b")
toggle_mode = true
radio_mode = true
button_group = &"example"
[node name="Label" type="Label" parent="BoxContainer2/BoxContainer/ButtonContainer2"]
layout_mode = 2
text = "UnPressed"
[node name="ButtonContainer3" type="PanelContainer" parent="BoxContainer2/BoxContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_g1pbs")
script = ExtResource("1_dy71b")
toggle_mode = true
radio_mode = true
button_group = &"example"
[node name="Label" type="Label" parent="BoxContainer2/BoxContainer/ButtonContainer3"]
layout_mode = 2
text = "UnPressed"
[connection signal="toggled" from="BoxContainer2/BoxContainer/ButtonContainer" to="BoxContainer2/BoxContainer/ButtonContainer" method="_on_toggled"]
[connection signal="toggled" from="BoxContainer2/BoxContainer/ButtonContainer2" to="BoxContainer2/BoxContainer/ButtonContainer2" method="_on_toggled"]
[connection signal="toggled" from="BoxContainer2/BoxContainer/ButtonContainer3" to="BoxContainer2/BoxContainer/ButtonContainer3" method="_on_toggled"]

View File

@ -0,0 +1,139 @@
@tool
@icon("res://addons/custom-ui-elements/nodes/ButtonContainer.svg")
extends PanelContainer
# todo add description and docs links when ready
class_name ButtonContainer
## Emitted when button is pressed
signal pressed
## Emitted when button is toggled
## Works only if `toggle_mode` is on.
signal toggled(value: bool)
signal state_changed(state_name: StringName)
## If true, button will be disabled
@export var disabled := false:
set(value):
disabled = value
if disabled:
_change_stylebox("disabled")
state_changed.emit(&"disabled")
return
_change_stylebox("normal")
state_changed.emit(&"normal")
## If true, button will be in toggle mode
@export var toggle_mode := false
var _toggled := false:
get: return _toggled
## If true, button will be in pressed state
@export var button_pressed := false:
set(value):
if toggle_mode:
button_pressed = value
_togglef(null, value)
return
emit_signal("pressed")
## Name of node group to be used as button group
## It changes all buttons with toggle_mode in group into radio buttons
@export var button_group: StringName = ""
@export_group("Styles", "style_")
@export var style_normal: StyleBox:
set(value):
style_normal = value
if !disabled or !button_pressed:
_change_stylebox("normal")
@export var style_focus: StyleBox
@export var style_pressed: StyleBox:
set(value):
style_pressed = value
if !disabled and button_pressed:
_change_stylebox("pressed")
@export var style_hover: StyleBox
@export var style_disabled: StyleBox:
set(value):
style_disabled = value
if disabled:
_change_stylebox("disabled")
var current_style: StringName
func _ready() -> void:
_change_stylebox("normal")
state_changed.emit(&"normal")
Utils.connect_if_possible(self, &"mouse_entered", _on_mouse_entered)
Utils.connect_if_possible(self, &"mouse_exited", _on_mouse_exited)
if button_group: add_to_group(button_group)
func _on_mouse_exited():
if disabled: return
if toggle_mode and _toggled: return
_change_stylebox("normal")
state_changed.emit(&"normal")
func _on_mouse_entered():
if disabled: return
_change_stylebox("hover")
state_changed.emit(&"hover")
func _change_stylebox(button_style: StringName = "normal"):
# prints("changed style to:", button_style)
var stylebox := get_theme_stylebox(button_style, "Button")
match button_style:
"normal":
stylebox = style_normal if style_normal else stylebox
"focus":
stylebox = style_focus if style_focus else stylebox
"pressed":
stylebox = style_pressed if style_pressed else stylebox
"hover":
stylebox = style_hover if style_hover else stylebox
"disabled":
stylebox = style_disabled if style_disabled else stylebox
if current_style != button_style:
current_style = button_style
add_theme_stylebox_override("panel", stylebox)
func _gui_input(event: InputEvent) -> void:
if disabled: return
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
if toggle_mode:
var t := !_toggled
_togglef(null, t)
if button_group:
get_tree().call_group(
button_group, "_togglef", self, !t)
return
state_changed.emit(&"pressed")
pressed.emit()
func _togglef(main_button: ButtonContainer, value: bool):
if disabled: return
if main_button == self: return
_toggled = value
if value:
_change_stylebox("pressed")
state_changed.emit(&"pressed")
else:
_change_stylebox("normal")
state_changed.emit(&"normal")
toggled.emit(_toggled)

View File

@ -0,0 +1 @@
uid://c63iqxnesjokx

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="24px"
viewBox="0 -960 960 960"
width="24px"
fill="#e8eaed"
version="1.1"
id="svg1"
sodipodi:docname="ButtonBox.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="33.708333"
inkscape:cx="11.777503"
inkscape:cy="11.851669"
inkscape:window-width="1920"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="40"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M160-240q-33 0-56.5-23.5T80-320v-320q0-33 23.5-56.5T160-720h640q33 0 56.5 23.5T880-640v320q0 33-23.5 56.5T800-240H160Zm0-80h640v-320H160v320Zm130-40h60v-90h90v-60h-90v-90h-60v90h-90v60h90v90Zm-130 40v-320 320Z"
id="path1"
style="fill:#8eef97;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d4nquc8rmh42g"
path="res://.godot/imported/ButtonContainer.svg-2244ab290648c46839f5024e765ecf96.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/rakugo-nodes/nodes/ButtonContainer.svg"
dest_files=["res://.godot/imported/ButtonContainer.svg-2244ab290648c46839f5024e765ecf96.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

View File

@ -0,0 +1,7 @@
[plugin]
name="Rakugo Nodes"
description="Collection of various custom Nodes"
author="Jeremi Biernacki"
version="1.1"
script="rakugo_nodes.gd"

View File

@ -0,0 +1,8 @@
@tool
extends EditorPlugin
func _enter_tree():
pass
func _exit_tree():
pass

View File

@ -0,0 +1 @@
uid://dk1343vabbdpj

View File

@ -0,0 +1,27 @@
class_name Utils
extends Object
## Is shortcut of:
## if !object.is_connected(signal_name, method):
## object.connect(signal_name, method)
static func connect_if_possible(
object:Object, signal_name:StringName, method:Callable):
if !object.is_connected(signal_name, method):
object.connect(signal_name, method)
## Is shortcut of:
## if object.is_connected(signal_name, method):
## object.disconnect(signal_name, method)
static func disconnect_if_possible(
object:Object, signal_name:StringName, method:Callable):
if object.is_connected(signal_name, method):
object.disconnect(signal_name, method)
## Is shortcut of:
## disconnect_if_possible(object, signal_name, old_method)
## connect_if_possible(object, signal_name, new_method)
static func change_signal(
object:Object, signal_name:StringName,
old_method:Callable, new_method:Callable):
disconnect_if_possible(object, signal_name, old_method)
connect_if_possible(object, signal_name, new_method)

View File

@ -0,0 +1 @@
uid://1cdqubssue7k

File diff suppressed because one or more lines are too long

View File

@ -55,6 +55,7 @@ consumers = [null]
metadata/_custom_type_script = "uid://c4fquatkjmsgu"
[node name="MultiMeshInstance3D" type="MultiMeshInstance3D" parent="."]
physics_interpolation_mode = 2
multimesh = SubResource("MultiMesh_6iypd")
[connection signal="item_added" from="Consumer" to="." method="consumer_has_item"]

View File

@ -78,6 +78,7 @@ transform = Transform3D(1, 0, 0, 0, 1.31134e-07, 1, 0, -1, 1.31134e-07, 0, 0, 0)
shape = SubResource("CapsuleShape3D_3ndsa")
[node name="GPUTrail3D" type="GPUParticles3D" parent="."]
physics_interpolation_mode = 2
transform = Transform3D(0.233971, -0.972243, 0, 0.972243, 0.233971, 0, 0, 0, 1, 0, 0, 0)
amount = 19
lifetime = 19.0

View File

@ -7,3 +7,6 @@
collision_layer = 4
collision_mask = 31
script = ExtResource("2_14ipn")
[node name="Label3D" parent="." index="4"]
text = "awawawawawa"

View File

@ -27,15 +27,25 @@ config/name="TowerGame3D"
run/main_scene="uid://bwftban1ppo17"
config/features=PackedStringArray("4.4")
config/icon="uid://u1hpdb62rxlc"
addons/icon_finder/preview_size=24
[autoload]
IconsFonts="*res://addons/icons-fonts/icons_fonts/IconsFonts.gd"
[debug]
settings/stdout/print_fps=true
shapes/navigation/enable_edge_connections=false
shapes/navigation/enable_edge_connections_xray=false
shapes/navigation/enable_edge_lines_xray=false
shapes/navigation/enable_link_connections_xray=false
shapes/navigation/enable_agent_paths_xray=false
[display]
window/vsync/vsync_mode=2
[editor]
version_control/plugin_name="GitPlugin"
@ -43,12 +53,44 @@ version_control/autoload_on_startup=true
[editor_plugins]
enabled=PackedStringArray("res://addons/GPUTrail/plugin.cfg", "res://addons/PathMesh3D/plugin.cfg", "res://addons/git_describe/plugin.cfg", "res://addons/resources_spreadsheet_view/plugin.cfg")
enabled=PackedStringArray("res://addons/GPUTrail/plugin.cfg", "res://addons/PathMesh3D/plugin.cfg", "res://addons/git_describe/plugin.cfg", "res://addons/icons-fonts/plugin.cfg", "res://addons/rakugo-nodes/plugin.cfg", "res://addons/resources_spreadsheet_view/plugin.cfg")
[global_group]
Enemies=""
[input]
camera_forward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":-1.0,"script":null)
]
}
camera_backward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":3,"axis_value":1.0,"script":null)
]
}
camera_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
]
}
camera_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
]
}
camera_set_target={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":3,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
[layer_names]
3d_render/layer_1="Terrain"
@ -68,6 +110,10 @@ Enemies=""
3d/default_edge_connection_margin=0.6
3d/default_link_connection_radius=0.5
[physics]
common/physics_interpolation=true
[rendering]
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=0

View File

@ -80,6 +80,7 @@ func fire_at_target() -> void:
bullet.look_at(aim_target)
bullet.apply_impulse((aim_target - bullet.global_position).normalized()*shot_impulse)
bullet.angular_velocity = (bullet.global_basis * Vector3(0,0,-10))
current_target = null
ammo -= 1
if ammo <= 0:

View File

@ -5,7 +5,7 @@ extends RigidBody3D
@export var lifetime: float = 2.0
func _on_body_entered(body: Node) -> void:
print("Bullet collided with %s" % body.name)
#print("Bullet collided with %s" % body.name)
if body is Unit:
var speed: float = linear_velocity.length()
var damage: float = speed * damage_per_speed

86
scripts/camera.gd Normal file
View File

@ -0,0 +1,86 @@
extends Camera3D
@export var pan_speed: float = 10
var focus_offset: Vector3 = Vector3(10, 10, 10)
var focus_object: Node3D = null
var focus_position: Vector3
var last_mouse_position: Vector2
@onready var focus_marker: Node3D = $"Camera Focus"
func _physics_process(delta: float) -> void:
var mouse_position: Vector2 = get_viewport().get_mouse_position()
var ray_origin: Vector3 = project_ray_origin(mouse_position)
var ray_normal: Vector3 = ray_origin + project_ray_normal(mouse_position) * 200
var params = PhysicsRayQueryParameters3D.create(ray_origin, ray_normal)
var result: Dictionary = get_world_3d().direct_space_state.intersect_ray(params)
if result.has("position"):
DebugDraw3D.draw_sphere(result["position"], 0.5, Color.REBECCA_PURPLE)
if Input.is_action_just_released("camera_set_target"):
if result.has("collider") and !(result["collider"] is GridMap):
focus_object = result["collider"]
else:
focus_object = null
focus_position = result["position"]
if focus_object != null:
focus_position = focus_object.global_position
process_focus_movement(delta)
process_camera_rotation(delta)
var target_position: Vector3 = focus_position + focus_offset
if target_position.distance_squared_to(global_position) > 0:
global_position = global_position.move_toward(target_position, 30*(1/60.0))
else:
look_at(focus_position)
#global_position = global_position.lerp(target_position, 0.05)
#basis = basis.slerp(Basis.looking_at(focus_position - global_position), 0.05)
focus_marker.global_position = focus_position
#look_at(focus_position)
func process_focus_movement(delta: float) -> void:
var camera_forwards: Vector3 = -global_basis.z
camera_forwards = camera_forwards.slide(Vector3.UP).normalized()
var input_basis: Basis = Basis.looking_at(camera_forwards)
var x_input: float = Input.get_axis("camera_left", "camera_right")
var y_input: float = Input.get_axis("camera_backward", "camera_forward")
var input: Vector3 = Vector3(x_input, 0, -y_input).limit_length(1)
if input.is_zero_approx():
return
if focus_object != null:
focus_object = null
input = input_basis * input
var movement: Vector3 = input * pan_speed * delta
global_position += movement
focus_position += movement
# handle moving on/off ramps
var params: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.new()
params.from = focus_position + Vector3(0,2,0)
params.to = focus_position - Vector3(0,2,0)
var result: Dictionary = get_world_3d().direct_space_state.intersect_ray(params)
if result.has("position"):
focus_position = result["position"]
DebugDraw3D.draw_gizmo(Transform3D(input_basis, focus_position))
func process_camera_rotation(delta: float) -> void:
var mouse_position: Vector2 = get_viewport().get_mouse_position()
var mouse_movement: Vector2 = mouse_position - last_mouse_position
last_mouse_position = mouse_position
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
focus_offset = focus_offset.rotated(Vector3.UP, -mouse_movement.x * 0.006)
var vertical_axis: Vector3 = focus_offset.cross(Vector3.UP).normalized()
focus_offset = focus_offset.rotated(vertical_axis, mouse_movement.y * 0.006)
global_position = focus_position + focus_offset
look_at(focus_position)

1
scripts/camera.gd.uid Normal file
View File

@ -0,0 +1 @@
uid://tx3j02eib7ba

6
scripts/camera_focus.gd Normal file
View File

@ -0,0 +1,6 @@
extends Node3D
@export var movement_speed: float = 5.0
#@export_node_path("Camera3D") var camera_path: NodePath
@onready var camera: Camera3D = get_viewport().get_camera_3d()

View File

@ -0,0 +1 @@
uid://jinwrvx7tmqy

View File

@ -66,7 +66,7 @@ func unstuck() -> void:
func hurt(damage: float) -> void:
hp -= damage
print("%s hit for %f damage, HP=%f" % [name, damage, hp])
#print("%s hit for %f damage, HP=%f" % [name, damage, hp])
if hp <= 0:
die()