Showing preview only (284K chars total). Download the full file or copy to clipboard to get everything.
Repository: brombres/Godot-GameGUI
Branch: main
Commit: 1049c3a8c7d8
Files: 48
Total size: 268.9 KB
Directory structure:
gitextract_ft3ucw32/
├── .gdignore
├── .gitignore
├── Build.rogue
├── ChangeLog.md
├── DemoProject/
│ ├── .gdignore
│ ├── Border.gd
│ ├── DemoScene.tscn
│ ├── LayoutConfig.gd
│ ├── TextArea.gd
│ ├── addons/
│ │ └── GameGUI/
│ │ ├── GGButton.gd
│ │ ├── GGComponent.gd
│ │ ├── GGFiller.gd
│ │ ├── GGHBox.gd
│ │ ├── GGInitialWindowSize.gd
│ │ ├── GGLabel.gd
│ │ ├── GGLayoutConfig.gd
│ │ ├── GGLimitedSizeComponent.gd
│ │ ├── GGMarginLayout.gd
│ │ ├── GGNinePatchRect.gd
│ │ ├── GGOverlay.gd
│ │ ├── GGParameterSetter.gd
│ │ ├── GGRichTextLabel.gd
│ │ ├── GGTextureRect.gd
│ │ ├── GGVBox.gd
│ │ ├── plugin.cfg
│ │ └── plugin.gd
│ └── project.godot
├── LICENSE
├── Media/
│ ├── .gdignore
│ └── Images/
│ └── Icons/
│ └── Icons.sketch
├── README.md
└── addons/
└── GameGUI/
├── GGButton.gd
├── GGComponent.gd
├── GGFiller.gd
├── GGHBox.gd
├── GGInitialWindowSize.gd
├── GGLabel.gd
├── GGLayoutConfig.gd
├── GGLimitedSizeComponent.gd
├── GGMarginLayout.gd
├── GGNinePatchRect.gd
├── GGOverlay.gd
├── GGParameterSetter.gd
├── GGRichTextLabel.gd
├── GGTextureRect.gd
├── GGVBox.gd
├── plugin.cfg
└── plugin.gd
================================================
FILE CONTENTS
================================================
================================================
FILE: .gdignore
================================================
This file prevents Godot from directly importing these files.
================================================
FILE: .gitignore
================================================
# Demo Project
DemoProject/Test.tscn
# Godot-specific ignores
.godot
.import/
export.cfg
export_presets.cfg
*.import
# Mono-specific ignores
.mono/
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Rogo
.rogo
BuildLocal.rogue
# Vim
*.swp
# macOS
.DS_Store
================================================
FILE: Build.rogue
================================================
# To run this build file, install Rogue from github.com/brombres/Rogue then cd
# to this folder and type "rogo" at the command line, or "rogo help" for a list
# of supported commands.
$requireRogue "2.28"
uses Utility/FilePatcher
uses Utility/VersionNumber
routine rogo_default
# Runs 'rogo help'.
rogo_help
endRoutine
routine rogo_clean
# Deletes the .rogo build folders.
File( ".rogo" ).delete
endRoutine
routine rogo_update_version( version:String )
# Updates the version number and date in the project source and README.md.
if (not version) version = ""
version .= after_any("v")
localize current_version
if (version.count == 0) throw Error( "New version number expected, e.g. '1.0.2'. Current version: $"(current_version) )
if (current_version != "(none)" and VersionNumber(version) <= current_version)
error.println( "[WARNING] The new version number ($) is not higher than the current version number ($)."(version,current_version) )
endIf
block file = File( "README.md" )
if (file.exists)
local og_content = String(file)
local content = String( og_content.count+20 )
forEach (line in LineReader(og_content))
if (line.contains_pattern("Version|$(I)"))
content.println( "$| $" (line.before_last('|'),version) )
elseIf (line.contains_pattern("Date|"))
local today = Date.today->String(&verbose)
content.println( "$| $" (line.before_last('|'),today) )
else
content.println( line )
endIf
endForEach
if (content != og_content)
println "Updating version and date in $"(file)
file.save( content )
endIf
endIf
endBlock
block patcher = FilePatcher( File("addons/GameGUI/plugin.cfg") )
patcher.replace_line( "version=", ''version="$"''(version) )
patcher.save
endBlock
Files("addons/GameGUI/**").sync_to( File("DemoProject/addons/GameGUI"), &keep_unused, &verbose )
endRoutine
routine rogo_commit( new_version:String )
# Updates source and README version number and git-commits with a version number message.
new_version .= after_any("v")
if (not String.exists(new_version)) throw Error( "Expected version number after 'rogo commit'. Current version: $"(current_version) )
rogo_update_version( new_version )
if (not System.find_executable("git")) throw Error( "Git must be installed." )
if (project_has_uncommitted_changes)
execute ''git commit -am "[v$]"'' (new_version)
else
println "No changes to commit."
endIf
endRoutine
routine rogo_publish( new_version:String )
# Updates source and README version number, commits, and publishes a release to GitHub.
new_version .= after_any("v")
if (not String.exists(new_version)) throw Error( "Expected version number after 'rogo publish'. Current version: $"(current_version) )
if (not System.find_executable("gh")) throw Error( "The GitHub command line tool 'gh' must be installed." )
if (not String(File("ChangeLog.md")).contains(new_version))
throw Error( "The change log needs updating." )
endIf
rogo_commit( new_version )
# These can be hard-coded to suit your project
local cur_branch : String
local main_branch : String
if (not cur_branch)
local result = Process.run("git branch --show-current",&env)
if (result.success) cur_branch = result->String.trimmed
else throw Error( "Unable to detect current git branch." )
endIf
if (not main_branch)
local result = Process.run("git branch --list",&env)
if (result.success)
local branches = result->String.replacing('*',' ').split('\n').[modify($.trimmed)]
forEach (branch in ["main","master"])
if (branches.contains(branch)) main_branch=branch; escapeForEach
endForEach
endIf
if (not main_branch) throw Error( "Unable to detect name of main (release) branch." )
endIf
execute "git push origin $"(cur_branch)
if (cur_branch != main_branch)
if (not Console.input("$ will be merged into $. Continue? "(cur_branch,main_branch)).to_lowercase.begins_with('y')) return
execute "git checkout $"(main_branch)
execute "git pull"
execute "git merge $"(cur_branch)
execute "git push origin $"(main_branch)
execute "git checkout $"(cur_branch)
endIf
if (not Console.input("Continue publishing release to GitHub? ").to_lowercase.begins_with('y')) return
execute ''gh release create v$ --title "v$" --notes ""''(new_version,new_version)
endRoutine
routine project_has_uncommitted_changes->Logical
local result = Process.run( "git status --porcelain", &env )
if (not result.success)
Console.error.println result->String
System.exit 1
endIf
local lines = result->String.trimmed.split('\n').[discard($.begins_with("??"))]
lines.discard( $ == "" )
return not lines.is_empty
endRoutine
routine current_version->String
contingent
local file = File("README.md")
necessary (file.exists)
if local line = String(file).split('\n').find( $.contains_pattern("Version|$(I)") )
local v = line.after_first('|').trimmed
necessary (v.count)
return v
endIf
endContingent
return "(none)"
endRoutine
routine execute( commands:String, error_message=null:String, &suppress_error )->Logical
forEach (cmd in LineReader(commands))
print( "> " )
println( cmd )
if (0 != System.run(cmd))
if (suppress_error)
return false
else
if (not error_message) error_message = "Build failed."
throw Error( error_message )
endIf
endIf
endForEach
return true
endRoutine
#-------------------------------------------------------------------------------
# Introspection-based Launcher Framework
#-------------------------------------------------------------------------------
# Rogo is a "build your own build system" facilitator. At its core Rogo just
# recompiles build files if needed and then runs the build executable while
# forwarding any command line arguments. This file contains a default framework
# which uses introspection to turn command line arguments into parameterized
# routine calls.
# Example: to handle the command "rogo abc xyz 5", define
# "routine rogo_abc_xyz( n:Int32 )".
# "rogo_default" will run in the absence of any other command line argument.
# The following "comment directives" can be used in this file to control how
# RogueC compiles it and to manage automatic dependency installation and
# linking.
# Each of the following should be on a line beginning with the characters #$
# (preceding whitespace is fine). Sample args are given.
# ROGUEC = roguec # Path to roguec to compile this file with
# ROGUEC_ARGS = --whatever # Additional options to pass to RogueC
# CC = gcc -Wall -fno-strict-aliasing
# CC_ARGS = -a -b -c # Additional C args
# LINK = -lalpha -lbeta # Link this build file with these options
# LINK(macOS) = ... # Options applying only to
# # System.os=="macOS" (use with any OS and
# # any comment directive)
# LINK_LIBS = true # Links following LIBRARIES with this Build
# # file (otherwise just installs them)
# LINK_LIBS = false # Linking turned off for following
# # LIBRARIES - info can still be obtained
# # from $LIBRARY_FLAGS or $LIBRARIES(libname,...)
# LIBRARIES = libalpha
# LIBRARIES = libbeta(library-name)
# LIBRARIES = libfreetype6-dev(freetype2)
# DEPENDENCIES = Library/Rogue/**/*.rogue
#
# LIBRARIES = name(package)
# LIBRARIES = name(
# exe:<which-name>
# exists-cmd:<exists-cmd>
# flags:<library-flags>
# ignore-exe-only:<setting>
# info:<info-name>
# info-cmd:<get-info-cmd>
# install:<install-name>
# install-cmd:<install-cmd>
# link:<setting>
# package:<package-name>
# )
#
# The following macro is replaced within this file (Build.rogue) - the libraries
# should normally also be declared in #$ LIBRARIES:
#
# $LIBRARY_FLAGS(lib1,lib2) # sample macro
# ->
# -Ipath/to/lib1/include -Lpath/to/lib1/library -I ... # sample replacement
routine syntax( command:String, text:String )
Build.rogo_syntax[ command ] = text
endRoutine
routine description( command:String, text:String )
Build.rogo_descriptions[ command ] = text
endRoutine
routine help( command:String, description_text=null:String, syntax_text=null:String )
if (description_text) description( command, description_text )
if (syntax_text) syntax( command, syntax_text )
endRoutine
try
Build.launch
catch (err:Error)
Build.rogo_error = err
Build.on_error
endTry
class Build [singleton]
PROPERTIES
rogo_syntax = [String:String]
rogo_descriptions = [String:String]
rogo_prefix = "rogo_"
rogo_command = "default"
rogo_args = @[]
rogo_error : Error
LOCAL_SETTINGS_FILE = "Local.rogo"
METHODS
method launch
rogo_args.add( forEach in System.command_line_arguments )
read_defs
on_launch
parse_args
dispatch_command
method dispatch_command
local m = find_command( rogo_command )
if (not m) throw Error( "No such routine rogo_$()" (rogo_command) )
local args = @[]
forEach (arg in rogo_args)
which (arg)
case "true": args.add( true )
case "false": args.add( false )
case "null": args.add( null )
others: args.add( arg )
endWhich
endForEach
m( args )
method find_command( name:String )->MethodInfo
return <<Routine>>.find_global_method( rogo_prefix + name )
method on_error
local w = Console.width.or_smaller( 80 )
Console.error.println "=" * w
Console.error.println rogo_error->String.word_wrapped(w)
Console.error.println "=" * w
on_exit
System.exit 1
method on_command_found
noAction
method on_command_not_found
local w = Console.width.or_smaller( 80 )
println "=" * w
println "ERROR: No such command '$'." (rogo_args.first)
println "=" * w
println
rogo_command = "help"
rogo_args.clear
on_command_found
method on_launch
noAction
method on_exit
noAction
method parse_args
block
if (rogo_args.count)
local parts = String[]
parts.add( forEach in rogo_args )
rogo_args.clear
while (parts.count)
local cmd = parts.join("_")
if (find_command(cmd))
rogo_command = cmd
on_command_found
escapeBlock
endIf
rogo_args.insert( parts.remove_last )
endWhile
on_command_not_found
endIf
# Use default command
on_command_found
endBlock
method read_defs
read_defs( LOCAL_SETTINGS_FILE )
method read_defs( defs_filepath:String )
# Attempt to read defs from Local.rogo
local overrides = String[]
if (File(defs_filepath).exists)
forEach (line in LineReader(File(defs_filepath)))
if (line.contains("="))
local name = line.before_first('=').trimmed
local rhs = line.after_first('=').trimmed
local value : Value
if (rhs.begins_with('"') or rhs.begins_with('\''))
value = rhs.leftmost(-1).rightmost(-1)
elseIf (rhs.begins_with('{') or rhs.begins_with('['))
value = JSON.parse( rhs )
else
value = rhs
endIf
local p = <<Build>>.find_property( name )
if (p)
overrides.add( "$ = $" (name,value) )
p.set_value( this, value )
endIf
endIf
endForEach
endIf
method _join( value:Value )->String
local args = String[]
args.add( forEach in value )
return args.join( "_" )
endClass
routine rogo_help( command="":String )
# SYNTAX: rogo help [command]
# Displays help for a specified command or else all build commands.
command = Build._join( Build.rogo_args )
if (command.count)
local syntax = get_syntax( command )
local success = false
if (syntax)
println "SYNTAX"
println " " + syntax
println
success = true
endIf
local description = get_description( command )
if (description)
description .= replacing("<br>","\n")
local max_w = Console.width - 2
println "DESCRIPTION"
forEach (line in LineReader(description.word_wrapped(max_w)))
print( " " )
println( line )
endForEach
println
success = true
endIf
if (success)
return
else
local w = Console.width.or_smaller( 80 )
println "=" * w
println "ERROR: No such command '$'." (command)
println "=" * w
println
endIf
endIf
println "USAGE"
local entries = CommandInfo[]
local max_len = 0
forEach (m in <<Routine>>.global_methods)
if (m.name.begins_with(Build.rogo_prefix))
local name = m.name.after_first( Build.rogo_prefix )
local entry = CommandInfo( name, get_syntax(name), get_description(name) )
max_len .= or_larger( entry.syntax.count )
entries.add entry
endIf
endForEach
entries.sort( $1.name < $2.name )
max_len += 2
local max_w = Console.width
forEach (entry in entries)
print " " + entry.syntax
if (entry.@description)
local description = entry.@description.before_first( '\n' )
loop (max_len - entry.syntax.count) print ' '
contingent
sufficient (2 + max_len + description.count <= max_w)
if (description.contains(". "))
description = description.before_first( ". " ) + "."
sufficient (max_len + description.count <= max_w)
endIf
necessary (max_len + 10 <= max_w)
description = description.unright( (description.count - (max_w - max_len))+5 ) + "..."
satisfied
print description
endContingent
endIf
println
endForEach
println
endRoutine
routine get_syntax( m_name:String )->String
if (Build.rogo_syntax.contains(m_name))
return "rogo " + Build.rogo_syntax[ m_name ]
else
local m = <<Routine>>.find_global_method( Build.rogo_prefix + m_name )
if (not m) return null
local line = "rogo $" (m_name.replacing('_',' '))
line += " <$>" ((forEach in m.parameters).name)
return line
endIf
endRoutine
routine get_description( m_name:String )->String
if (Build.rogo_descriptions.contains(m_name))
return Build.rogo_descriptions[ m_name ]
else
return null
endIf
endRoutine
class CommandInfo( name:String, syntax:String, description:String );
================================================
FILE: ChangeLog.md
================================================
# GameGUI Change Log
## v1.5 (October 18, 2023)
### Text Scaling Bugfix
- Scaling text nodes previously failed to update their text size on the first layout. This is fixed in v1.5.
- If defined on any GameGUI node or child of a GameGUI node, the new `_on_resolve_size(available_size:Vector2)` event callback is called prior to resolving each component's size.
- No action is required. However, a component can adjust its sizing mode, its layout size, and/or any other properties in this callback.
- Scaling text components GGLabel, GGRichTextLabel, and GGButton formerly set their text size in `_on_update_size()`. However the reference nodes they use to determine their current font size most likely did not have their node sizes set on the first layout.
- The scaling text components now update their font size in `_on_resolve_size()` instead because any nodes higher in the tree will have resolved their size.
## v1.4.2 (October 15, 2023)
### Structural Changes For Godot AssetLib
- No actual code changes.
- Version 1.4.1 was published on Godot Asset Library.
- The 1.4.1 download is bloated with media files and there is no `.gdignore` inside the folder.
- A mininmal plug-in branch has been created, called `addon`.
- This `addon` branch will be the one linked to in the asset library.
- `addon` v1.4.1 was submitted to AssetLib but it seems like the maintainers may not be willing to approve the commit number change without a corresponding version number bump.
- Consequently this v1.4.2 release is in hopes of getting the compact version live on the asset library.
## v1.4.1 (October 14, 2023)
### [GGNinePatchRect](README.md#GGNinePatchRect)
New component GGNinePatchRect replicates NinePatchRect functionality and makes the following improvement: when the bounds of a GGNinePatchRect are smaller than its corners, the corners are proportionally shrunk to fit the available bounds.
### [Safe Area Margin Parameters](README.md#Built-In-Parameters)
Built-in parameters `safe_area_top_margin` (etc.) are now automatically set and maintained by GameGUI.
## v1.3.1 (October 10, 2023)
### GGMarginLayout fixes
- Fixed margin calculation bugs (`x` <-> `y` errors in two spots).
## v1.3 (September 23, 2023)
Aspect-Fit and Aspect-Fill sizing modes can now be mixed and matched, with e.g. one dimension using Fit and the other using Fill.
There are four possible aspect-mode combinations:
Horizontal Mode | Vertical Mode | Effect
----------------|---------------|-------
Aspect-Fit | Aspect-Fit | Component maintains the specified aspect ratio and is sized as large as possible while still fitting in the available area.
Aspect-Fill | Aspect-Fill | Component maintains the specified aspect ratio and is sized as small as possible while still completely filling the available area.
Aspect-Fill | Aspect-Fit | Component occupies all available width while maintaining the specified aspect ratio.
Aspect-Fit | Aspect-Fill | Component occupies all available height while maintaining the specified aspect ratio.
## v1.2 (September 9, 2023)
### GGOverlay supports scale > 1.0
H&V Scale Constants can now be manually set to values > 1.0.
### GGHBox and GGVBox Content Alignment <font color=red>[breaking change]</font>
GGHBox and GGVBox now have a **Content Alignment** property which specifies how the content as a whole is aligned
if it is larger than or smaller than the size of the box. For GGHBox the options are **Left**, **Center**, and **Right**.
For GGVBox the options are **Top**, **Center**, and **Bottom**.
This is a breaking change because the default for both is **Center** whereas the previous layout behavior matched
a default of **Left** for GGHBox or **Top** for GGVBox. If an existing GameGUI project has misaligned elements
after the update then there are GGHBox and GGVBox components that need their Content Alignment to be set to Left or Top.
## v1.1 (September 5, 2023)
### GGHBox and GGVBox more robust
More robust layout of Shrink-to-Fit children.
- OLD:
- Shrink-to-Fit children counted as fixed size for layout purposes.
- Assumption was that STF children had fixed-size children.
- NEW
- STF children given their maximum size during layout.
- STF children may base their size on the available space given to STF, which in turn affects STF final size.
- STF child sizes no longer prematurely assumed.
## v1.0.1 (September 3, 2023)
- Original release.
================================================
FILE: DemoProject/.gdignore
================================================
Godot will ignore this folder when installing the GameGUI addon.
================================================
FILE: DemoProject/Border.gd
================================================
@tool
extends Control
func _draw():
draw_rect( Rect2(position,size), Color(1,1,1), false, 1 )
================================================
FILE: DemoProject/DemoScene.tscn
================================================
[gd_scene load_steps=15 format=3 uid="uid://s72astt3jqi8"]
[ext_resource type="Script" path="res://addons/GameGUI/GGInitialWindowSize.gd" id="1_vk4tw"]
[ext_resource type="Script" path="res://addons/GameGUI/GGComponent.gd" id="2_g0xq4"]
[ext_resource type="Texture2D" uid="uid://bho7el5ufsu4h" path="res://Background.png" id="2_yy4fm"]
[ext_resource type="Script" path="res://addons/GameGUI/GGTextureRect.gd" id="3_yjvjq"]
[ext_resource type="Script" path="res://addons/GameGUI/GGOverlay.gd" id="5_1wg16"]
[ext_resource type="Texture2D" uid="uid://bebonjfbutevo" path="res://Godot.png" id="6_a1gd3"]
[ext_resource type="Script" path="res://addons/GameGUI/GGLabel.gd" id="7_7fgfl"]
[ext_resource type="Script" path="res://addons/GameGUI/GGVBox.gd" id="7_u7o6r"]
[ext_resource type="Script" path="res://addons/GameGUI/GGFiller.gd" id="8_evd4p"]
[ext_resource type="Script" path="res://addons/GameGUI/GGMarginLayout.gd" id="9_jlyx2"]
[ext_resource type="Script" path="res://addons/GameGUI/GGHBox.gd" id="11_y6opp"]
[ext_resource type="Script" path="res://TextArea.gd" id="12_kat07"]
[ext_resource type="Script" path="res://addons/GameGUI/GGButton.gd" id="13_a6138"]
[ext_resource type="Script" path="res://Border.gd" id="14_6xi7d"]
[node name="GGInitialWindowSize" type="Container"]
texture_filter = 4
offset_right = 800.0
offset_bottom = 600.0
script = ExtResource("1_vk4tw")
initial_window_size = Vector2(800, 600)
layout_size = Vector2(1, 1)
[node name="GGComponent - Size Ref" type="Container" parent="."]
layout_mode = 2
mouse_filter = 2
script = ExtResource("2_g0xq4")
horizontal_mode = 1
vertical_mode = 1
layout_size = Vector2(1, 1)
[node name="BG" type="Container" parent="."]
layout_mode = 2
mouse_filter = 2
script = ExtResource("2_g0xq4")
[node name="GGTextureRect" type="TextureRect" parent="BG"]
layout_mode = 2
texture = ExtResource("2_yy4fm")
expand_mode = 1
script = ExtResource("3_yjvjq")
horizontal_mode = 2
vertical_mode = 2
layout_size = Vector2(960, 448)
is_configured = true
[node name="ColorRect" type="ColorRect" parent="BG"]
layout_mode = 2
color = Color(0, 0, 0, 0.266667)
[node name="Overlays" type="Container" parent="."]
layout_mode = 2
mouse_filter = 2
script = ExtResource("2_g0xq4")
[node name="GGOverlay" type="Container" parent="Overlays"]
layout_mode = 2
script = ExtResource("5_1wg16")
h_scale_constant = 0.3561
[node name="GGTextureRect - Icon" type="TextureRect" parent="Overlays/GGOverlay"]
layout_mode = 2
texture = ExtResource("6_a1gd3")
expand_mode = 1
script = ExtResource("3_yjvjq")
horizontal_mode = 1
vertical_mode = 1
layout_size = Vector2(128, 128)
is_configured = true
[node name="GGOverlay2" type="Container" parent="Overlays"]
layout_mode = 2
script = ExtResource("5_1wg16")
child_y = 0.1515
h_scale_constant = 0.3561
[node name="GGLabel - GameGUI" type="Label" parent="Overlays/GGOverlay2" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_font_sizes/font_size = 100
text = "GameGUI"
horizontal_alignment = 1
script = ExtResource("7_7fgfl")
text_size_mode = 1
reference_node = NodePath("../../../BG")
reference_node_height = 480
reference_font_size = 80
vertical_mode = 5
layout_size = Vector2(0, 137)
is_configured = true
[node name="GGOverlay3" type="Container" parent="Overlays"]
layout_mode = 2
script = ExtResource("5_1wg16")
child_y = 0.25
h_scale_constant = 0.3561
[node name="GGLabel - GGLabel" type="Label" parent="Overlays/GGOverlay3" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 0.533333)
theme_override_font_sizes/font_size = 25
text = "[GGOverlay + GGLabel]"
horizontal_alignment = 1
script = ExtResource("7_7fgfl")
text_size_mode = 1
reference_node = NodePath("../../../BG")
reference_node_height = 480
reference_font_size = 20
vertical_mode = 5
layout_size = Vector2(0, 35)
is_configured = true
[node name="GGOverlay4" type="Container" parent="Overlays"]
layout_mode = 2
script = ExtResource("5_1wg16")
child_y = 0.6742
h_scale_constant = 0.3561
[node name="GGLabel - GGTextureRect" type="Label" parent="Overlays/GGOverlay4" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 0.533333)
theme_override_font_sizes/font_size = 25
text = "[GGOverlay + GGTextureRect]"
horizontal_alignment = 1
script = ExtResource("7_7fgfl")
text_size_mode = 1
reference_node = NodePath("../../../BG")
reference_node_height = 480
reference_font_size = 20
vertical_mode = 5
layout_size = Vector2(0, 35)
is_configured = true
[node name="GGVBox - Text Area" type="Container" parent="."]
layout_mode = 2
script = ExtResource("7_u7o6r")
[node name="GGFiller" type="Container" parent="GGVBox - Text Area"]
layout_mode = 2
size_flags_stretch_ratio = 2.5
mouse_filter = 2
script = ExtResource("8_evd4p")
[node name="GGMarginLayout - Outer" type="Container" parent="GGVBox - Text Area" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
script = ExtResource("9_jlyx2")
left_margin = 0.0333333
right_margin = 0.0333333
bottom_margin = 0.0333333
reference_node = NodePath("../../GGComponent - Size Ref")
[node name="GGMarginLayout - Inner" type="Container" parent="GGVBox - Text Area/GGMarginLayout - Outer" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
mouse_filter = 2
script = ExtResource("9_jlyx2")
left_margin = 0.00833333
top_margin = 0.00833333
right_margin = 0.00833333
bottom_margin = 0.00833333
reference_node = NodePath("../../../GGComponent - Size Ref")
[node name="GGHBox" type="Container" parent="GGVBox - Text Area/GGMarginLayout - Outer/GGMarginLayout - Inner"]
layout_mode = 2
mouse_filter = 2
script = ExtResource("11_y6opp")
[node name="GGRichTextLabel - Text Area" type="RichTextLabel" parent="GGVBox - Text Area/GGMarginLayout - Outer/GGMarginLayout - Inner/GGHBox" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_font_sizes/normal_font_size = 16
theme_override_font_sizes/bold_font_size = 16
theme_override_font_sizes/italics_font_size = 16
theme_override_font_sizes/bold_italics_font_size = 16
theme_override_font_sizes/mono_font_size = 16
bbcode_enabled = true
text = "GameGUI is a set of Godot Control nodes that provide alternative layout capabilities to the built-in Container classes. With Godot's built-in containers it is easy to make fixed-layout UI's that scale with the screen resolution OR dynamic (responsive) layouts with fixed-size controls (such as labels), but it can be difficult to make dynamic layouts with scaling controls. GameGUI solves the latter case. Click the buttons on the right for additional information."
script = ExtResource("12_kat07")
text_size_mode = 1
reference_node = NodePath("..")
reference_node_height = 145
reference_font_sizes = {
"bold": 16,
"bold_italics": 16,
"italics": 16,
"mono": 16,
"normal": 16
}
layout_size = Vector2(750, 115)
is_configured = true
[node name="ReferenceRect - Border" type="ReferenceRect" parent="GGVBox - Text Area/GGMarginLayout - Outer"]
layout_mode = 2
mouse_filter = 2
border_color = Color(1, 1, 1, 1)
editor_only = false
[node name="GGVBox2 - Side Buttons" type="Container" parent="."]
layout_mode = 2
mouse_filter = 2
script = ExtResource("7_u7o6r")
[node name="GGFiller" type="Container" parent="GGVBox2 - Side Buttons"]
layout_mode = 2
size_flags_stretch_ratio = 0.84
script = ExtResource("8_evd4p")
[node name="GGHBox" type="Container" parent="GGVBox2 - Side Buttons"]
layout_mode = 2
script = ExtResource("11_y6opp")
[node name="GGFiller" type="Container" parent="GGVBox2 - Side Buttons/GGHBox"]
layout_mode = 2
script = ExtResource("8_evd4p")
[node name="GGVBox - Buttons" type="Container" parent="GGVBox2 - Side Buttons/GGHBox"]
layout_mode = 2
size_flags_stretch_ratio = 0.33
script = ExtResource("7_u7o6r")
[node name="GGMarginLayout" type="Container" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
script = ExtResource("9_jlyx2")
left_margin = 0.0125
top_margin = 0.00416667
right_margin = 0.0
bottom_margin = 0.00416667
reference_node = NodePath("../../../../GGComponent - Size Ref")
[node name="GGButton - Layout" type="Button" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme_override_font_sizes/font_size = 17
text = "Layout"
script = ExtResource("13_a6138")
text_size_mode = 1
reference_node = NodePath("..")
reference_node_height = 36
reference_font_size = 14
is_configured = true
[node name="Control" type="Control" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout/GGButton - Layout"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("14_6xi7d")
[node name="GGMarginLayout2" type="Container" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
script = ExtResource("9_jlyx2")
left_margin = 0.0125
top_margin = 0.00416667
right_margin = 0.0
bottom_margin = 0.00416667
reference_node = NodePath("../../../../GGComponent - Size Ref")
[node name="GGButton - Text" type="Button" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout2" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_font_sizes/font_size = 17
text = "Text"
script = ExtResource("13_a6138")
text_size_mode = 1
reference_node = NodePath("..")
reference_node_height = 36
reference_font_size = 14
is_configured = true
[node name="Control2" type="Control" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout2/GGButton - Text"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("14_6xi7d")
[node name="GGMarginLayout3" type="Container" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
script = ExtResource("9_jlyx2")
left_margin = 0.0125
top_margin = 0.00416667
right_margin = 0.0
bottom_margin = 0.00416667
reference_node = NodePath("../../../../GGComponent - Size Ref")
[node name="GGButton - Images" type="Button" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout3" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_font_sizes/font_size = 17
text = "Images"
script = ExtResource("13_a6138")
text_size_mode = 1
reference_node = NodePath("..")
reference_node_height = 36
reference_font_size = 14
is_configured = true
[node name="Control3" type="Control" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout3/GGButton - Images"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("14_6xi7d")
[node name="GGMarginLayout4" type="Container" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
script = ExtResource("9_jlyx2")
left_margin = 0.0125
top_margin = 0.00416667
right_margin = 0.0
bottom_margin = 0.00416667
reference_node = NodePath("../../../../GGComponent - Size Ref")
[node name="GGButton - Misc" type="Button" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout4" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_font_sizes/font_size = 17
text = "Misc"
script = ExtResource("13_a6138")
text_size_mode = 1
reference_node = NodePath("../../GGMarginLayout3")
reference_node_height = 36
reference_font_size = 14
is_configured = true
[node name="Control4" type="Control" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout4/GGButton - Misc"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
script = ExtResource("14_6xi7d")
[node name="GGLabel" type="Label" parent="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons" node_paths=PackedStringArray("reference_node")]
layout_mode = 2
theme_override_colors/font_color = Color(1, 1, 1, 0.533333)
theme_override_font_sizes/font_size = 18
text = "[GGButtons]"
horizontal_alignment = 1
script = ExtResource("7_7fgfl")
text_size_mode = 1
reference_node = NodePath("..")
reference_node_height = 179
reference_font_size = 16
vertical_mode = 5
layout_size = Vector2(0, 26)
is_configured = true
[node name="GGFiller2" type="Container" parent="GGVBox2 - Side Buttons/GGHBox"]
layout_mode = 2
script = ExtResource("8_evd4p")
horizontal_mode = 3
layout_size = Vector2(0.025, 1)
[node name="GGFiller2" type="Container" parent="GGVBox2 - Side Buttons"]
layout_mode = 2
mouse_filter = 2
script = ExtResource("8_evd4p")
[connection signal="pressed" from="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout/GGButton - Layout" to="GGVBox - Text Area/GGMarginLayout - Outer/GGMarginLayout - Inner/GGHBox/GGRichTextLabel - Text Area" method="_on_gg_button__layout_pressed"]
[connection signal="pressed" from="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout2/GGButton - Text" to="GGVBox - Text Area/GGMarginLayout - Outer/GGMarginLayout - Inner/GGHBox/GGRichTextLabel - Text Area" method="_on_gg_button__text_pressed"]
[connection signal="pressed" from="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout3/GGButton - Images" to="GGVBox - Text Area/GGMarginLayout - Outer/GGMarginLayout - Inner/GGHBox/GGRichTextLabel - Text Area" method="_on_gg_button__images_pressed"]
[connection signal="pressed" from="GGVBox2 - Side Buttons/GGHBox/GGVBox - Buttons/GGMarginLayout4/GGButton - Misc" to="GGVBox - Text Area/GGMarginLayout - Outer/GGMarginLayout - Inner/GGHBox/GGRichTextLabel - Text Area" method="_on_gg_button__misc_pressed"]
================================================
FILE: DemoProject/LayoutConfig.gd
================================================
@tool
extends GGLayoutConfig
func _on_begin_layout( display_size:Vector2 ):
set_parameter( "half_width", int(display_size.x/2) )
set_parameter( "half_height", int(display_size.y/2) )
================================================
FILE: DemoProject/TextArea.gd
================================================
@tool
extends GGRichTextLabel
func _on_gg_button__layout_pressed():
text = "[b]GGComponent[/b] - GameGUI base node type. Useful as container, sizer, filler, spacer. Lays out children in a layered stack.\n" \
+ "[b]GGHBox[/b] - Lays out children in a horizontal row.\n" \
+ "[b]GGVBox[/b] - Lays out children in a vertical column.\n" \
+ "[b]GGInitialWindowSize[/b] - IF it is the root node of a scene, sets the window size to its own size on launch. Useful for testing independent UI component scenes at typical aspect ratios.\n" \
+ "[b]GGMarginLayout[/b] - Adds inside margins to the layout of its child content.\n" \
+ "[b]GGOverlay[/b] - Positions its child content arbitrarily within its layout area in a sprite-like manner.\n" \
+ "[b]GGLimitedSizeComponent[/b] - Applies minimum and/or maximum sizes to its child content.\n" \
+ "[b]GGFiller[/b] - A GGComponent with an icon and default name that indicates it's purpose is to fill up extra space."
func _on_gg_button__text_pressed():
text = "[b]GGLabel[/b] - A Label that can auto-scale its text size.\n" \
+ "[b]GGRichTextLabel[/b] - A RichTextLabel that can auto-scale its text size.\n" \
+ "[b]GGButton[/b] - A Button that can auto-scale its text size.\n"
func _on_gg_button__images_pressed():
text = "[b]GGTextureRect[/b] - A TextureRect that uses the GameGUI layout system and automatically configures itself with appropriate defaults.\n"
func _on_gg_button__misc_pressed():
text = "[b]GGLayoutConfig[/b] - Place this near the root of the scene, extend the script, make it a @tool, override [code]func _on_begin_layout(display_size:Vector2)[/code], and call [code]set_parameter(name,value)[/code] with various computed values related to the current display size. Those parameters can be automatically used by other GameGUI nodes by setting their sizing mode to [b]Parameter[/b] and supplying the desired parameter name.\n" \
+ "[b]GGParameterSetter[/b] - Sets specified subtree parameters to its own width and/or height, which can then be used to set the size of other components when they're in [b]Parameter[/b] sizing mode.\n\n" \
+ "Any Godot control node can be the child of a GameGUI component. They are automatically scaled to fit available width and height. Their size can be controlled by wrapping them in various GameGUI components.\n\n"
================================================
FILE: DemoProject/addons/GameGUI/GGButton.gd
================================================
@tool
class_name GGButton
extends Button
#-------------------------------------------------------------------------------
# GAMEGUI PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Text Size")
## Check to lock in the current font size and reference node height as reference
## values that will be used to scale the font size.
@export var text_size_mode:GGComponent.TextSizeMode:
set(value):
if text_size_mode == value: return
text_size_mode = value
if not get_parent(): return # resource loading is setting properties
match value:
GGComponent.TextSizeMode.DEFAULT:
reference_node_height = 0
reference_font_size = 0
GGComponent.TextSizeMode.SCALE:
if reference_node: reference_node_height = floor(reference_node.size.y)
reference_font_size = get_theme_font_size( "font_size" )
GGComponent.TextSizeMode.PARAMETER:
reference_node_height = 0
reference_font_size = 0
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
request_layout()
## A node that will be used as a height reference for scaling this node's text.
@export var reference_node:Control :
set(value):
if reference_node == value: return
reference_node = value
if reference_node and reference_node_height == 0:
reference_node_height = int(value.size.y)
request_layout()
## The height of the [Button] node that the [member reference_font_size] was designed for.
## This is used to scale the font based on the current height of the reference node.
@export var reference_node_height := 0 :
set(value):
if reference_node_height == value: return
reference_node_height = value
request_layout()
## The original size of the font.
@export var reference_font_size := 0 :
set(value):
if reference_font_size == value: return
reference_font_size = value
request_layout()
## The name of the parameter to use when [member text_size_mode] is [b]Parameter[/b].
@export var text_size_parameter:String = "" :
set(value):
if text_size_parameter == value: return
text_size_parameter = value
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Automatically set to indicate that default properties have been set
## for this node. Uncheck to reset those defaults.
@export var is_configured := false
# Internal editor use to detect font size changes and request an updated layout.
var _current_node_height := 0
var _current_font_size := 0
# GameGUI framework use
var is_width_resolved := false ## Internal GameGUI use.
var is_height_resolved := false ## Internal GameGUI use.
#-------------------------------------------------------------------------------
# GGLABEL METHODS
#-------------------------------------------------------------------------------
func _ready():
_configure()
func _process(_delta):
_configure()
_check_for_modified_font_size()
func _check_for_modified_font_size():
if not Engine.is_editor_hint(): return
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
var cur_size = get_theme_font_size( "font_size" )
if cur_size != _current_font_size:
_current_font_size = cur_size
request_layout()
GGComponent.TextSizeMode.SCALE:
if _current_font_size != reference_font_size:
_current_font_size = reference_font_size
request_layout()
if reference_node:
var h = int(reference_node.size.y)
if _current_node_height != h:
_current_node_height = h
request_layout()
GGComponent.TextSizeMode.PARAMETER:
pass
func _configure():
if not is_configured and size.y > 0:
is_configured = true
if text == "": text = "Button"
#-------------------------------------------------------------------------------
# GAMEGUI API
#-------------------------------------------------------------------------------
## Returns the specified parameter's value if it exists in a [GGComponent]
## parent or ancestor. If it doesn't exist, returns [code]0[/code] or a
## specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this [GGComponent] subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in a
## [GGComponent] parent or ancestor.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level [GGComponent] root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
# Called when this component is about to compute its size. Any size computations
# relative to reference nodes higher in the tree should be performed here.
func _on_resolve_size( available_size:Vector2 ):
if Engine.is_editor_hint():
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
# Save current font size theme override size to check for editor changes
_current_font_size = get_theme_font_size( "font_size" )
GGComponent.TextSizeMode.SCALE:
# Save current font reference size to check for editor changes
_current_font_size = reference_font_size
if reference_node: _current_node_height = int( reference_node.size.y )
match text_size_mode:
GGComponent.TextSizeMode.SCALE:
if reference_node and reference_node_height:
var cur_scale = floor(reference_node.size.y) / reference_node_height
# Override the size of the font to dynamically size it
var cur_size = reference_font_size * cur_scale
if cur_size:
add_theme_font_size_override( "font_size", cur_size )
GGComponent.TextSizeMode.PARAMETER:
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
var top = get_top_level_component()
if top: top.request_layout()
================================================
FILE: DemoProject/addons/GameGUI/GGComponent.gd
================================================
@tool
## General-purpose layout box and base class to other GameGUI layout components. Children are
## stacked in layers.
##
## Nodes that do not extend [GGComponent] can still be children of GameGUI nodes in one of two
## ways.[br][br]
## First, GameGUI adapter code can be added to an extended class. See the [GGTextureRect] source
## code for an example that can be copy-pasted into other extended classes with only small
## modifications required.[br][br]
## Second, any non-[GGComponent] Control node that does not contain GameGUI adapter code will be
## treated as a component with [member horizontal_mode] and [member vertical_mode] both set to
## [b]Expand To Fill[/b]. The size of the node can be further managed by making it a child of
## a [GGComponent] that has other sizing modes.[br][br]
class_name GGComponent
extends Container
#-------------------------------------------------------------------------------
# SIGNALS
#-------------------------------------------------------------------------------
## Signals the beginning of the layout process for a GGComponent subtree.
## This signal is only emitted by the top-level root of a GGComponent subtree.
signal begin_layout
## Signals the end of the layout process for a GGComponent subtree.
## This signal is only emitted by the top-level root of a GGComponent subtree.
signal end_layout
#-------------------------------------------------------------------------------
# ENUMS
#-------------------------------------------------------------------------------
## The possible horizontal and vertical sizing modes for a GameGUI component.
enum ScalingMode
{
EXPAND_TO_FILL, ## Fill all available space along this dimension.
ASPECT_FIT, ## Dynamically adjusts size to maintain aspect ratio [member layout_size].x:[member layout_size].y, just small enough to entirely fit available space.
ASPECT_FILL, ## Dynamically adjusts size to maintain aspect ratio [member layout_size].x:[member layout_size].y, just large enough to entirely fill available space.
PROPORTIONAL, ## The layout size represents a proportional fraction of 1) the available area or 2) the size of the [member reference_node] if defined.
SHRINK_TO_FIT, ## Make the size just large enough to contain all child nodes in their layout.
FIXED, ## Fixed pixel size along this dimension.
PARAMETER ## One of the subtree [member parameters] is used as the size.
}
## The text sizing mode for [GGLabel], [GGRichTextLabel], and [GGButton].
enum TextSizeMode
{
DEFAULT, ## Text size is whatever size you assign in the editor.
SCALE, ## Text scales with the size of a reference node.
PARAMETER ## Text size is set to the value of one of the defined [member parameters].
}
## The texture fill mode used by [method fill_texture].
enum FillMode
{
STRETCH, # Stretch or compress each patch to cover the available space.
TILE, # Repeatedly tile each patch at its original pixel size to cover the available space.
TILE_FIT # Tile each patche, stretching slightly as necessary to ensure a whole number of tiles fit in the available space.
}
#-------------------------------------------------------------------------------
# PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## An optional node to use as a size reference for [b]Proportional[/b] scaling
## mode. The reference node must be in a subtree higher in the scene tree than
## this node. Often the size reference is an invisible root-level square-aspect
## component; this allows same-size horizontal and vertical proportional spacers.
@export var reference_node:Control = null :
set(value):
if reference_node != value:
reference_node = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Parameter definitions for nodes that use scaling mode PARAMETER. Parameters are stored
## the root of a GGComponent subtree. Use [method get_parameter], [method has_parameter], and
## [method set_parameter] to access parameters from any subtree nodes.
@export var parameters := {} :
set(value):
if parameters == value: return
parameters = value
request_layout()
# A top-level GGComponent is one that has no GGComponent parent.
# It oversees the layout of its descendent nodes.
var _is_top_level := false
var _layout_stage := 0 # top-level component use. 0=layout finished, 1=layout requested, 2=performing layout
#-------------------------------------------------------------------------------
# EXTERNAL API
#-------------------------------------------------------------------------------
## Utility method that draws a texture with any combination of horizontal and vertical fill modes: Stretch, Tile, Tile Fit.
## Used primarily by [GGComponent].
func fill_texture( texture:Texture2D, dest_rect:Rect2, src_rect:Rect2, horizontal_fill_mode:FillMode=FillMode.STRETCH,
vertical_fill_mode:FillMode=FillMode.STRETCH, modulate:Color=Color(1,1,1,1) ):
if dest_rect.size.x <= 0 or dest_rect.size.y <= 0: return
if horizontal_fill_mode == FillMode.TILE and src_rect.size.x > dest_rect.size.x:
horizontal_fill_mode = FillMode.TILE_FIT
if vertical_fill_mode == FillMode.TILE and src_rect.size.y > dest_rect.size.y:
vertical_fill_mode = FillMode.TILE_FIT
match horizontal_fill_mode:
FillMode.TILE:
var tile_size = src_rect.size
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_w > 0:
if tile_size.x <= dest_w:
var _dest_rect = Rect2( dest_pos, Vector2(tile_size.x,dest_h) )
fill_texture( texture, _dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
var _src_rect = Rect2( src_rect.position, Vector2(dest_w,src_rect.size.y) )
fill_texture( texture, _dest_rect, _src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
return
dest_pos += Vector2( tile_size.x, 0 )
dest_w -= tile_size.x
return
FillMode.TILE_FIT:
var n = int( (dest_rect.size.x / src_rect.size.x) + 0.5 )
if n == 0:
fill_texture( texture, dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
else:
var tile_size = Vector2( dest_rect.size.x / n, src_rect.size.y )
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_w > 0:
if tile_size.x <= dest_w:
var _dest_rect = Rect2( dest_pos, Vector2(tile_size.x,dest_h) )
fill_texture( texture, _dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
fill_texture( texture, _dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
return
dest_pos += Vector2( tile_size.x, 0 )
dest_w -= tile_size.x
return
match vertical_fill_mode:
FillMode.TILE:
var tile_size = src_rect.size
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_h > 0:
if tile_size.y <= dest_h:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w, tile_size.y) )
fill_texture( texture, _dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
var _src_rect = Rect2( src_rect.position, Vector2(src_rect.size.x,dest_h) )
fill_texture( texture, _dest_rect, _src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
return
dest_pos += Vector2( 0, tile_size.y )
dest_h -= tile_size.y
return
FillMode.TILE_FIT:
var n = int( (dest_rect.size.y / src_rect.size.y) + 0.5 )
if n == 0:
fill_texture( texture, dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
else:
var tile_size = Vector2( src_rect.size.x, dest_rect.size.y / n )
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_h > 0:
if tile_size.y <= dest_h:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w, tile_size.y) )
fill_texture( texture, _dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
fill_texture( texture, _dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
return
dest_pos += Vector2( 0, tile_size.y )
dest_h -= tile_size.y
return
# Horizontal and vertical fill are both STRETCH
draw_texture_rect_region( texture, dest_rect, src_rect, modulate )
## Returns the specified parameter's value if it exists in the [member parameters]
## of this node or a [GGComponent] ancestor. If it doesn't exist, returns
## [code]0[/code] or a specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this GGComponent subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in the
## [member parameters] of this node or one of its ancestors.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
if _is_top_level:
if _layout_stage == 0:
_layout_stage = 1
queue_sort()
else:
var top = get_top_level_component()
if top: top.request_layout()
#-------------------------------------------------------------------------------
# KEY OVERRIDES
#-------------------------------------------------------------------------------
func _on_resolve_size( available_size:Vector2 ):
# Overrideable.
# Called just before this component's size is resolved.
# Override and adjust this component's size if desired.
pass
func _on_update_size():
# Overrideable.
# Called at the beginning of layout.
# Override and adjust this GGComponent's size if desired.
pass
func _perform_child_layout( available_bounds:Rect2 ):
for i in range(get_child_count()):
_perform_component_layout( get_child(i), available_bounds )
func _resolve_child_sizes( available_size:Vector2, limited:bool=false ):
for i in range(get_child_count()):
_resolve_child_size( get_child(i), available_size, limited )
func _resolve_shrink_to_fit_height( _available_size:Vector2 ):
# Override in extended classes.
size.y = _get_largest_child_size().y
func _resolve_shrink_to_fit_width( _available_size:Vector2 ):
# Override in extended classes.
size.x = _get_largest_child_size().x
func _with_margins( rect:Rect2 )->Rect2:
return rect
#-------------------------------------------------------------------------------
# INTERNAL GAMEGUI API
#-------------------------------------------------------------------------------
func _get_largest_child_size()->Vector2:
# 'x' and 'y' will possibly come from different children.
var max_w := 0.0
var max_h := 0.0
for i in range(get_child_count()):
var child = get_child(i)
if not (child is Control) or not child.visible: continue
if child is Control: # includes GGComponent
max_w = max( max_w, child.size.x )
max_h = max( max_h, child.size.y )
return Vector2(max_w,max_h)
func _get_sum_of_child_sizes()->Vector2:
var sum := Vector2(0,0)
for i in range(get_child_count()):
var child = get_child(i)
if not (child is Control) or not child.visible: continue
sum += child.size
return sum
func _perform_layout( available_bounds:Rect2 ):
_place_component( self, available_bounds )
var bounds = _with_margins( Rect2(Vector2(0,0),size) )
_perform_child_layout( bounds )
func _place_component( component:Control, available_bounds:Rect2 ):
component.position = _rect_position_within_parent_bounds( component, component.size, available_bounds )
func _rect_position_within_parent_bounds( component:Control, rect_size:Vector2, available_bounds:Rect2 )->Vector2:
var pos = available_bounds.position
if component is Control: # includes GGComponent
if component.size_flags_horizontal & (SizeFlags.SIZE_SHRINK_CENTER | SizeFlags.SIZE_FILL):
pos.x += floor( (available_bounds.size.x - rect_size.x) / 2 )
elif component.size_flags_horizontal & SizeFlags.SIZE_SHRINK_END:
pos.x += available_bounds.size.x - rect_size.x
if component.size_flags_vertical & (SizeFlags.SIZE_SHRINK_CENTER | SizeFlags.SIZE_FILL):
pos.y += floor( (available_bounds.size.y - rect_size.y) / 2 )
elif component.size_flags_vertical & SizeFlags.SIZE_SHRINK_END:
pos.y += available_bounds.size.y - rect_size.y
return pos
func _resolve_child_size( child:Node, available_size:Vector2, limited:bool=false ):
if not child.visible or not child is Control: return
if child is GGComponent:
child._resolve_size( available_size, limited )
else:
_resolve_component_size( child, available_size )
_resolve_shrink_to_fit_size( child, available_size )
func _resolve_component_size( component:Node, available_size:Vector2 )->Vector2:
var component_size := available_size
var is_gg = component is GGComponent
if is_gg or component.has_method("_on_resolve_size"):
component._on_resolve_size( available_size )
var has_mode = is_gg or component.has_method("request_layout")
var h_mode = component.horizontal_mode if has_mode else ScalingMode.EXPAND_TO_FILL
var v_mode = component.vertical_mode if has_mode else ScalingMode.EXPAND_TO_FILL
match h_mode:
ScalingMode.EXPAND_TO_FILL:
pass # use available width
ScalingMode.SHRINK_TO_FIT:
if not component is GGComponent: component_size.x = component.size.x
ScalingMode.ASPECT_FIT:
if v_mode == ScalingMode.ASPECT_FILL:
component_size.x = floor( (available_size.y / component.layout_size.y) * component.layout_size.x )
else:
var fit_x = floor( (available_size.y / component.layout_size.y) * component.layout_size.x )
if fit_x <= available_size.x: component_size.x = fit_x
ScalingMode.ASPECT_FILL:
if v_mode != ScalingMode.ASPECT_FIT:
var scale_x = (available_size.x / component.layout_size.x)
var scale_y = (available_size.y / component.layout_size.y)
component_size.x = floor( max(scale_x,scale_y) * component.layout_size.x )
ScalingMode.FIXED:
component_size.x = component.layout_size.x
ScalingMode.PARAMETER:
component_size.x = get_parameter( component.width_parameter, component.layout_size.x )
ScalingMode.PROPORTIONAL:
if reference_node:
component_size.x = int(component.layout_size.x * reference_node.size.x)
else:
component_size.x = int(component.layout_size.x * available_size.x)
match v_mode:
ScalingMode.EXPAND_TO_FILL:
pass # use available height
ScalingMode.SHRINK_TO_FIT:
if not component is GGComponent: component_size.y = component.size.y
ScalingMode.ASPECT_FIT:
if h_mode == ScalingMode.ASPECT_FILL:
component_size.y = floor( (available_size.x / component.layout_size.x) * component.layout_size.y )
else:
var fit_y = floor( (available_size.x / component.layout_size.x) * component.layout_size.y )
if fit_y <= available_size.y: component_size.y = fit_y
ScalingMode.ASPECT_FILL:
if h_mode != ScalingMode.ASPECT_FIT:
var scale_x = (available_size.x / component.layout_size.x)
var scale_y = (available_size.y / component.layout_size.y)
component_size.y = floor( max(scale_x,scale_y) * component.layout_size.y )
ScalingMode.FIXED:
component_size.y = component.layout_size.y
ScalingMode.PARAMETER:
component_size.y = get_parameter( component.height_parameter, component.layout_size.y )
ScalingMode.PROPORTIONAL:
if reference_node:
component_size.y = int(component.layout_size.y * reference_node.size.y)
else:
component_size.y = int(component.layout_size.y * available_size.y)
if not component is GGComponent or not component._is_top_level: component.size = component_size
return component_size
func _perform_component_layout( component:Node, available_bounds:Rect2 ):
if component is GGComponent:
component._perform_layout(available_bounds)
elif component is Control:
_place_component( component, available_bounds )
func _resolve_shrink_to_fit_size( component:Node, available_size:Vector2 )->Vector2:
if not (component is GGComponent or component.has_method("request_layout")): return available_size
var component_size := available_size
match component.horizontal_mode:
ScalingMode.SHRINK_TO_FIT:
if component is GGComponent:
_resolve_shrink_to_fit_width( available_size )
else:
component_size.x = component.size.x
match component.vertical_mode:
ScalingMode.SHRINK_TO_FIT:
if component is GGComponent:
_resolve_shrink_to_fit_height( available_size )
else:
component_size.y = component.size.y
return component_size
func _resolve_size( available_size:Vector2, limited:bool=false ):
# limited
# Don't recurse to children unless necessary (Shrink to Fit mode). limited==true indicates
# that several sizing options are being checked by GGHBox/GGVBox/etc., so we only need
# to figure out the size of this node.
available_size = _resolve_component_size( self, available_size )
var inner_size = _with_margins( Rect2(Vector2(0,0), available_size) ).size
if not limited or ScalingMode.SHRINK_TO_FIT in [horizontal_mode,vertical_mode]:
_resolve_child_sizes( inner_size, limited )
_resolve_shrink_to_fit_size( self, available_size )
func _update_layout():
if not _is_top_level or _layout_stage == 2: return
_layout_stage = 2
_update_safe_area()
begin_layout.emit()
_update_size()
_resolve_size( size )
_perform_layout( _with_margins(Rect2(Vector2(0,0),size)) )
end_layout.emit()
_layout_stage = 0
func _update_size():
_on_update_size()
for i in range(get_child_count()):
var child = get_child(i)
if child is GGComponent:
child._update_size()
elif child.has_method("_on_update_size"):
child._on_update_size()
#-------------------------------------------------------------------------------
# INTERNAL NODE API
#-------------------------------------------------------------------------------
func _disconnect( sig:Signal, callback:Callable ):
if sig.is_connected(callback):
sig.disconnect( callback )
func _enter_tree():
_is_top_level = not (get_parent() is GGComponent)
if _is_top_level:
resized.connect( request_layout )
sort_children.connect( _on_sort_children )
_update_safe_area()
child_entered_tree.connect( _on_child_entered_tree )
child_exiting_tree.connect( _on_child_exiting_tree )
child_order_changed.connect( request_layout )
request_layout()
func _exit_tree():
if _is_top_level:
_disconnect( resized, request_layout )
_disconnect( sort_children, _on_sort_children )
_disconnect( child_entered_tree, _on_child_entered_tree )
_disconnect( child_exiting_tree, _on_child_exiting_tree )
_disconnect( child_order_changed, request_layout )
request_layout()
func _on_child_entered_tree( child:Node ):
if child is Control: child.visibility_changed.connect( request_layout )
if Engine.is_editor_hint() and child is Control:
child.minimum_size_changed.connect( request_layout )
child.resized.connect( request_layout )
child.size_flags_changed.connect( request_layout )
request_layout()
func _on_child_exiting_tree( child:Node ):
if child is Control: _disconnect( child.visibility_changed, request_layout )
if Engine.is_editor_hint() and child is Control:
_disconnect( child.minimum_size_changed, request_layout )
_disconnect( child.resized, request_layout )
_disconnect( child.size_flags_changed, request_layout )
request_layout()
func _on_child_visibility_changed():
request_layout()
func _on_sort_children():
_update_layout()
func _update_safe_area():
var viewport = get_viewport()
if viewport:
var display_size = viewport.get_visible_rect().size
var safe_area = Rect2( Vector2(0,0), display_size )
match DisplayServer.window_get_mode():
DisplayServer.WindowMode.WINDOW_MODE_FULLSCREEN, \
DisplayServer.WindowMode.WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
safe_area = DisplayServer.get_display_safe_area()
set_parameter( "safe_area_left_margin", safe_area.position.x )
set_parameter( "safe_area_top_margin", safe_area.position.y )
set_parameter( "safe_area_right_margin", display_size.x - safe_area.end.x )
set_parameter( "safe_area_bottom_margin", display_size.y - safe_area.end.y )
================================================
FILE: DemoProject/addons/GameGUI/GGFiller.gd
================================================
@tool
## A GameGUI node that by default will expand to fill any extra space within a layout.
## There is no functional difference between [GGFiller] and [GGComponent], but the name [GGFiller]
## can make a node tree more readable.
class_name GGFiller
extends GGComponent
================================================
FILE: DemoProject/addons/GameGUI/GGHBox.gd
================================================
@tool
## A GameGUI layout that arranges its child elements in a horizontal row.
class_name GGHBox
extends GGComponent
enum HorizontalContentAlignment
{
LEFT, ## Left-align the content.
CENTER, ## Center the content.
RIGHT ## Right-align the content.
}
## Specify the horizontal alignment of the content as a whole.
@export var content_alignment := HorizontalContentAlignment.CENTER :
set(value):
content_alignment = value
request_layout()
var _min_widths:Array[int] = []
var _max_widths:Array[int] = []
func _resolve_child_sizes( available_size:Vector2, limited:bool=false ):
# Resolve for and collect min and max sizes
_max_widths.clear()
for i in range(get_child_count()):
var child = get_child(i)
if child is Control and child.visible:
_resolve_child_size( child, available_size, true )
_max_widths.push_back( int(child.size.x) )
else:
_max_widths.push_back( 0 )
_min_widths.clear()
for i in range(get_child_count()):
var child = get_child(i)
if child is Control and child.visible:
_resolve_child_size( child, Vector2(0,available_size.y), true )
_min_widths.push_back( int(child.size.x) )
else:
_min_widths.push_back( 0 )
var expand_count := 0
var total_stretch_ratio := 0.0
var fixed_width := 0
var min_width := 0
# Leaving other children at their minimum width, set aspect-fit, proportional,
# and shrink-to-fit width nodes to their maximum size.
var modes = [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.SHRINK_TO_FIT]
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
var has_mode = child is GGComponent or child.has_method("request_layout")
if has_mode and child.horizontal_mode in modes:
_resolve_child_size( child, available_size, limited )
var w = int(child.size.x)
min_width += w
fixed_width += w
_min_widths[i] = w
_max_widths[i] = w
else:
var w = _min_widths[i]
min_width += w
if _min_widths[i] == _max_widths[i]:
fixed_width += w
_resolve_child_size( child, child.size, limited ) # final resolve
else:
expand_count += 1
total_stretch_ratio += child.size_flags_stretch_ratio
if expand_count == 0 or total_stretch_ratio == 0.0 or min_width >= available_size.x: return
var excess_width = int(available_size.x - fixed_width)
var remaining_width = excess_width
# Find children with a min width larger than their portion. Let them keep their min width and adjust remaining.
var remaining_total_stretch_ratio = total_stretch_ratio
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
if _min_widths[i] == _max_widths[i]: continue
var w := 0
if expand_count == 1: w = remaining_width
else: w = int( excess_width * child.size_flags_stretch_ratio / total_stretch_ratio )
if w < _min_widths[i]:
w = _min_widths[i]
remaining_width -= w
expand_count -= 1
remaining_total_stretch_ratio -= child.size_flags_stretch_ratio
_min_widths[i] = _max_widths[i] # skip this node in the next pass
_resolve_child_size( child, child.size, limited ) # final resolve
excess_width = remaining_width
total_stretch_ratio = remaining_total_stretch_ratio
if expand_count == 0 or abs(total_stretch_ratio) < 0.0001: return
# Distribute remaining width next to children with a max width smaller than their portion.
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
if _min_widths[i] == _max_widths[i]: continue
var w := 0
if expand_count == 1: w = remaining_width
else: w = int( excess_width * child.size_flags_stretch_ratio / total_stretch_ratio )
if w > _max_widths[i]:
w = _max_widths[i]
_resolve_child_size( child, Vector2(w,available_size.y), limited )
remaining_width -= w
expand_count -= 1
remaining_total_stretch_ratio -= child.size_flags_stretch_ratio
_min_widths[i] = _max_widths[i] # skip this node in the next pass
excess_width = remaining_width
total_stretch_ratio = remaining_total_stretch_ratio
if expand_count == 0 or abs(total_stretch_ratio) < 0.0001: return
# If this GGHBox is shrink-to-fit width then we're done; don't add remaining space to
# the children.
if horizontal_mode == GGComponent.ScalingMode.SHRINK_TO_FIT:
return
# Distribute remaining width
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
if _min_widths[i] == _max_widths[i]: continue
var w := 0
if expand_count == 1: w = remaining_width
else: w = int( excess_width * child.size_flags_stretch_ratio / total_stretch_ratio )
_resolve_child_size( child, Vector2(w,available_size.y), limited )
remaining_width -= w
expand_count -= 1
func _resolve_shrink_to_fit_width( _available_size:Vector2 ):
size.x = _get_sum_of_child_sizes().x
func _resolve_shrink_to_fit_height( _available_size:Vector2 ):
size.y = _get_largest_child_size().y
func _perform_layout( available_bounds:Rect2 ):
_place_component( self, available_bounds )
var inner_bounds = _with_margins( Rect2(Vector2(0,0),size) )
var pos = inner_bounds.position
var sz = inner_bounds.size
var diff = sz.x - _get_sum_of_child_sizes().x
match content_alignment:
HorizontalContentAlignment.LEFT: pass
HorizontalContentAlignment.CENTER: pos.x += int(diff/2.0)
HorizontalContentAlignment.RIGHT: pos.x += diff
for i in range(get_child_count()):
var child = get_child(i)
if not (child is Control) or not child.visible: continue
if child is Control:
_perform_component_layout( child, Rect2(pos,Vector2(child.size.x,sz.y)) )
pos += Vector2( child.size.x, 0 )
================================================
FILE: DemoProject/addons/GameGUI/GGInitialWindowSize.gd
================================================
@tool
## A control that sets the initial window size to be its own size if it is the base
## node of the scene. Facilitates testing child scenes individually with a customized aspect
## ratio similar to what their bounds will be when placed in the parent scene.
class_name GGInitialWindowSize
extends GGComponent
## The desired initial size of the window. Only takes effect when this node is the base node
## of a scene at runtime.
@export var initial_window_size := Vector2(1280,720):
set(value):
if initial_window_size == value: return
initial_window_size = value
var viewport = get_viewport()
if not viewport: return # engine is setting the initial value of this property
if not Engine.is_editor_hint() or viewport != get_parent(): return
if value != size:
size = value
request_layout()
func _ready():
if get_parent() == get_viewport() and not Engine.is_editor_hint():
DisplayServer.window_set_size( initial_window_size )
var screen_size = Vector2(DisplayServer.screen_get_size())
var pos = Vector2i( (screen_size-initial_window_size)/2.0 )
DisplayServer.window_set_position( pos )
set_anchors_and_offsets_preset( LayoutPreset.PRESET_FULL_RECT )
else:
initial_window_size = Vector2i(size)
func _enter_tree():
super()
if get_parent() == get_viewport():
resized.connect( _on_resized )
func _exit_tree():
super()
if resized.is_connected( _on_resized ):
resized.disconnect( _on_resized )
func _on_resized():
if Engine.is_editor_hint(): initial_window_size = size
================================================
FILE: DemoProject/addons/GameGUI/GGLabel.gd
================================================
@tool
## A Label with expanded layout and scaling capabilities. Make this a child of an extended
## GGComponent, not a standard Control or Container.
class_name GGLabel
extends Label
#-------------------------------------------------------------------------------
# GAMEGUI PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Text Size")
## Check to lock in the current font size and reference node height as reference
## values that will be used to scale the font size.
@export var text_size_mode:GGComponent.TextSizeMode:
set(value):
if text_size_mode == value: return
text_size_mode = value
if not get_parent(): return # resource loading is setting properties
match value:
GGComponent.TextSizeMode.DEFAULT:
reference_node_height = 0
reference_font_size = 0
GGComponent.TextSizeMode.SCALE:
if reference_node: reference_node_height = int(reference_node.size.y)
reference_font_size = get_theme_font_size( "font_size" )
GGComponent.TextSizeMode.PARAMETER:
reference_node_height = 0
reference_font_size = 0
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
## A node that will be used as a height reference for scaling this node's text.
@export var reference_node:Control :
set(value):
if reference_node == value: return
reference_node = value
if reference_node and reference_node_height == 0:
reference_node_height = int(value.size.y)
request_layout()
## The height of the [Label] node that the [member reference_font_size] was designed for.
## This is used to scale the font based on the current height of the reference node.
## Setting [member text_size_mode] to [b]SCALE[/b] will automatically set this property's value.
@export var reference_node_height := 0 :
set(value):
if reference_node_height == value: return
reference_node_height = value
request_layout()
## The original size of the font. Setting [member text_size_mode] to [b]SCALE[/b] will
## automatically set this property's value.
@export var reference_font_size := 0 :
set(value):
if reference_font_size == value: return
reference_font_size = value
request_layout()
## The name of the parameter to use when [member text_size_mode] is [b]Parameter[/b].
@export var text_size_parameter:String = "" :
set(value):
if text_size_parameter == value: return
text_size_parameter = value
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Automatically set to indicate that default properties have been set
## for this node. Uncheck to reset those defaults.
@export var is_configured := false
# Internal editor use to detect font size changes and request an updated layout.
var _current_node_height := 0
var _current_font_size := 0
# GameGUI framework use
var is_width_resolved := false ## Internal GameGUI use.
var is_height_resolved := false ## Internal GameGUI use.
#-------------------------------------------------------------------------------
# GGLABEL METHODS
#-------------------------------------------------------------------------------
func _ready():
_configure()
func _process(_delta):
_configure()
_check_for_modified_font_size()
func _check_for_modified_font_size():
if not Engine.is_editor_hint(): return
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
var cur_size = get_theme_font_size( "font_size" )
if cur_size != _current_font_size:
_current_font_size = cur_size
request_layout()
GGComponent.TextSizeMode.SCALE:
if _current_font_size != reference_font_size:
_current_font_size = reference_font_size
request_layout()
if reference_node:
var h = int(reference_node.size.y)
if _current_node_height != h:
_current_node_height = h
request_layout()
GGComponent.TextSizeMode.PARAMETER:
pass
func _configure():
if not is_configured and size.y > 0:
is_configured = true
horizontal_mode = GGComponent.ScalingMode.EXPAND_TO_FILL
vertical_mode = GGComponent.ScalingMode.FIXED
if abs(layout_size.x) < 0.0001 and abs(layout_size.y) < 0.0001:
layout_size = Vector2( 0, get_line_count()*get_line_height() )
if text == "": text = "Label"
#-------------------------------------------------------------------------------
# GAMEGUI API
#-------------------------------------------------------------------------------
## Returns the specified parameter's value if it exists in a [GGComponent]
## parent or ancestor. If it doesn't exist, returns [code]0[/code] or a
## specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this [GGComponent] subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in a
## [GGComponent] parent or ancestor.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level [GGComponent] root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
# Called when this component is about to compute its size. Any size computations
# relative to reference nodes higher in the tree should be performed here.
func _on_resolve_size( available_size:Vector2 ):
if Engine.is_editor_hint():
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
# Save current font size theme override size to check for editor changes
_current_font_size = get_theme_font_size( "font_size" )
GGComponent.TextSizeMode.SCALE:
# Save current font reference size to check for editor changes
_current_font_size = reference_font_size
if reference_node: _current_node_height = int( reference_node.size.y )
match text_size_mode:
GGComponent.TextSizeMode.SCALE:
if reference_node and reference_node_height:
var cur_scale = floor(reference_node.size.y) / reference_node_height
# Override the size of the font to dynamically size it
var cur_size = reference_font_size * cur_scale
if cur_size:
add_theme_font_size_override( "font_size", cur_size )
GGComponent.TextSizeMode.PARAMETER:
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
layout_size = Vector2( 0, get_line_count()*get_line_height() )
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
var top = get_top_level_component()
if top: top.request_layout()
================================================
FILE: DemoProject/addons/GameGUI/GGLayoutConfig.gd
================================================
@tool
## Extend this node and override [method _on_begin_layout] to set GameGUI parameters
## prior to each layout via [method set_parameter]. Ensure the extended node
## begins with the [code]@tool[/code] annotation.
class_name GGLayoutConfig
extends Node2D
func _enter_tree():
var top = get_top_level_component()
if top:
top.begin_layout.connect( _dispatch_on_begin_layout )
func _exit_tree():
var top = get_top_level_component()
if top:
top._disconnect( top.begin_layout, _dispatch_on_begin_layout )
func _dispatch_on_begin_layout():
var viewport = get_viewport()
if not viewport: return
var display_size = viewport.get_visible_rect().size
var top = get_top_level_component()
if Engine.is_editor_hint() and top: display_size = top.size
_on_begin_layout( display_size )
## Layout for the [GGComponent] subtree this node is attached to is about to begin.
## Override this method and set parameters or other configuration needs.
func _on_begin_layout( display_size:Vector2 ):
pass
## Returns the specified parameter's value if it exists in the [member parameters]
## of the [GGComponent] subtree this node is attached to. If it doesn't exist, returns
## [code]0[/code] or a specified default result.
func get_parameter( parameter_name:String, default_result:Variant )->Variant:
var cur = get_parent()
while cur and not cur is GGComponent:
cur = cur.get_parent()
if cur: return cur.get_parameter( parameter_name, default_result )
else: return default_result
## Returns the root of the [GGComponent] subtree this node is attached to.
func get_top_level_component()->GGComponent:
var cur = get_parent()
while cur and not cur is GGComponent:
cur = cur.get_parent()
if cur: return cur.get_top_level_component()
else: return null
## Returns [code]true[/code] if the specified parameter exists in the
## [member parameters] of the [GGComponent] subtree this node is attached to.
func has_parameter( parameter_name:String )->bool:
var cur = get_parent()
while cur and not cur is GGComponent:
cur = cur.get_parent()
if cur: return cur.has_parameter( parameter_name )
else: return false
## Sets the named parameter's value in the top-level root of the [GGComponent]
## subtree this node is attached to.
func set_parameter( parameter_name:String, value:Variant ):
var cur = get_parent()
while cur and not cur is GGComponent:
cur = cur.get_parent()
if cur: cur.set_parameter( parameter_name, value )
================================================
FILE: DemoProject/addons/GameGUI/GGLimitedSizeComponent.gd
================================================
@tool
## A component that can limit its size to arbitrary minimum and/or maximum values.
class_name GGLimitedSizeComponent
extends GGComponent
enum LimitType
{
UNLIMITED, ## No size limit.
FIXED, ## A limited size in pixels.
PARAMETER ## A parameter defines the size limit in pixels.
}
@export_group("Minimum Size")
@export var min_width := LimitType.UNLIMITED :
set(value):
if min_width == value: return
min_width = value
request_layout()
@export var min_height := LimitType.UNLIMITED :
set(value):
if min_height == value: return
min_height = value
request_layout()
@export var min_size := Vector2(0,0) :
set(value):
if min_size == value: return
min_size = value
request_layout()
@export var min_width_parameter := "" :
set(value):
if min_width_parameter == value: return
min_width_parameter = value
request_layout()
@export var min_height_parameter := "" :
set(value):
if min_height_parameter == value: return
min_height_parameter = value
request_layout()
@export_group("Maximum Size")
@export var max_width := LimitType.UNLIMITED :
set(value):
if max_width == value: return
max_width = value
request_layout()
@export var max_height := LimitType.UNLIMITED :
set(value):
if max_height == value: return
max_height = value
request_layout()
@export var max_size := Vector2(0,0) :
set(value):
if max_size == value: return
max_size = value
request_layout()
@export var max_width_parameter := "" :
set(value):
if max_width_parameter == value: return
max_width_parameter = value
request_layout()
@export var max_height_parameter := "" :
set(value):
if max_height_parameter == value: return
max_height_parameter = value
request_layout()
func _effective_min_height()->int:
match min_height:
LimitType.FIXED:
return int(min_size.y)
LimitType.PARAMETER:
return get_parameter( min_height_parameter, 0 )
_:
return 0
func _effective_min_width()->int:
match min_width:
LimitType.FIXED:
return int(min_size.x)
LimitType.PARAMETER:
return get_parameter( min_width_parameter, 0 )
_:
return 0
func _effective_max_height()->int:
match max_height:
LimitType.FIXED:
return int(max_size.y)
LimitType.PARAMETER:
return get_parameter( max_height_parameter, 0 )
_:
return 0
func _effective_max_width()->int:
match max_width:
LimitType.FIXED:
return int(max_size.x)
LimitType.PARAMETER:
return get_parameter( max_width_parameter, 0 )
_:
return 0
func _resolve_size( available_size:Vector2, limited:bool=false ):
if min_width != LimitType.UNLIMITED:
available_size.x = max( available_size.x, _effective_min_width() )
if min_height != LimitType.UNLIMITED:
available_size.y = max( available_size.y, _effective_min_height() )
if max_width != LimitType.UNLIMITED:
available_size.x = min( available_size.x, _effective_max_width() )
if max_height != LimitType.UNLIMITED:
available_size.y = min( available_size.y, _effective_max_height() )
super( available_size, limited )
================================================
FILE: DemoProject/addons/GameGUI/GGMarginLayout.gd
================================================
@tool
## Lays out its children in a layered stack with pixel or proportional margins.
class_name GGMarginLayout
extends GGComponent
enum MarginType
{
PROPORTIONAL, ## Specify margins between 0.0 and 1.0, similar to anchors.
FIXED, ## Margins have fixed pixel sizes.
PARAMETER ## Margins use parameters as their pixel sizes.
}
@export_group("Margins")
## Specifies the type of margin that will surround the child content area.
@export var margin_type := MarginType.PROPORTIONAL :
set(value):
if margin_type == value: return
if reference_node:
var ref_size = reference_node.size
if value == MarginType.PROPORTIONAL:
if margin_type == MarginType.FIXED and ref_size.x and ref_size.y:
left_margin /= ref_size.x
top_margin /= ref_size.y
right_margin /= ref_size.x
bottom_margin /= ref_size.y
else:
left_margin = 0.0
top_margin = 0.0
right_margin = 0.0
bottom_margin = 0.0
elif value == MarginType.FIXED:
if margin_type == MarginType.PROPORTIONAL and ref_size.x and ref_size.y:
left_margin = int( left_margin * ref_size.x )
top_margin = int( top_margin * ref_size.y )
right_margin = int( right_margin * ref_size.x )
bottom_margin = int( bottom_margin * ref_size.y )
else:
left_margin = 0
top_margin = 0
right_margin = 0
bottom_margin = 0
else:
if value == MarginType.PROPORTIONAL:
if margin_type == MarginType.FIXED and size.x and size.y:
left_margin /= size.x
top_margin /= size.y
right_margin = 1.0 - (right_margin/size.x)
bottom_margin = 1.0 - (bottom_margin/size.y)
else:
left_margin = 0.0
top_margin = 0.0
right_margin = 1.0
bottom_margin = 1.0
elif value == MarginType.FIXED:
if margin_type == MarginType.PROPORTIONAL and size.x and size.y:
left_margin = int( left_margin * size.x )
top_margin = int( top_margin * size.y )
right_margin = int( (1.0 - right_margin) * size.x )
bottom_margin = int( (1.0 - bottom_margin) * size.y )
else:
left_margin = 0
top_margin = 0
right_margin = 0
bottom_margin = 0
margin_type = value
request_layout()
## [b]Proportional[/b] Margin Type, no [member reference_node][br] 0.0: no margin[br] 0.25: 25% margin, etc.[br]
## [b]Proportional[/b] Margin Type, [member reference_node] set[br] 0.0: no margin[br] 0.25: margin is 25% size of ref node, etc.[br]
## [b]Fixed[/b] Margin Type[br] 0: no margin[br] 25: 25 pixel margin, etc.[br]
@export_range(0.0,1.0,0.0001,"or_less","or_greater") var left_margin:float = 0.0 :
set(value):
if left_margin == value: return
left_margin = value
request_layout()
## [b]Proportional[/b] Margin Type, no [member reference_node][br] 0.0: no margin[br] 0.25: 25% margin, etc.[br]
## [b]Proportional[/b] Margin Type, [member reference_node] set[br] 0.0: no margin[br] 0.25: margin is 25% size of ref node, etc.[br]
## [b]Fixed[/b] Margin Type[br] 0: no margin[br] 25: 25 pixel margin, etc.[br]
@export_range(0.0,1.0,0.0001,"or_less","or_greater") var top_margin:float = 0.0 :
set(value):
if top_margin == value: return
top_margin = value
request_layout()
## [b]Proportional[/b] Margin Type, no [member reference_node][br] 0.0: no margin[br] 0.75: 25% margin, etc.[br]
## [b]Proportional[/b] Margin Type, [member reference_node] set[br] 0.0: no margin[br] 0.25: margin is 25% size of ref node, etc.[br]
## [b]Fixed[/b] Margin Type[br] 0: no margin[br] 25: 25 pixel margin, etc.[br]
@export_range(0.0,1.0,0.0001,"or_less","or_greater") var right_margin:float = 1.0 :
set(value):
if right_margin == value: return
right_margin = value
request_layout()
## [b]Proportional[/b] Margin Type, no [member reference_node][br] 0.0: no margin[br] 0.75: 25% margin, etc.[br]
## [b]Proportional[/b] Margin Type, [member reference_node] set[br] 0.0: no margin[br] 0.25: margin is 25% size of ref node, etc.[br]
## [b]Fixed[/b] Margin Type[br] 0: no margin[br] 25: 25 pixel margin, etc.[br]
@export_range(0.0,1.0,0.0001,"or_less","or_greater") var bottom_margin:float = 1.0 :
set(value):
if bottom_margin == value: return
bottom_margin = value
request_layout()
## [b]Parameter[/b] Margin Type[br] "": no left margin[br] "abc": left margin is [code]get_parameter("abc")[/code] pixels, etc.
@export var left_parameter:String = "" :
set(value):
if left_parameter == value: return
left_parameter = value
request_layout()
## [b]Parameter[/b] Margin Type[br] "": no top margin[br] "abc": top margin is [code]get_parameter("abc")[/code] pixels, etc.
@export var top_parameter:String = "" :
set(value):
if top_parameter == value: return
top_parameter = value
request_layout()
## [b]Parameter[/b] Margin Type[br] "": no right margin[br] "abc": right margin is [code]get_parameter("abc")[/code] pixels, etc.
@export var right_parameter:String = "" :
set(value):
if right_parameter == value: return
right_parameter = value
request_layout()
## [b]Parameter[/b] Margin Type[br] "": no bottom margin[br] "abc": bottom margin is [code]get_parameter("abc")[/code] pixels, etc.
@export var bottom_parameter:String = "" :
set(value):
if bottom_parameter == value: return
bottom_parameter = value
request_layout()
func _resolve_shrink_to_fit_height( available_size:Vector2 ):
super( available_size )
match margin_type:
MarginType.PROPORTIONAL:
if reference_node:
size.y += int( top_margin * reference_node.size.y )
size.y += int( bottom_margin * reference_node.size.y )
else:
size.y += int( available_size.y * top_margin )
size.y += int( available_size.y * bottom_margin )
MarginType.FIXED:
size.y += top_margin
size.y += bottom_margin
MarginType.PARAMETER:
size.y += get_parameter(top_parameter,0)
size.y += get_parameter(bottom_parameter,0)
func _resolve_shrink_to_fit_width( available_size:Vector2 ):
super( available_size )
match margin_type:
MarginType.PROPORTIONAL:
if reference_node:
size.x += int( left_margin * reference_node.size.x )
size.x += int( right_margin * reference_node.size.x )
else:
size.x += int( available_size.x * left_margin )
size.x += int( available_size.x * right_margin )
MarginType.FIXED:
size.x += left_margin
size.x += right_margin
MarginType.PARAMETER:
size.x += get_parameter(left_parameter,0)
size.x += get_parameter(right_parameter,0)
func _with_margins( rect:Rect2 )->Rect2:
match margin_type:
MarginType.PROPORTIONAL:
if reference_node:
var left = int( left_margin * reference_node.size.x )
var right = int( right_margin * reference_node.size.x )
var top = int( top_margin * reference_node.size.y )
var bottom = int( bottom_margin * reference_node.size.y )
var x = rect.position.x + left
var y = rect.position.y + top
var x2 = rect.position.x + (rect.size.x - right)
var y2 = rect.position.y + (rect.size.y - bottom)
var w = x2 - x
var h = y2 - y
if w < 0: w = 0
if h < 0: h = 0
return Rect2( x, y, w, h )
else:
var x = rect.position.x + floor( rect.size.x * left_margin )
var y = rect.position.y + floor( rect.size.y * top_margin )
var x2 = rect.position.x + floor( rect.size.x * right_margin )
var y2 = rect.position.y + floor( rect.size.y * bottom_margin )
var w = x2 - x
var h = y2 - y
if w < 0: w = 0
if h < 0: h = 0
return Rect2( x, y, w, h )
MarginType.FIXED:
var x = rect.position.x + left_margin
var y = rect.position.y + top_margin
var x2 = rect.position.x + (rect.size.x - right_margin)
var y2 = rect.position.y + (rect.size.y - bottom_margin)
var w = x2 - x
var h = y2 - y
if w < 0: w = 0
if h < 0: h = 0
return Rect2( x, y, w, h )
MarginType.PARAMETER:
var x = rect.position.x + get_parameter(left_parameter,0)
var y = rect.position.y + get_parameter(top_parameter,0)
var x2 = rect.position.x + (rect.size.x - get_parameter(right_parameter,0))
var y2 = rect.position.y + (rect.size.y - get_parameter(bottom_parameter,0))
var w = x2 - x
var h = y2 - y
if w < 0: w = 0
if h < 0: h = 0
return Rect2( x, y, w, h )
_: return rect
================================================
FILE: DemoProject/addons/GameGUI/GGNinePatchRect.gd
================================================
@tool
class_name GGNinePatchRect
extends GGComponent
@export var texture:Texture2D :
set(value):
texture = value
if not texture: return
_texture_region = Rect2( 0, 0, texture.get_width(), texture.get_height() )
_update_piece_rects()
@export var draw_center := true :
set(value):
draw_center = value
queue_redraw()
@export_group("Patch Margin")
@export var left := 0:
set(value):
left = clamp( value, 0, _texture_region.size.x )
_update_piece_rects()
@export var top := 0:
set(value):
top = clamp( value, 0, _texture_region.size.y )
_update_piece_rects()
@export var right := 0:
set(value):
right = clamp( value, 0, _texture_region.size.x )
_update_piece_rects()
@export var bottom := 0:
set(value):
bottom = clamp( value, 0, _texture_region.size.y )
_update_piece_rects()
@export_group("Fill Mode")
@export var horizontal_fill := FillMode.STRETCH :
set(value):
horizontal_fill = value
queue_redraw()
@export var vertical_fill := FillMode.STRETCH :
set(value):
vertical_fill = value
queue_redraw()
var _texture_region:Rect2
var _piece_rects:Array[Rect2] = []
func _draw():
if size.x == 0 or size.y == 0 or not texture: return
var _left = left
var _right = right
var _top = top
var _bottom = bottom
if left + right > size.x or top + bottom > size.y:
var scale_x = size.x / (_left + _right)
var scale_y = size.y / (_top + _bottom)
var scale = min( scale_x, scale_y )
_left = floor( _left * scale )
_right = ceil( _right * scale )
_top = floor( _top * scale )
_bottom = ceil( _bottom * scale )
var mid_w = max( size.x - (_left+_right), 0 )
var mid_h = max( size.y - (_top+_bottom), 0 )
var pos = position
if _top > 0:
if _left > 0: draw_texture_rect_region( texture, Rect2(pos,Vector2(_left,_top)), _piece_rects[0], modulate )
pos += Vector2( _left, 0 )
fill_texture( texture, Rect2(pos,Vector2(mid_w,_top)), _piece_rects[1], horizontal_fill, vertical_fill, modulate )
pos += Vector2( mid_w, 0 )
if _right > 0: draw_texture_rect_region( texture, Rect2(pos,Vector2(_right,_top)), _piece_rects[2], modulate )
pos = Vector2( position.x, pos.y + _top )
if mid_h > 0:
fill_texture( texture, Rect2(pos,Vector2(_left,mid_h)), _piece_rects[3], horizontal_fill, vertical_fill, modulate )
pos += Vector2( _left, 0 )
if draw_center and mid_w > 0: fill_texture( texture, Rect2(pos,Vector2(mid_w,mid_h)), _piece_rects[4], horizontal_fill, vertical_fill, modulate )
pos += Vector2( mid_w, 0 )
fill_texture( texture, Rect2(pos,Vector2(_right,mid_h)), _piece_rects[5], horizontal_fill, vertical_fill, modulate )
pos = Vector2( position.x, pos.y + mid_h )
if _bottom > 0:
if _left > 0: draw_texture_rect_region( texture, Rect2(pos,Vector2(_left,_bottom)), _piece_rects[6], modulate )
pos += Vector2( _left, 0 )
fill_texture( texture, Rect2(pos,Vector2(mid_w,_bottom)), _piece_rects[7], horizontal_fill, vertical_fill, modulate )
pos += Vector2( mid_w, 0 )
if _right > 0: draw_texture_rect_region( texture, Rect2(pos,Vector2(_right,_bottom)), _piece_rects[8], modulate )
func _update_piece_rects():
var x = _texture_region.position.x
var y = _texture_region.position.y
var w = _texture_region.size.x
var h = _texture_region.size.y
var mid_w = max( w - (left+right), 0 )
var mid_h = max( h - (top+bottom), 0 )
_piece_rects = []
_piece_rects.push_back( Rect2 ( x, y, left, top) ) # TL
_piece_rects.push_back( Rect2( x+left, y, mid_w, top) ) # T
_piece_rects.push_back( Rect2( x+(w-right), y, right, top) ) # TR
_piece_rects.push_back( Rect2( x, y+top, left, mid_h) ) # L
_piece_rects.push_back( Rect2( x+left, y+top, mid_w, mid_h) ) # M
_piece_rects.push_back( Rect2( x+(w-right), y+top, right, mid_h) ) # R
_piece_rects.push_back( Rect2( x, y+(h-bottom), left, bottom) ) # BL
_piece_rects.push_back( Rect2( x+left, y+(h-bottom), mid_w, bottom) ) # B
_piece_rects.push_back( Rect2( x+(w-right), y+(h-bottom), right, bottom) ) # BR
queue_redraw()
================================================
FILE: DemoProject/addons/GameGUI/GGOverlay.gd
================================================
@tool
## Positions its children at arbitrary coordinates within its own bounds, similiar to a sprite.
## Not intended for use with an actual Sprite2D; use GGTextureRect or other Control types as
## children. Typically used with a single child node.
class_name GGOverlay
extends GGComponent
enum PositioningMode
{
PROPORTIONAL, ## Specify child position as a fraction between 0.0 and 1.0.
FIXED, ## Child position is a fixed pixel offset.
PARAMETER ## Use a parameter as the child's relative pixel offset.
}
enum ScaleFactor
{
CONSTANT, ## Scale using a fixed scale factor.
PARAMETER ## Scale using a subtree parameter.
}
@export_group("Child Position and Scale")
## The child positioning mode.
@export var positioning_mode := PositioningMode.PROPORTIONAL :
set(value):
if positioning_mode == value: return
match value:
PositioningMode.PROPORTIONAL:
if positioning_mode == PositioningMode.FIXED:
child_x /= size.x
child_y /= size.y
else:
child_x = 0.5
child_y = 0.5
PositioningMode.FIXED:
if positioning_mode == PositioningMode.PROPORTIONAL:
child_x = int( child_x * size.x )
child_y = int( child_y * size.y )
else:
child_x = int(size.x / 2.0)
child_y = int(size.y / 2.0)
positioning_mode = value
request_layout()
## The child 'x' offset within this component. Use 0.0-1.0 for positioning mode [b]Proportional[/b] and integer values for [b]Fixed[/b].
@export_range(0.0,1.0,0.0001,"or_less","or_greater") var child_x:float = 0.5 :
set(value):
if child_x == value: return
child_x = value
request_layout()
## The child 'y' offset within this component. Use 0.0-1.0 for positioning mode [b]Proportional[/b] and integer values for [b]Fixed[/b].
@export_range(0.0,1.0,0.0001,"or_less","or_greater") var child_y:float = 0.5 :
set(value):
if child_y == value: return
child_y = value
request_layout()
## The parameter name to use for the child 'x' offset.
@export var child_x_parameter := "" :
set(value):
if child_x_parameter == value: return
child_x_parameter = value
request_layout()
## The parameter name to use for the child 'y' offset.
@export var child_y_parameter := "" :
set(value):
if child_y_parameter == value: return
child_y_parameter = value
request_layout()
## The horizontal scale mode.
@export var h_scale_factor := ScaleFactor.CONSTANT :
set(value):
if h_scale_factor == value: return
h_scale_factor = value
request_layout()
## The vertical scale mode.
@export var v_scale_factor := ScaleFactor.CONSTANT :
set(value):
if v_scale_factor == value: return
v_scale_factor = value
request_layout()
## The horizontal scale factor to use when [member h_scale_factor] is [b]Constant[/b].
@export_range(0.0,1.0,0.0001,"or_greater") var h_scale_constant:float = 1.0 :
set(value):
if h_scale_constant == value: return
h_scale_constant = value
request_layout()
## The vertical scale factor to use when [member v_scale_factor] is [b]Constant[/b].
@export_range(0.0,1.0,0.0001,"or_greater") var v_scale_constant:float = 1.0 :
set(value):
if v_scale_constant == value: return
v_scale_constant = value
request_layout()
## The horizontal scale factor to use when [member h_scale_factor] is [b]Parameter[/b].
@export var h_scale_parameter:String = "" :
set(value):
if h_scale_parameter == value: return
h_scale_parameter = value
request_layout()
## The vertical scale factor to use when [member v_scale_factor] is [b]Parameter[/b].
@export var v_scale_parameter:String = "" :
set(value):
if v_scale_parameter == value: return
v_scale_parameter = value
request_layout()
func _get_scale()->Vector2:
var sx := 0.0
var sy := 0.0
match h_scale_factor:
ScaleFactor.CONSTANT:
sx = h_scale_constant
ScaleFactor.PARAMETER:
sx = get_parameter( h_scale_parameter )
match v_scale_factor:
ScaleFactor.CONSTANT:
sy = v_scale_constant
ScaleFactor.PARAMETER:
sy = get_parameter( v_scale_parameter )
return Vector2(sx,sy)
func _perform_child_layout( available_bounds:Rect2 ):
for i in range(get_child_count()):
var child = get_child(i)
if not child is Control or not child.visible: continue
var x_pos := 0
var y_pos := 0
match positioning_mode:
PositioningMode.PROPORTIONAL:
x_pos = available_bounds.size.x * child_x
y_pos = available_bounds.size.y * child_y
PositioningMode.FIXED:
x_pos = child_x
y_pos = child_y
PositioningMode.PARAMETER:
x_pos = get_parameter( child_x_parameter, child_x )
y_pos = get_parameter( child_y_parameter, child_y )
# Adjust x_pos and y_pos for SIZE_SHRINK_X.
if child.size_flags_horizontal & (SizeFlags.SIZE_SHRINK_CENTER | SizeFlags.SIZE_FILL):
x_pos -= int(child.size.x / 2.0)
elif child.size_flags_horizontal & SizeFlags.SIZE_SHRINK_END:
x_pos -= int(child.size.x)
if child.size_flags_vertical & (SizeFlags.SIZE_SHRINK_CENTER | SizeFlags.SIZE_FILL):
y_pos -= int(child.size.y / 2.0)
elif child.size_flags_vertical & SizeFlags.SIZE_SHRINK_END:
y_pos -= int(child.size.y)
_perform_component_layout( child, Rect2(Vector2(x_pos,y_pos),child.size) )
func _resolve_child_sizes( available_size:Vector2, limited:bool=false ):
var scale = _get_scale()
for i in range(get_child_count()):
var child = get_child(i)
if not child is Control or not child.visible: continue
# Resolve once at full size to get the child's full size
_resolve_child_size( child, available_size, limited )
# Apply the scale factor to the child
_resolve_child_size( child, floor( child.size * scale ), limited )
================================================
FILE: DemoProject/addons/GameGUI/GGParameterSetter.gd
================================================
@tool
## A component that sizes normally and then sets subtree parameters to its own width and/or height.
class_name GGParameterSetter
extends GGComponent
@export_group("Parameter Names")
## Optional name of a parameter to save this node's width in.
@export var width_store := "" :
set(value):
if width_store == value: return
width_store = value
if value != "":
if value != _cur_width_parameter:
var top = get_top_level_component()
if top: top.parameters.erase( _cur_width_parameter )
_cur_width_parameter = value
set_parameter( width_store, size.x )
## Optional name of a parameter to save this node's height in.
@export var height_store := "" :
set(value):
if height_store == value: return
height_store = value
if value != "":
if value != _cur_height_parameter:
var top = get_top_level_component()
if top: top.parameters.erase( _cur_height_parameter )
_cur_height_parameter = value
set_parameter( height_store, size.y )
var _cur_width_parameter := ""
var _cur_height_parameter := ""
func _resolve_size( available_size:Vector2, limited:bool=false ):
super( available_size, limited )
if width_store != "": set_parameter( width_store, size.x )
if height_store != "": set_parameter( height_store, size.y )
================================================
FILE: DemoProject/addons/GameGUI/GGRichTextLabel.gd
================================================
@tool
class_name GGRichTextLabel
extends RichTextLabel
#-------------------------------------------------------------------------------
# GAMEGUI PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Text Size")
# Check to lock in the current font size and reference node height as reference
# values that will be used to scale the font size.
@export var text_size_mode:GGComponent.TextSizeMode:
set(value):
if text_size_mode == value: return
text_size_mode = value
if not get_parent(): return # resource loading is setting properties
match value:
GGComponent.TextSizeMode.DEFAULT:
reference_node_height = 0
for style_name in reference_font_sizes.keys():
reference_font_sizes[style_name] = 0
GGComponent.TextSizeMode.SCALE:
if reference_node: reference_node_height = floor(reference_node.size.y)
for style_name in reference_font_sizes.keys():
var style_size_name = style_name + "_font_size"
reference_font_sizes[style_name] = get_theme_font_size( style_size_name )
GGComponent.TextSizeMode.PARAMETER:
reference_node_height = 0
for style_name in reference_font_sizes.keys():
reference_font_sizes[style_name] = 0
for style_name in text_size_parameters.keys():
var var_name = text_size_parameters[style_name]
if not has_parameter(var_name): var_name = text_size_parameters["normal"]
if has_parameter(var_name):
var cur_size = int(get_parameter(var_name))
if cur_size:
var style_size_name = style_name + "_font_size"
add_theme_font_size_override( style_size_name, cur_size )
## A node that will be used as a height reference for scaling this node's text.
@export var reference_node:Control :
set(value):
if reference_node == value: return
reference_node = value
if reference_node and reference_node_height == 0:
reference_node_height = int(value.size.y)
request_layout()
## The height of the [RichTextLabel] node that the [member reference_font_size] was designed for.
## This is used to scale the font based on the current height of the reference node.
@export var reference_node_height := 0 :
set(value):
if reference_node_height == value: return
reference_node_height = value
request_layout()
## The original size of each font style.
@export var reference_font_sizes:Dictionary = {"normal":0,"bold":0,"italics":0,"bold_italics":0,"mono":0}
## The names of the parameters to use when [member text_size_mode] is [b]Parameter[/b].
## The parameter for style "normal" will be the default for any other style that does not specify a
## parameter.
@export var text_size_parameters:Dictionary = {"normal":"","bold":"","italics":"","bold_italics":"","mono":""} :
set(value):
if text_size_parameters == value: return
text_size_parameters = value
if text_size_mode == GGComponent.TextSizeMode.PARAMETER:
for style_name in text_size_parameters.keys():
var var_name = text_size_parameters[style_name]
if not has_parameter(var_name): var_name = text_size_parameters["normal"]
if has_parameter(var_name):
var cur_size = int(get_parameter(var_name))
if cur_size:
var style_size_name = style_name + "_font_size"
add_theme_font_size_override( style_size_name, cur_size )
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Automatically set to indicate that default properties have been set
## for this node. Uncheck to reset those defaults.
@export var is_configured := false
# Internal editor use to detect font size changes and request an updated layout.
var _current_node_height := 0
var _current_font_sizes:Dictionary = {"normal":0,"bold":0,"italics":0,"bold_italics":0,"mono":0}
# GameGUI framework use
var is_width_resolved := false ## Internal GameGUI use.
var is_height_resolved := false ## Internal GameGUI use.
#-------------------------------------------------------------------------------
# GGRICHTEXTLABEL METHODS
#-------------------------------------------------------------------------------
func _ready():
_configure()
func _process(_delta):
_configure()
_check_for_modified_font_size()
func _check_for_modified_font_size():
if not Engine.is_editor_hint(): return
var any_modified = false
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
for style_name in _current_font_sizes.keys():
var style_size_name = style_name + "_font_size"
var cur_size = get_theme_font_size( style_size_name )
if cur_size != _current_font_sizes[style_name]:
_current_font_sizes[style_name] = cur_size
any_modified = true
GGComponent.TextSizeMode.SCALE:
for style_name in _current_font_sizes.keys():
if _current_font_sizes[style_name] != reference_font_sizes[style_name]:
_current_font_sizes[style_name] = reference_font_sizes[style_name]
any_modified = true
if reference_node:
var h = int(reference_node.size.y)
if _current_node_height != h:
_current_node_height = h
any_modified = true
GGComponent.TextSizeMode.PARAMETER:
pass
if any_modified: request_layout()
func _configure():
if not is_configured and size.y > 0:
is_configured = true
bbcode_enabled = true
scroll_active = false
horizontal_mode = GGComponent.ScalingMode.EXPAND_TO_FILL
vertical_mode = GGComponent.ScalingMode.FIXED
if abs(layout_size.x) < 0.0001 and abs(layout_size.y) < 0.0001:
layout_size = Vector2( get_content_width(), get_content_height() )
if text == "": text = "[center]GGRichTextLabel"
#-------------------------------------------------------------------------------
# GAMEGUI API
#-------------------------------------------------------------------------------
## Returns the specified parameter's value if it exists in a [GGComponent]
## parent or ancestor. If it doesn't exist, returns [code]0[/code] or a
## specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this [GGComponent] subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in a
## [GGComponent] parent or ancestor.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level [GGComponent] root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
# Called when this component is about to compute its size. Any size computations
# relative to reference nodes higher in the tree should be performed here.
func _on_resolve_size( available_size:Vector2 ):
if Engine.is_editor_hint():
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
# Save current font size theme override size to check for editor changes
for style_name in _current_font_sizes.keys():
var style_size_name = style_name + "_font_size"
_current_font_sizes[style_name] = get_theme_font_size( style_size_name )
GGComponent.TextSizeMode.SCALE:
# Save current font reference size to check for editor changes
if reference_node: _current_node_height = int( reference_node.size.y )
for style_name in reference_font_sizes.keys():
_current_font_sizes[style_name] = reference_font_sizes[style_name]
match text_size_mode:
GGComponent.TextSizeMode.SCALE:
if reference_node and reference_node_height:
var cur_scale = floor(reference_node.size.y) / reference_node_height
# Override the size of each font
for style_name in reference_font_sizes.keys():
var cur_size = reference_font_sizes[style_name] * cur_scale
if cur_size:
var style_size_name = style_name + "_font_size"
add_theme_font_size_override( style_size_name, cur_size )
GGComponent.TextSizeMode.PARAMETER:
for style_name in text_size_parameters.keys():
var var_name = text_size_parameters[style_name]
if not has_parameter(var_name): var_name = text_size_parameters["normal"]
if has_parameter(var_name):
var cur_size = int(get_parameter(var_name))
if cur_size:
var style_size_name = style_name + "_font_size"
add_theme_font_size_override( style_size_name, cur_size )
layout_size = Vector2( get_content_width(), get_content_height() )
size = layout_size
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
var top = get_top_level_component()
if top: top.request_layout()
================================================
FILE: DemoProject/addons/GameGUI/GGTextureRect.gd
================================================
@tool
## Extends TextureRect and adapts it to work painlessly with the GameGUI layout system.
class_name GGTextureRect
extends TextureRect
#-------------------------------------------------------------------------------
# GAMEGUI PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## An optional node to use as a size reference for [b]Proportional[/b] scaling
## mode. The reference node must be in a subtree higher in the scene tree than
## this node. Often the size reference is an invisible root-level square-aspect
## component; this allows same-size horizontal and vertical proportional spacers.
@export var reference_node:Control = null :
set(value):
if reference_node != value:
reference_node = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Automatically set to indicate that GameGUI-related properties have been set
## for this node. Uncheck to automatically reconfigure those properties.
@export var is_configured := false
# GameGUI framework use
var is_width_resolved := false ## Internal GameGUI use.
var is_height_resolved := false ## Internal GameGUI use.
#-------------------------------------------------------------------------------
# CONFIGURATION METHODS
#-------------------------------------------------------------------------------
func _ready():
_configure()
func _process(_delta):
_configure()
func _configure():
if not is_configured and texture:
is_configured = true
horizontal_mode = GGComponent.ScalingMode.ASPECT_FIT
vertical_mode = GGComponent.ScalingMode.ASPECT_FIT
layout_size = texture.get_size() # TextureRect-specific
# Let the GG framework and wrapper handle the sizing (TextureRect-specific)
expand_mode = TextureRect.ExpandMode.EXPAND_IGNORE_SIZE
#-------------------------------------------------------------------------------
# GAMEGUI API METHODS
#-------------------------------------------------------------------------------
## Returns the specified parameter's value if it exists in a [GGComponent]
## parent or ancestor. If it doesn't exist, returns [code]0[/code] or a
## specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this [GGComponent] subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in a
## [GGComponent] parent or ancestor.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level [GGComponent] root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
# Called when this component is about to compute its size. Any size computations
# relative to reference nodes higher in the tree should be performed here.
func _on_resolve_size( available_size:Vector2 ):
pass
# Called at the beginning of GGLayout. Adjust 'horizontal_mode',
# 'vertical_mode', and/or 'layout_size'. Other nodes may not have their sizes set yet,
# so defer relative size computations to _on_resolve_size(available_size:Vector2).
func _on_update_size():
pass
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
var top = get_top_level_component()
if top: top.request_layout()
================================================
FILE: DemoProject/addons/GameGUI/GGVBox.gd
================================================
@tool
## A GameGUI layout that arranges its child elements in a vertical column.
class_name GGVBox
extends GGComponent
enum VerticalContentAlignment
{
TOP, ## Top-align the content.
CENTER, ## Center the content.
BOTTOM ## Bottom-align the content.
}
## Specify the vertical alignment of the content as a whole.
@export var content_alignment := VerticalContentAlignment.CENTER :
set(value):
content_alignment = value
request_layout()
var _min_heights:Array[int] = []
var _max_heights:Array[int] = []
func _resolve_child_sizes( available_size:Vector2, limited:bool=false ):
# Resolve for and collect min and max sizes
_max_heights.clear()
for i in range(get_child_count()):
var child = get_child(i)
if child is Control and child.visible:
_resolve_child_size( child, available_size, true )
_max_heights.push_back( int(child.size.y) )
else:
_max_heights.push_back( 0 )
_min_heights.clear()
for i in range(get_child_count()):
var child = get_child(i)
if child is Control and child.visible:
_resolve_child_size( child, Vector2(available_size.x,0), true )
_min_heights.push_back( int(child.size.y) )
else:
_min_heights.push_back( 0 )
var expand_count := 0
var total_stretch_ratio := 0.0
var fixed_height := 0
var min_height := 0
# Leaving other children at their minimum height, set aspect-fit, proportional,
# and shrink-to-fit height nodes to their maximum size.
var modes = [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.SHRINK_TO_FIT]
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
var has_mode = child is GGComponent or child.has_method("request_layout")
if has_mode and child.vertical_mode in modes:
_resolve_child_size( child, available_size, limited )
var h = int(child.size.y)
min_height += h
fixed_height += h
_min_heights[i] = h
_max_heights[i] = h
else:
var h = _min_heights[i]
min_height += h
if _min_heights[i] == _max_heights[i]:
fixed_height += h
_resolve_child_size( child, child.size, limited ) # final resolve
else:
expand_count += 1
total_stretch_ratio += child.size_flags_stretch_ratio
if expand_count == 0 or total_stretch_ratio == 0.0 or min_height >= available_size.y: return
var excess_height = int(available_size.y - fixed_height)
var remaining_height = excess_height
# Find children with a min height larger than their portion. Let them keep their min height and adjust remaining.
var remaining_total_stretch_ratio = total_stretch_ratio
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
if _min_heights[i] == _max_heights[i]: continue
var h := 0
if expand_count == 1: h = remaining_height
else: h = int( excess_height * child.size_flags_stretch_ratio / total_stretch_ratio )
if h < _min_heights[i]:
h = _min_heights[i]
remaining_height -= h
expand_count -= 1
remaining_total_stretch_ratio -= child.size_flags_stretch_ratio
_min_heights[i] = _max_heights[i] # skip this node in the next pass
_resolve_child_size( child, child.size, limited ) # final resolve
excess_height = remaining_height
total_stretch_ratio = remaining_total_stretch_ratio
if expand_count == 0 or abs(total_stretch_ratio) < 0.0001: return
# Distribute remaining height next to children with a max height smaller than their portion.
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
if _min_heights[i] == _max_heights[i]: continue
var h := 0
if expand_count == 1: h = remaining_height
else: h = int( excess_height * child.size_flags_stretch_ratio / total_stretch_ratio )
if h > _max_heights[i]:
h = _max_heights[i]
_resolve_child_size( child, Vector2(available_size.x,h), limited )
remaining_height -= h
expand_count -= 1
remaining_total_stretch_ratio -= child.size_flags_stretch_ratio
_min_heights[i] = _max_heights[i] # skip this node in the next pass
excess_height = remaining_height
total_stretch_ratio = remaining_total_stretch_ratio
if expand_count == 0 or abs(total_stretch_ratio) < 0.0001: return
# If this GGVBox is shrink-to-fit height then we're done; don't add remaining space to
# the children.
if vertical_mode == GGComponent.ScalingMode.SHRINK_TO_FIT:
return
# Distribute remaining height
for i in range(get_child_count()):
var child = get_child(i)
if not child.visible or not child is Control: continue
if _min_heights[i] == _max_heights[i]: continue
var h := 0
if expand_count == 1: h = remaining_height
else: h = int( excess_height * child.size_flags_stretch_ratio / total_stretch_ratio )
_resolve_child_size( child, Vector2(available_size.x,h), limited )
remaining_height -= h
expand_count -= 1
func _resolve_shrink_to_fit_height( _available_size:Vector2 ):
size.y = _get_sum_of_child_sizes().y
func _resolve_shrink_to_fit_width( _available_size:Vector2 ):
size.x = _get_largest_child_size().x
func _perform_layout( available_bounds:Rect2 ):
_place_component( self, available_bounds )
var inner_bounds = _with_margins( Rect2(Vector2(0,0),size) )
var pos = inner_bounds.position
var sz = inner_bounds.size
var diff = sz.y - _get_sum_of_child_sizes().y
match content_alignment:
VerticalContentAlignment.TOP: pass
VerticalContentAlignment.CENTER: pos.y += int(diff/2.0)
VerticalContentAlignment.BOTTOM: pos.y += diff
for i in range(get_child_count()):
var child = get_child(i)
if not (child is Control) or not child.visible: continue
if child is Control:
_perform_component_layout( child, Rect2(pos,Vector2(sz.x,child.size.y)) )
pos += Vector2( 0, child.size.y )
================================================
FILE: DemoProject/addons/GameGUI/plugin.cfg
================================================
[plugin]
name="GameGUI"
description="A collection of dynamic layout nodes that provide a full-featured alternative to Container nodes."
author="Brom Bresenham"
version="1.5"
script="plugin.gd"
================================================
FILE: DemoProject/addons/GameGUI/plugin.gd
================================================
@tool
extends EditorPlugin
func _enter_tree():
# Initialization of the plugin goes here.
add_custom_type( "GGButton", "Button", preload("GGButton.gd"), preload("Icons/GGButton.svg") )
add_custom_type( "GGComponent", "Container", preload("GGComponent.gd"), preload("Icons/GGComponent.svg") )
add_custom_type( "GGFiller", "GGComponent", preload("GGFiller.gd"), preload("Icons/GGFiller.svg") )
add_custom_type( "GGHBox", "GGComponent", preload("GGHBox.gd"), preload("Icons/GGHBox.svg") )
add_custom_type( "GGInitialWindowSize", "GGComponent", preload("GGInitialWindowSize.gd"), preload("Icons/GGInitialWindowSize.svg") )
add_custom_type( "GGLabel", "Label", preload("GGLabel.gd"), preload("Icons/GGLabel.svg") )
add_custom_type( "GGLayoutConfig", "Node2D", preload("GGLayoutConfig.gd"), preload("Icons/GGLayoutConfig.svg") )
add_custom_type( "GGLimitedSizeComponent", "GGComponent", preload("GGLimitedSizeComponent.gd"), preload("Icons/GGLimitedSizeComponent.svg") )
add_custom_type( "GGMarginLayout", "GGComponent", preload("GGMarginLayout.gd"), preload("Icons/GGMarginLayout.svg") )
add_custom_type( "GGNinePatchRect", "GGComponent", preload("GGNinePatchRect.gd"), preload("Icons/GGNinePatchRect.svg") )
add_custom_type( "GGParameterSetter", "GGComponent", preload("GGParameterSetter.gd"), preload("Icons/GGParameterSetter.svg") )
add_custom_type( "GGOverlay", "GGComponent", preload("GGOverlay.gd"), preload("Icons/GGOverlay.svg") )
add_custom_type( "GGRichTextLabel", "RichTextLabel", preload("GGRichTextLabel.gd"), preload("Icons/GGRichTextLabel.svg") )
add_custom_type( "GGTextureRect", "TextureRect", preload("GGTextureRect.gd"), preload("Icons/GGTextureRect.svg") )
add_custom_type( "GGVBox", "GGComponent", preload("GGVBox.gd"), preload("Icons/GGVBox.svg") )
func _exit_tree():
# Clean-up of the plugin goes here.
remove_custom_type( "GGButton" )
remove_custom_type( "GGComponent" )
remove_custom_type( "GGFiller" )
remove_custom_type( "GGHBox" )
remove_custom_type( "GGInitialWindowSize" )
remove_custom_type( "GGLabel" )
remove_custom_type( "GGLayoutConfig" )
remove_custom_type( "GGLimitedSizeComponent" )
remove_custom_type( "GGMarginLayout" )
remove_custom_type( "GGNinePatchRect" )
remove_custom_type( "GGParameterSetter" )
remove_custom_type( "GGOverlay" )
remove_custom_type( "GGRichTextLabel" )
remove_custom_type( "GGTextureRect" )
remove_custom_type( "GGVBox" )
================================================
FILE: DemoProject/project.godot
================================================
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="DemoProject"
config/features=PackedStringArray("4.2", "Mobile")
config/icon="res://icon.svg"
[editor_plugins]
enabled=PackedStringArray("res://addons/GameGUI/plugin.cfg")
[rendering]
renderer/rendering_method="mobile"
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Brom Bresenham
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Media/.gdignore
================================================
Godot will ignore this folder when installing the GameGUI addon.
================================================
FILE: README.md
================================================
# Godot-GameGUI
A Godot 4.x plug-in that implements a rich and robust dynamic layout and sizing system for building user interfaces.
About | Current Release
-----------|-----------------------
Version | 1.5
Date | October 18, 2023
Change Log | [Change Log](ChangeLog.md)
Platform | Godot 4.x (tested on 4.2-dev3)
License | [MIT License](LICENSE)
Author | Brom Bresenham

*DemoProject - included in this repo*

*Axor the Mighty - see how this was built in the [Week 6 Devlog](https://youtu.be/Bq8M416Lcz4?si=9NVwZfn5KlI3fstB)*
# About
GameGUI is a set of Godot Control nodes that provide alternative layout capabilities to Godot's built-in Container classes. With Godot's built-in containers it is easy to make fixed-layout user interfaces that scale with the screen resolution *or* dynamic (responsive) layouts with fixed-size controls (such as labels), but it can be difficult to make dynamic layouts with scaling controls and text, and that's where GameGUI fits in.
## Key Features
- GameGUI layout nodes can be intermingled with standard Control nodes.
- Layout of GameGUI subtrees is performed through recursive subdivision of available space via nested nodes.
- Each GameGUI node can be configured to use various sizing modes that determine how it uses the space available to it: Expand-to-Fill, Aspect-Fit, etc.
- Godot Control Container Sizing alignments (`Shrink Begin`, etc.) are respected.
- GGComponent is the base node that implements the core functionality of the GameGUI system.
- GGHBox and GGVbox are the layout workhorses. They are GameGUI variations of HBoxContainer and VBoxContainer that distribute available space to their children in an intuitive way.
- Minimum and maximum node constraints can be established with a GGLimitedSizeComponent node.
- Non-GameGUI Control nodes can be children of GameGUI nodes. They are stretched horizontally and vertically to fit the space their parent provides.
- Any control node can be adapted to have the same scaling options as a GGComponent node by extending them with "adapter code" that adds certain properties and methods, making the control node a "duck-typed" fit. See GGTextureRect for a general example of adapter code that can be copy-pasted with minimal changes, or see GGLabel for code that auto-scales text size as well.
- GGLabel, GGRichTextLabel, and GGButton are adapted versions of their namesakes that can scale their text size with the layout size.
- GameGUI subtrees can define key-value "parameters" that components use for their size and/or for certain other properties. This allows complex sizing logic to be executed via GDScript and the results stored in the subtree for use by the components.
- GGLayoutConfig provides a convenient way to set parameters by extending its script and overriding a single method.
- [Built-in parameters](#Built-In-Parameters) `safe_area_[left|top|right|bottom]_margin` are automatically set by GameGUI.
# Installation
## Installing From Godot AssetLib
1. Switch to the "AssetLib" tab in the Godot editor.
2. Search for "GameGUI".
3. Click and install the GameGUI addon.

## Installing From GitHub Release
1. Download the latest [release](https://github.com/brombres/Godot-GameGUI/releases).
2. Drag the `addons` folder into a Godot project.
3. Enable the plug-in under Project > Project Settings... > Plug-ins.

# Component Overview
## Core Layout Components
<table width=100%>
<tr>
<th>Icon</th>
<th>Node</th>
<th>Description</th>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGComponent.png"></td>
<td>GGComponent</td>
<td>GameGUI base node type. Useful as container, sizer, filler, spacer. Lays out children in a layered stack.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGHBox.png"></td>
<td>GGHBox</td>
<td>Lays out children in a horizontal row.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGVBox.png"></td>
<td>GGVBox</td>
<td>Lays out children in a vertical column.</td>
</tr>
</table>
## Additional Layout Components
<table width=100%>
<tr>
<td width=80px><img src="Media/Images/Icons/GGFiller.png"></td>
<td>GGFiller</td>
<td>A GGComponent with an icon and default name that indicates its purpose is to fill up extra space.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGInitialWindowSize.png"></td>
<td>GGInitialWindowSize</td>
<td>If it is the root node of a scene, sets the window size to its own size on launch. Useful for testing independent UI component scenes at typical aspect ratios. If it is not the root scene node the window size is unaffected.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGLimitedSizeComponent.png"></td>
<td>GGLimitedSizeComponent</td>
<td>Applies minimum and/or maximum sizes to its child content.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGMarginLayout.png"></td>
<td>GGMarginLayout</td>
<td>Adds inside margins to the layout of its child content.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGOverlay.png"></td>
<td>GGOverlay</td>
<td>Positions its child content arbitrarily within its layout area in a sprite-like manner.</td>
</tr>
</table>
## Scaling Text Components
<table width=100%>
<tr>
<td width=80px><img src="Media/Images/Icons/GGButton.png"></td>
<td>GGButton</td>
<td>A Button that can auto-scale its text size.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGLabel.png"></td>
<td>GGLabel</td>
<td>A Label that can auto-scale its text size.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGRichTextLabel.png"></td>
<td>GGRichTextLabel</td>
<td>A RichTextLabel that can auto-scale its text size.</td>
</tr>
</table>
## Image Components
<table width=100%>
<tr>
<td width=80px><img src="Media/Images/Icons/GGTextureRect.png"></td>
<td>GGTextureRect</td>
<td>A TextureRect that uses the GameGUI layout system and automatically configures itself with appropriate defaults.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGNinePatchRect.png"></td>
<td>GGNinePatchRect</td>
<td>Replicates NinePatchRect functionality and makes the following improvement: when the bounds of a GGNinePatchRect are smaller than its corners, the corners are proportionally shrunk to fit the available bounds.</td>
</tr>
</table>
## Parameter Components
<table width=100%>
<tr>
<td width=80px><img src="Media/Images/Icons/GGLayoutConfig.png"></td>
<td>GGLayoutConfig</td>
<td>Place this near the root of the scene, extend the script, make it a @tool, override <code>func _on_begin_layout(display_size:Vector2)</code>, and call <code>set_parameter(name,value)</code> with various computed values related to the current display size. Those parameters can be automatically used by other GameGUI nodes by setting their sizing mode to <i>Parameter</i> and supplying the desired parameter name.</td>
</tr>
<tr>
<td width=80px><img src="Media/Images/Icons/GGParameterSetter.png"></td>
<td>GGParameterSetter</td>
<td>Sets specified subtree parameters to its own width and/or height, which can then be used to set the size of other components when they're in <i>Parameter</i> sizing mode.</td>
</tr>
</table>
# Scaling Modes
Each GameGUI component supports the following seven scaling modes. Horizontal and vertical scaling modes are set separately.

Mode | Description
-----|--------------
**Expand-to-Fill** | The component stretches or compresses to fill the available area.<br><br>
**Aspect-Fit** | The component maintains the specified aspect ratio and is sized as large as possible while still fitting in the available area.<br><br><br><br>The <b>Layout Size</b> property should be set to the desired aspect ratio. Note that 1680x840 has the same effect as 168x84, etc.<br><br>
**Aspect-Fill** | The component maintains the specified aspect ratio and is sized as small as possible while still completely filling the available area.<br><br><br><br>Note that Container Sizing options can be used to pin the content to a side or a corner.<br><br>
**Proportional** | Proportional mode can be used in one of two ways:<br><br>1. In the standard mode, the node size or other applicable property (such as GGMarginLayout margins) becomes a fraction of its available layout area, from 0.0 to 1.0.<br><br><br>2. Alternatively, a component's `reference node` property can be set and the proportional value is now relative to the size of the reference node. The reference node should be in a higher subtree (closer to the root) than the node referencing it to ensure that the reference node size is established first. In the example below, a square-aspect component is used as the reference node for a GGMarginLayout so that the gap around the image has the same thickness on all sides.<br>
**Shrink-to-Fit** | The component is sized as small as possible to enclose all of its children. For example, here is a GGVBox with fixed-size children set to **Shrink-to-Fit**.<br><br>
**Fixed** | The component uses **Layout Size** as a fixed pixel size.<br><br>
**Parameter** | The component sets its pixel size to the subtree parameters named by the properties **Width Parameter** and/or **Height Parameter**. Create a `GGLayoutConfig` config as the first child of a GameGUI subtree, extend its script, add the `@tool` annotation, and override `func _on_begin_layout(display_size:Vector2)` to update parameters (via `set_parameter(name:String,value:Variant)`, `get_parameter(name:String)->Variant`, and/or `has_parameter(name:String)->bool`) whenever the layout is about to be updated.<br><br><br><br>Parameter values can be added, inspected, and removed in the editor by examining the **Parameters** property of a GameGUI subtree root.<br><br>
## Aspect Mode Combinations
Horizontal Mode | Vertical Mode | Effect
----------------|---------------|-------
Aspect-Fit | Aspect-Fit | Component maintains the specified aspect ratio and is sized as large as possible while still fitting in the available area.
Aspect-Fill | Aspect-Fill | Component maintains the specified aspect ratio and is sized as small as possible while still completely filling the available area.
Aspect-Fill | Aspect-Fit | Component occupies all available width while maintaining the specified aspect ratio.
Aspect-Fit | Aspect-Fill | Component occupies all available height while maintaining the specified aspect ratio.
## Built-In Parameters
GameGUI automatically sets and maintains a small set of built-in parameters.
Any component extending GGComponent can set its width and/or height to a parameter value by selecting the "Fixed" Horizontal and/or Vertical Mode and supplying the appropriate parameter name in the "Width Parameter" and/or "Height Parameter" field.
In scripts extending a GameGUI component these parameters can be retrieved with `get_parameter("parameter_name",default_value)`.
### Safe Area Margin Parameters
Each margin parameter contains the number of pixels at a given edge of the display that cannot be safely drawn to due to a notch or rounded corners. For example, on iPhone 12 Pro, `safe_area_top_margin` will be set to `132` and `safe_area_bottom_margin` will be set to `102`.
Parameter Name | Description
--------------------------|--------------
`safe_area_left_margin` | The left pixel margin outside the safe area.
`safe_area_top_margin` | The top pixel margin outside the safe area.
`safe_area_right_margin` | The right pixel margin outside the safe area.
`safe_area_bottom_margin` | The bottom pixel margin outside the safe area.
Here is a simple GameGUI setup to include safe area margins in an app. A GGVbox contains top and bottom GGFiller components, their heights set to `Parameter` `safe_area_top_margin` and `Parameter` `safe_area_bottom_margin`, respectively. These parameters will be zero during desktop testing in windowed mode and non-zero on any modern iPhone, for example.

# Component Details
## Core Layout Components
### GGComponent

GameGUI base node type. Useful as container, sizer, filler, spacer. Lays out children in a layered stack.
A common use case is to create multiple UI layers in a root GGComponent (or GGInitialWindowSize, etc.).
The first child might be an aspect-fill GGTextureRect background image, the second child a GGHBox or
GGVBox for the main layout, and possibly finishing with a GGOverlay for a floating info panel or similar.
The following properties are available in all GameGUI nodes except for GGLayoutConfig.
#### Properties

<table>
<tr>
<th>Property</th><th>Description</th>
</tr>
<tr>
<th>Horizontal Mode</th>
<td>Set to one of the <a href="#Scaling-Modes">Scaling Modes</a>.</td>
</tr>
<tr>
<th>Vertical Mode</th>
<td>Set to one of the <a href="#Scaling-Modes">Scaling Modes</a>. Can be set to a different mode than <b>Horizontal Mode</b>.</td>
</tr>
<tr>
<th>Layout Size</th>
<td>
<ul>
<li>Treated as an aspect ratio for scaling modes <b>Aspect-Fit</b> and <b>Aspect-Fill</b>.
<li>Treated as a fractional value between 0.0 and 1.0 for scaling mode <b>Proportional</b>. Note that Godot allows
values to be entered as e.g. <code>32.0/1024.0</code> for convenience.
<li>Treated as a pixel value for scaling mode <b>Fixed</b>.
<li>Treated as a default pixel value for scaling mode <b>Parameter</b>.
For example, if <b>Width Parameter</b> is undefined, <code>x</code> will be used as a pixel width.
</ul>
</td>
</tr>
<tr>
<th>Reference Node</th>
<td>
When a scaling mode is set to <b>Proportional</b>, by default the <b>Layout Size</b> is taken to be a fraction
of the layout size available to this node - 1.0 for full size, 0.5 for half size, and so on. If <b>Reference Node</b>
is set, <b>Layout Size</b> is a fraction of the reference node's size instead. The reference node should be
in a higher subtree (closer to the root) than the node referencing it.
</td>
</tr>
<tr>
<th>Width Parameter</th>
<td>
When <b>Horizontal Mode</b> is set to <b>Parameter</b>, <b>Width Parameter</b> is the name of the subtree
parameter to use as the pixel width for this node. If the name or the parameter is undefined, the <code>x</code>
component of <b>Layout Size</b> is used as the pixel width.
</td>
</tr>
<tr>
<th>Height Parameter</th>
<td>
when <b>Vertical Mode</b> is set to <b>Parameter</b>, <b>Height Parameter</b> is the name of the subtree
parameter to use as the pixel height for this node. If the name or the parameter is undefined, the <code>y</code>
component of <b>Layout Size</b> is used as the pixel height.
</td>
</tr>
<tr>
<th>Parameters</th>
<td>
If this node is the root of a GameGUI subtree (meaning its parent does not extend <code>GGComponent</code>),
any parameters defined using <code>set_parameter(name:String,value:Variant)</code> will be stored here.
Parameters can be added, inspected, or modified in the Editor as well.
</td>
</tr>
<tr>
<th>Clip Contents</th>
<td>
This inherited property is useful for clipping the overflow of child components that are larger than their parent.
</td>
</tr>
<tr>
<th>Custom Minimum Size [IGNORED]</th>
<td>
This inherited property is <b>ignored</b> by GameGUI. Instead wrap components in a GGLimitedSizeComponent with more flexible
minimum size options.
</td>
</tr>
<tr>
<th>Container Sizing: Horizontal and Vertical</th>
<td>
GGComponent respects the inherited <b>Horizontal and Vertical Container Sizing</b> properties for aligning child content that is smaller than the parent.
Note that <b>Fill</b> is treated as <b>Center</b> and <b>Expand</b> is ignored.
</td>
</tr>
<tr>
<th>Container Sizing: Stretch Ratio</th>
<td>
GGComponent respects the inherited <b>Stretch Ratio</b> property for setting the proportional expansion weights
of <b>Expand-to-Fill</b> components.
</td>
</tr>
</table>
### GGHBox

Lays out children in a horizontal row. Here is the general algorithm:
1. Fixed-width, aspect-width, and GGLimitedSizeComponent minimum-width children are given their fixed or minimum width, with aspect width being calculated using the height of the GGHBox.
2. Remaining width is distributed among remaining expand-to-fill-width children according to their stretch ratio weights.
### GGVBox

Lays out children in a vertical row. Here is the general algorithm:
1. Fixed-height, aspect-height, and GGLimitedSizeComponent minimum-height children are given their fixed or minimum height, with aspect height being calculated using the width of the GGVBox.
2. Remaining height is distributed among remaining expand-to-fill-height children according to their stretch ratio weights.
## Additional Layout Components
### GGFiller

A GGComponent with an icon and default name that indicates its purpose is to fill up extra space. There is no technical difference between a GGFiller and a GGComponent.
### GGInitialWindowSize

IF it is the root node of a scene, sets the window size to its own size on launch. Useful for testing independent UI component scenes at typical aspect ratios. If it is not the root scene node the window size is unaffected.
#### Properties

Property|Description
--------|-----------
**Initial Window Size** | Arbitrary pixel dimensions for the initial window size. This can be set by editing the numbers or by dragging the component's bounding box handles in the 2D view.
### GGLimitedSizeComponent

Applies minimum and/or maximum sizes to its child content.
#### Properties

Property|Description
--------|-----------
**Min Width** | Selects the minimum width mode: Unlimited, Fixed, or Parameter.
**Min Height** | Selects the minimum height mode: Unlimited, Fixed, or Parameter.
**Min Size** | The minimum size pixel values in Fixed mode and the default pixel sizes in Parameter mode if the parameter is undefined.
**Min Width Parameter** | The name of a subtree parameter to use as the minimum pixel width.
**Min Height Parameter** | The name of a subtree parameter to use as the minimum pixel height.
**Max Width** | Selects the maximum width mode: Unlimited, Fixed, or Parameter.
**Max Height** | Selects the maximum height mode: Unlimited, Fixed, or Parameter.
**Max Size** | The maximum size pixel values in Fixed mode and the default pixel sizes in Parameter mode if the parameter is undefined.
**Max Width Parameter** | The name of a subtree parameter to use as the maximum pixel width.
**Max Height Parameter** | The name of a subtree parameter to use as the maximum pixel height.
### GGMarginLayout

Adds inside margins to the layout of its child content.
#### Properties

##### Margin Type: Proportional (No Reference Node)
Property|Description
--------|-----------
**Left Margin<br>Top Margin** | The top and left margins as a proportion of the available area, `0.0`-`1.0`.
**Right Margin<br>Bottom Margin** | The right and bottom margins as a proportion of the available area, `0.0`-`1.0`. Note that `1.0` means "no margin" and `0.9` means a 10% margin because it begins 90% of the way into the available area.
##### Margin Type: Proportional (With Reference Node)
Property|Description
--------|-----------
**Left Margin<br>Top Margin** | The top and left margins as a proportion of the reference node size, `0.0`-`1.0`.
**Right Margin<br>Bottom Margin** | The right and bottom margins as a proportion of the reference node size, 0.0-1.0. Note that `0.0` means "no margin" and `0.1` means a margin that is 10% of the size of the reference node.
##### Margin Type: Fixed
Property|Description
--------|-----------
**Left Margin<br>Top Margin<br>Right Margin<br>Bottom Margin** | The pixel sizes of the four margins. `0` means "no margin".
##### Margin Type: Parameter
Property|Description
--------|-----------
**Left Parameter<br>Top Parameter<br>Right Parameter<br>Bottom Parameter** | The names of subtree parameters that define the pixel sizes of the four margins. If Left Parameter is undefined then Left Margin is used as the pixel size, and so on.
### GGOverlay

Positions its child content arbitrarily within its layout area in a sprite-like manner.

#### Properties
Property|Description
--------|-----------
**Positioning Mode** | Selects the Positioning Mode: Proportional, Fixed, or Parameter.
**Child X** | The horizontal child offset: `0.0`-`1.0` in Proportional mode, a pixel offset in Fixed mode, and a default pixel offset for an undefined parameter in Parameter mode.
**Child Y** | The vertical child offset: `0.0`-`1.0` in Proportional mode, a pixel offset in Fixed mode, and a default pixel offset for an undefined parameter in Parameter mode.
**Child X Parameter** | The name of the subtree parameter for the horizontal child offset in Parameter mode.
**Child Y Parameter** | The name of the subtree parameter for the vertical child offset in Parameter mode.
**H Scale Factor** | Selects the horizontal child layout area scaling mode: Constant or Parameter.
**V Scale Factor** | Selects the vertical child layout area scaling mode: Constant or Parameter.
**H Scale Constant** | The horizontal child layout area scaling factor in Constant mode and the default scaling factor for an undefined parameter in Parameter mode.
**V Scale Constant** | The vertical child layout area scaling factor in Constant mode and the default scaling factor for an undefined parameter in Parameter mode.
**H Scale Parameter** | The name of the subtree parameter for the horizontal child layout area scaling factor.
**V Scale Parameter** | The name of the subtree parameter for the vertical child layout area scaling factor.
## Scaling Text Components
### GGButton

A Button that can auto-scale its text size.
#### Text Size Modes

Mode|Description
--------|-----------
**Default** | The size of the button text is fixed and is set via standard Button control properties. Extended GGButton properties have no effect.
**Scale** | Set the button text size as desired for the current layout, assign a size Reference Node (which can be the GGButton itself), and then switch Text Size Mode to Scale. The GGButton's text size will then automatically scale with the size of the reference node.
**Parameter** | The size of the button text is determined by the subtree parameter named by the Text Size Parameter property.
### GGLabel

A Label that can auto-scale its text size.
#### Text Size Modes

Mode|Description
--------|-----------
**Default** | The size of the label text is fixed and is set via standard Label control properties. Extended GGLabel properties have no effect.
**Scale** | Set the label text size as desired for the current layout, assign a size Reference Node (typically the GGLabel's parent, as the GGLabel's size is dependent on the text size), and then switch Text Size Mode to Scale. The GGLabel's text size will then automatically scale with the size of the reference node.
**Parameter** | The size of the label text is determined by the subtree parameter named by the Text Size Parameter property.
### GGRichTextLabel

A RichTextLabel that can auto-scale its text size.
#### Text Size Modes

Mode|Description
--------|-----------
**Default** | The size of the label text is fixed and is set via standard Label control properties. Extended GGRichTextLabel properties have no effect.
**Scale** | Set the label text size as desired for the current layout, assign a size Reference Node (typically the GGRichTextLabel's parent, as the GGRichTextLabel's size is dependent on the text size), and then switch Text Size Mode to Scale. The GGRichTextLabel's text size will then automatically scale with the size of the reference node.
**Parameter** | The size of the label text is determined by the subtree parameter named by the Text Size Parameter property.
## Image Components
### GGTextureRect

GGTextureRect is a TextureRect that supports GameGUI scaling modes and auto-configures appropriate properties.
Once a texture is assigned to the Texture property, the GGTextureRect auto-configures itself by setting the following properties:
- Horizontal and Vertical Scaling Modes are set to Aspect Fit.
- Layout Size is set to the original pixel dimensions of the texture.
- Expand Mode is set to Ignore Size.
- Is Configured is set to `true`.
If the configuration above becomes modified and you wish to restore the original configuration, uncheck Is Configured and the GGTextureRect will be reconfigured.

For most purposes the images that provide the textures for GGTextureRect nodes should be imported with mipmaps enabled and the texture filter mode should be set to Linear Mipmap so that images look good when scaled down. To import with mipmaps, select one or more images in the FileSystem panel, switch from the Scene tab to the Import tab, tick the `Mipmaps > Generate` checkbox, and click `Reimport(*)`.

To enable Linear Mipmap mode, inspect the properties of the scene's root node (or the CanvasItem parent node that's closest to the scene root) and change `Texture > Filter` to `Linear Mipmap`. As nodes use mode `Inherited` by default, all other nodes will now use Linear Mipmap mode.

### GGNinePatchRect

GGNinePatchRect replicates NinePatchRect functionality and makes the following improvement: when the bounds of a GGNinePatchRect are smaller than its corners, the corners are proportionally shrunk to fit the available bounds.

(The 9-patch image is from [pixy.org](https://pixy.org/474956/))
#### Properties

Property|Description
--------|-----------
**Texture** | The texture to use for the nine-patch rect.
**Draw Center** | Check to draw the center patch of the nine-patch rect (default); uncheck to omit the center patch.
**Patch Margin: Left** | The pixel width of the left side of the texture that comprises three patches: top-left, left, and bottom-left.
**Patch Margin: Top** | The pixel height of the top side of the texture that comprises three patches: top-left, top, and top-right.
**Patch Margin: Right** | The pixel width of the right side of the texture that comprises three patches: top-right, right, and bottom-right.
**Patch Margin: Bottom** | The pixel height of the bottom side of the texture that comprises three patches: bottom-left, bottom, and bottom-right.
**Fill Mode: Horizontal** | The horizontal fill mode for each patch.
**Fill Mode: Vertical** | Ther vertical fill mode for each patch.
Fill Mode | Description
----------|------------
Stretch | Stretch or compress each patch to cover the available space.
Tile | Repeatedly tile each patch at its original pixel size to cover the available space.
Tile Fit | Tile each patch, stretching slightly as necessary to ensure a whole number of tiles fit in the available space.
## Parameter Components
### GGLayoutConfig

Place this node near the root of the GameGUI scene or subtree, extend the script, make it a `@tool`, override `func _on_begin_layout(display_size:Vector2)`, and call inherited method `set_parameter(name,value)` with various computed values related to the current display size. Those parameters can be automatically used by other GameGUI nodes by setting their sizing mode to *Parameter* and supplying the desired parameter name.

All GameGUI components define the following methods. A "subtree root" is the highest-level ancestor in an unbroken line of GameGUI component ancestors from the component that one of these methods is called on.
Method | Description
-------|------------------
`has_parameter(name:String)->bool` | Returns true if the subtree root's `parameters` dictionary defines a value with the specified name.
`set_parameter(name:String,value:Variant)` | Sets a value in the subtree root's `parameters` dictionary.
`get_parameter(name:String,default_result:Variant=0)->Variant` | Returns the specified value from the subtree root's `parameters` dictionary, if it exists, or else returns `default_result` if the value doesn't exist.
### GGParameterSetter

After a GGParameterSetter has been sized according to its settings, it sets specified subtree parameters to its own width and/or height, which can then be used to set the size of other components when they're in *Parameter* sizing mode.
#### Properties

Property|Description
--------|-----------
**Width Store** | The name of the subtree parameter to store this node's width in.
**Height Store** | The name of the subtree parameter to store this node's height in.
================================================
FILE: addons/GameGUI/GGButton.gd
================================================
@tool
class_name GGButton
extends Button
#-------------------------------------------------------------------------------
# GAMEGUI PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Text Size")
## Check to lock in the current font size and reference node height as reference
## values that will be used to scale the font size.
@export var text_size_mode:GGComponent.TextSizeMode:
set(value):
if text_size_mode == value: return
text_size_mode = value
if not get_parent(): return # resource loading is setting properties
match value:
GGComponent.TextSizeMode.DEFAULT:
reference_node_height = 0
reference_font_size = 0
GGComponent.TextSizeMode.SCALE:
if reference_node: reference_node_height = floor(reference_node.size.y)
reference_font_size = get_theme_font_size( "font_size" )
GGComponent.TextSizeMode.PARAMETER:
reference_node_height = 0
reference_font_size = 0
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
request_layout()
## A node that will be used as a height reference for scaling this node's text.
@export var reference_node:Control :
set(value):
if reference_node == value: return
reference_node = value
if reference_node and reference_node_height == 0:
reference_node_height = int(value.size.y)
request_layout()
## The height of the [Button] node that the [member reference_font_size] was designed for.
## This is used to scale the font based on the current height of the reference node.
@export var reference_node_height := 0 :
set(value):
if reference_node_height == value: return
reference_node_height = value
request_layout()
## The original size of the font.
@export var reference_font_size := 0 :
set(value):
if reference_font_size == value: return
reference_font_size = value
request_layout()
## The name of the parameter to use when [member text_size_mode] is [b]Parameter[/b].
@export var text_size_parameter:String = "" :
set(value):
if text_size_parameter == value: return
text_size_parameter = value
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Automatically set to indicate that default properties have been set
## for this node. Uncheck to reset those defaults.
@export var is_configured := false
# Internal editor use to detect font size changes and request an updated layout.
var _current_node_height := 0
var _current_font_size := 0
# GameGUI framework use
var is_width_resolved := false ## Internal GameGUI use.
var is_height_resolved := false ## Internal GameGUI use.
#-------------------------------------------------------------------------------
# GGLABEL METHODS
#-------------------------------------------------------------------------------
func _ready():
_configure()
func _process(_delta):
_configure()
_check_for_modified_font_size()
func _check_for_modified_font_size():
if not Engine.is_editor_hint(): return
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
var cur_size = get_theme_font_size( "font_size" )
if cur_size != _current_font_size:
_current_font_size = cur_size
request_layout()
GGComponent.TextSizeMode.SCALE:
if _current_font_size != reference_font_size:
_current_font_size = reference_font_size
request_layout()
if reference_node:
var h = int(reference_node.size.y)
if _current_node_height != h:
_current_node_height = h
request_layout()
GGComponent.TextSizeMode.PARAMETER:
pass
func _configure():
if not is_configured and size.y > 0:
is_configured = true
if text == "": text = "Button"
#-------------------------------------------------------------------------------
# GAMEGUI API
#-------------------------------------------------------------------------------
## Returns the specified parameter's value if it exists in a [GGComponent]
## parent or ancestor. If it doesn't exist, returns [code]0[/code] or a
## specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this [GGComponent] subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in a
## [GGComponent] parent or ancestor.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level [GGComponent] root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
# Called when this component is about to compute its size. Any size computations
# relative to reference nodes higher in the tree should be performed here.
func _on_resolve_size( available_size:Vector2 ):
if Engine.is_editor_hint():
match text_size_mode:
GGComponent.TextSizeMode.DEFAULT:
# Save current font size theme override size to check for editor changes
_current_font_size = get_theme_font_size( "font_size" )
GGComponent.TextSizeMode.SCALE:
# Save current font reference size to check for editor changes
_current_font_size = reference_font_size
if reference_node: _current_node_height = int( reference_node.size.y )
match text_size_mode:
GGComponent.TextSizeMode.SCALE:
if reference_node and reference_node_height:
var cur_scale = floor(reference_node.size.y) / reference_node_height
# Override the size of the font to dynamically size it
var cur_size = reference_font_size * cur_scale
if cur_size:
add_theme_font_size_override( "font_size", cur_size )
GGComponent.TextSizeMode.PARAMETER:
if has_parameter( text_size_parameter ):
add_theme_font_size_override( "font_size", int(get_parameter(text_size_parameter)) )
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
var top = get_top_level_component()
if top: top.request_layout()
================================================
FILE: addons/GameGUI/GGComponent.gd
================================================
@tool
## General-purpose layout box and base class to other GameGUI layout components. Children are
## stacked in layers.
##
## Nodes that do not extend [GGComponent] can still be children of GameGUI nodes in one of two
## ways.[br][br]
## First, GameGUI adapter code can be added to an extended class. See the [GGTextureRect] source
## code for an example that can be copy-pasted into other extended classes with only small
## modifications required.[br][br]
## Second, any non-[GGComponent] Control node that does not contain GameGUI adapter code will be
## treated as a component with [member horizontal_mode] and [member vertical_mode] both set to
## [b]Expand To Fill[/b]. The size of the node can be further managed by making it a child of
## a [GGComponent] that has other sizing modes.[br][br]
class_name GGComponent
extends Container
#-------------------------------------------------------------------------------
# SIGNALS
#-------------------------------------------------------------------------------
## Signals the beginning of the layout process for a GGComponent subtree.
## This signal is only emitted by the top-level root of a GGComponent subtree.
signal begin_layout
## Signals the end of the layout process for a GGComponent subtree.
## This signal is only emitted by the top-level root of a GGComponent subtree.
signal end_layout
#-------------------------------------------------------------------------------
# ENUMS
#-------------------------------------------------------------------------------
## The possible horizontal and vertical sizing modes for a GameGUI component.
enum ScalingMode
{
EXPAND_TO_FILL, ## Fill all available space along this dimension.
ASPECT_FIT, ## Dynamically adjusts size to maintain aspect ratio [member layout_size].x:[member layout_size].y, just small enough to entirely fit available space.
ASPECT_FILL, ## Dynamically adjusts size to maintain aspect ratio [member layout_size].x:[member layout_size].y, just large enough to entirely fill available space.
PROPORTIONAL, ## The layout size represents a proportional fraction of 1) the available area or 2) the size of the [member reference_node] if defined.
SHRINK_TO_FIT, ## Make the size just large enough to contain all child nodes in their layout.
FIXED, ## Fixed pixel size along this dimension.
PARAMETER ## One of the subtree [member parameters] is used as the size.
}
## The text sizing mode for [GGLabel], [GGRichTextLabel], and [GGButton].
enum TextSizeMode
{
DEFAULT, ## Text size is whatever size you assign in the editor.
SCALE, ## Text scales with the size of a reference node.
PARAMETER ## Text size is set to the value of one of the defined [member parameters].
}
## The texture fill mode used by [method fill_texture].
enum FillMode
{
STRETCH, # Stretch or compress each patch to cover the available space.
TILE, # Repeatedly tile each patch at its original pixel size to cover the available space.
TILE_FIT # Tile each patche, stretching slightly as necessary to ensure a whole number of tiles fit in the available space.
}
#-------------------------------------------------------------------------------
# PROPERTIES
#-------------------------------------------------------------------------------
@export_group("Component Layout")
## The horizontal scaling mode for this node.
@export var horizontal_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if horizontal_mode == value: return
horizontal_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if vertical_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: vertical_mode = value
if layout_size.x < 0.0001: layout_size.x = 1
if layout_size.y < 0.0001: layout_size.y = 1
elif vertical_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): vertical_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## The vertical scaling mode for this node.
@export var vertical_mode := GGComponent.ScalingMode.EXPAND_TO_FILL:
set(value):
if vertical_mode == value: return
vertical_mode = value
if value in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if horizontal_mode in [GGComponent.ScalingMode.PROPORTIONAL,GGComponent.ScalingMode.FIXED,GGComponent.ScalingMode.PARAMETER]: horizontal_mode = value
if abs(layout_size.x) < 0.0001: layout_size.x = 1
if abs(layout_size.y) < 0.0001: layout_size.y = 1
elif horizontal_mode in [GGComponent.ScalingMode.ASPECT_FIT,GGComponent.ScalingMode.ASPECT_FILL]:
if not (value in [GGComponent.ScalingMode.EXPAND_TO_FILL,GGComponent.ScalingMode.SHRINK_TO_FIT,GGComponent.ScalingMode.PARAMETER]): horizontal_mode = value
if value == GGComponent.ScalingMode.PROPORTIONAL:
if layout_size.x < 0.0001 or layout_size.x > 1: layout_size.x = 1
if layout_size.y < 0.0001 or layout_size.x > 1: layout_size.y = 1
request_layout()
## Pixel values for scaling mode [b]Fixed[/b], fractional values for [b]Proportional[/b], and aspect ratio values for [b]Aspect Fit[/b] and [b]Aspect Fill[/b].
@export var layout_size := Vector2(0,0):
set(value):
if layout_size == value: return
# The initial Vector2(0,0) may come in as e.g. 0.00000000000208 for x and y
if abs(value.x) < 0.00001: value.x = 0
if abs(value.y) < 0.00001: value.y = 0
layout_size = value
request_layout()
## An optional node to use as a size reference for [b]Proportional[/b] scaling
## mode. The reference node must be in a subtree higher in the scene tree than
## this node. Often the size reference is an invisible root-level square-aspect
## component; this allows same-size horizontal and vertical proportional spacers.
@export var reference_node:Control = null :
set(value):
if reference_node != value:
reference_node = value
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] horizontal scaling mode.
@export var width_parameter := "" :
set(value):
if width_parameter == value: return
width_parameter = value
if value != "" and has_parameter(value):
request_layout()
## The name of the parameter to use for the [b]Parameter[/b] vertical_mode scaling mode.
@export var height_parameter := "" :
set(value):
if height_parameter == value: return
height_parameter = value
if value != "" and has_parameter(value):
request_layout()
## Parameter definitions for nodes that use scaling mode PARAMETER. Parameters are stored
## the root of a GGComponent subtree. Use [method get_parameter], [method has_parameter], and
## [method set_parameter] to access parameters from any subtree nodes.
@export var parameters := {} :
set(value):
if parameters == value: return
parameters = value
request_layout()
# A top-level GGComponent is one that has no GGComponent parent.
# It oversees the layout of its descendent nodes.
var _is_top_level := false
var _layout_stage := 0 # top-level component use. 0=layout finished, 1=layout requested, 2=performing layout
#-------------------------------------------------------------------------------
# EXTERNAL API
#-------------------------------------------------------------------------------
## Utility method that draws a texture with any combination of horizontal and vertical fill modes: Stretch, Tile, Tile Fit.
## Used primarily by [GGComponent].
func fill_texture( texture:Texture2D, dest_rect:Rect2, src_rect:Rect2, horizontal_fill_mode:FillMode=FillMode.STRETCH,
vertical_fill_mode:FillMode=FillMode.STRETCH, modulate:Color=Color(1,1,1,1) ):
if dest_rect.size.x <= 0 or dest_rect.size.y <= 0: return
if horizontal_fill_mode == FillMode.TILE and src_rect.size.x > dest_rect.size.x:
horizontal_fill_mode = FillMode.TILE_FIT
if vertical_fill_mode == FillMode.TILE and src_rect.size.y > dest_rect.size.y:
vertical_fill_mode = FillMode.TILE_FIT
match horizontal_fill_mode:
FillMode.TILE:
var tile_size = src_rect.size
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_w > 0:
if tile_size.x <= dest_w:
var _dest_rect = Rect2( dest_pos, Vector2(tile_size.x,dest_h) )
fill_texture( texture, _dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
var _src_rect = Rect2( src_rect.position, Vector2(dest_w,src_rect.size.y) )
fill_texture( texture, _dest_rect, _src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
return
dest_pos += Vector2( tile_size.x, 0 )
dest_w -= tile_size.x
return
FillMode.TILE_FIT:
var n = int( (dest_rect.size.x / src_rect.size.x) + 0.5 )
if n == 0:
fill_texture( texture, dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
else:
var tile_size = Vector2( dest_rect.size.x / n, src_rect.size.y )
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_w > 0:
if tile_size.x <= dest_w:
var _dest_rect = Rect2( dest_pos, Vector2(tile_size.x,dest_h) )
fill_texture( texture, _dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
fill_texture( texture, _dest_rect, src_rect, FillMode.STRETCH, vertical_fill_mode, modulate )
return
dest_pos += Vector2( tile_size.x, 0 )
dest_w -= tile_size.x
return
match vertical_fill_mode:
FillMode.TILE:
var tile_size = src_rect.size
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_h > 0:
if tile_size.y <= dest_h:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w, tile_size.y) )
fill_texture( texture, _dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
var _src_rect = Rect2( src_rect.position, Vector2(src_rect.size.x,dest_h) )
fill_texture( texture, _dest_rect, _src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
return
dest_pos += Vector2( 0, tile_size.y )
dest_h -= tile_size.y
return
FillMode.TILE_FIT:
var n = int( (dest_rect.size.y / src_rect.size.y) + 0.5 )
if n == 0:
fill_texture( texture, dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
else:
var tile_size = Vector2( src_rect.size.x, dest_rect.size.y / n )
var dest_pos = dest_rect.position
var dest_w = dest_rect.size.x
var dest_h = dest_rect.size.y
while dest_h > 0:
if tile_size.y <= dest_h:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w, tile_size.y) )
fill_texture( texture, _dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
else:
var _dest_rect = Rect2( dest_pos, Vector2(dest_w,dest_h) )
fill_texture( texture, _dest_rect, src_rect, horizontal_fill_mode, FillMode.STRETCH, modulate )
return
dest_pos += Vector2( 0, tile_size.y )
dest_h -= tile_size.y
return
# Horizontal and vertical fill are both STRETCH
draw_texture_rect_region( texture, dest_rect, src_rect, modulate )
## Returns the specified parameter's value if it exists in the [member parameters]
## of this node or a [GGComponent] ancestor. If it doesn't exist, returns
## [code]0[/code] or a specified default result.
func get_parameter( parameter_name:String, default_result:Variant=0 )->Variant:
var top = get_top_level_component()
if top and top.parameters.has(parameter_name):
return top.parameters[parameter_name]
else:
return default_result
## Returns the root of this GGComponent subtree.
func get_top_level_component()->GGComponent:
var cur = self
while cur and (not cur is GGComponent or not cur._is_top_level):
cur = cur.get_parent()
return cur
## Returns [code]true[/code] if the specified parameter exists in the
## [member parameters] of this node or one of its ancestors.
func has_parameter( parameter_name:String )->bool:
var top = get_top_level_component()
if top:
return top.parameters.has(parameter_name)
else:
return false
## Sets the named parameter's value in the top-level root of this subtree.
func set_parameter( parameter_name:String, value:Variant ):
var top = get_top_level_component()
if top: top.parameters[parameter_name] = value
## Layout is performed automatically in most cases, but request_layout() can be
## called for edge cases.
func request_layout():
if _is_top_level:
if _layout_stage == 0:
_layout_stage = 1
queue_sort()
else:
var top = get_top_level_component()
if top: top.request_layout()
#-------------------------------------------------------------------------------
# KEY OVERRIDES
#-------------------------------------------------------------------------------
func _on_resolve_size( available_size:Vector2 ):
# Overrideable.
# Called just before this component's size is resolved.
# Override and adjust this component's size if desired.
pass
func _on_update_size():
# Overrideable.
# Called at the beginning of layout.
# Override and adjust this GGComponent's size if desired.
pass
func _perform_child_layout(
gitextract_ft3ucw32/
├── .gdignore
├── .gitignore
├── Build.rogue
├── ChangeLog.md
├── DemoProject/
│ ├── .gdignore
│ ├── Border.gd
│ ├── DemoScene.tscn
│ ├── LayoutConfig.gd
│ ├── TextArea.gd
│ ├── addons/
│ │ └── GameGUI/
│ │ ├── GGButton.gd
│ │ ├── GGComponent.gd
│ │ ├── GGFiller.gd
│ │ ├── GGHBox.gd
│ │ ├── GGInitialWindowSize.gd
│ │ ├── GGLabel.gd
│ │ ├── GGLayoutConfig.gd
│ │ ├── GGLimitedSizeComponent.gd
│ │ ├── GGMarginLayout.gd
│ │ ├── GGNinePatchRect.gd
│ │ ├── GGOverlay.gd
│ │ ├── GGParameterSetter.gd
│ │ ├── GGRichTextLabel.gd
│ │ ├── GGTextureRect.gd
│ │ ├── GGVBox.gd
│ │ ├── plugin.cfg
│ │ └── plugin.gd
│ └── project.godot
├── LICENSE
├── Media/
│ ├── .gdignore
│ └── Images/
│ └── Icons/
│ └── Icons.sketch
├── README.md
└── addons/
└── GameGUI/
├── GGButton.gd
├── GGComponent.gd
├── GGFiller.gd
├── GGHBox.gd
├── GGInitialWindowSize.gd
├── GGLabel.gd
├── GGLayoutConfig.gd
├── GGLimitedSizeComponent.gd
├── GGMarginLayout.gd
├── GGNinePatchRect.gd
├── GGOverlay.gd
├── GGParameterSetter.gd
├── GGRichTextLabel.gd
├── GGTextureRect.gd
├── GGVBox.gd
├── plugin.cfg
└── plugin.gd
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (295K chars).
[
{
"path": ".gdignore",
"chars": 62,
"preview": "This file prevents Godot from directly importing these files.\n"
},
{
"path": ".gitignore",
"chars": 498,
"preview": "# Demo Project\nDemoProject/Test.tscn\n\n# Godot-specific ignores\n.godot\n.import/\nexport.cfg\nexport_presets.cfg\n*.import\n\n#"
},
{
"path": "Build.rogue",
"chars": 15120,
"preview": "# To run this build file, install Rogue from github.com/brombres/Rogue then cd\n# to this folder and type \"rogo\" at the c"
},
{
"path": "ChangeLog.md",
"chars": 4443,
"preview": "# GameGUI Change Log\n\n## v1.5 (October 18, 2023)\n\n### Text Scaling Bugfix\n- Scaling text nodes previously failed to upda"
},
{
"path": "DemoProject/.gdignore",
"chars": 65,
"preview": "Godot will ignore this folder when installing the GameGUI addon.\n"
},
{
"path": "DemoProject/Border.gd",
"chars": 97,
"preview": "@tool\n\nextends Control\n\nfunc _draw():\n\tdraw_rect( Rect2(position,size), Color(1,1,1), false, 1 )\n"
},
{
"path": "DemoProject/DemoScene.tscn",
"chars": 13883,
"preview": "[gd_scene load_steps=15 format=3 uid=\"uid://s72astt3jqi8\"]\n\n[ext_resource type=\"Script\" path=\"res://addons/GameGUI/GGIni"
},
{
"path": "DemoProject/LayoutConfig.gd",
"chars": 188,
"preview": "@tool\n\nextends GGLayoutConfig\n\nfunc _on_begin_layout( display_size:Vector2 ):\n\tset_parameter( \"half_width\", int(display_"
},
{
"path": "DemoProject/TextArea.gd",
"chars": 2385,
"preview": "@tool\n\nextends GGRichTextLabel\n\nfunc _on_gg_button__layout_pressed():\n\ttext = \"[b]GGComponent[/b] - GameGUI base node ty"
},
{
"path": "DemoProject/addons/GameGUI/GGButton.gd",
"chars": 9468,
"preview": "@tool\n\nclass_name GGButton\nextends Button\n\n#----------------------------------------------------------------------------"
},
{
"path": "DemoProject/addons/GameGUI/GGComponent.gd",
"chars": 23912,
"preview": "@tool\n\n## General-purpose layout box and base class to other GameGUI layout components. Children are\n## stacked in layer"
},
{
"path": "DemoProject/addons/GameGUI/GGFiller.gd",
"chars": 272,
"preview": "@tool\n\n## A GameGUI node that by default will expand to fill any extra space within a layout.\n## There is no functional "
},
{
"path": "DemoProject/addons/GameGUI/GGHBox.gd",
"chars": 5802,
"preview": "@tool\n\n## A GameGUI layout that arranges its child elements in a horizontal row.\nclass_name GGHBox\nextends GGComponent\n\n"
},
{
"path": "DemoProject/addons/GameGUI/GGInitialWindowSize.gd",
"chars": 1515,
"preview": "@tool\n\n## A control that sets the initial window size to be its own size if it is the base\n## node of the scene. Facilit"
},
{
"path": "DemoProject/addons/GameGUI/GGLabel.gd",
"chars": 10084,
"preview": "@tool\n\n## A Label with expanded layout and scaling capabilities. Make this a child of an extended\n## GGComponent, not a "
},
{
"path": "DemoProject/addons/GameGUI/GGLayoutConfig.gd",
"chars": 2443,
"preview": "@tool\n\n## Extend this node and override [method _on_begin_layout] to set GameGUI parameters\n## prior to each layout via "
},
{
"path": "DemoProject/addons/GameGUI/GGLimitedSizeComponent.gd",
"chars": 3021,
"preview": "@tool\n\n## A component that can limit its size to arbitrary minimum and/or maximum values.\nclass_name GGLimitedSizeCompon"
},
{
"path": "DemoProject/addons/GameGUI/GGMarginLayout.gd",
"chars": 8183,
"preview": "@tool\n\n## Lays out its children in a layered stack with pixel or proportional margins.\nclass_name GGMarginLayout\nextends"
},
{
"path": "DemoProject/addons/GameGUI/GGNinePatchRect.gd",
"chars": 4040,
"preview": "@tool\n\nclass_name GGNinePatchRect\nextends GGComponent\n\n@export var texture:Texture2D :\n\tset(value):\n\t\ttexture = value\n\t\t"
},
{
"path": "DemoProject/addons/GameGUI/GGOverlay.gd",
"chars": 5586,
"preview": "@tool\n\n## Positions its children at arbitrary coordinates within its own bounds, similiar to a sprite.\n## Not intended f"
},
{
"path": "DemoProject/addons/GameGUI/GGParameterSetter.gd",
"chars": 1264,
"preview": "@tool\n\n## A component that sizes normally and then sets subtree parameters to its own width and/or height.\nclass_name GG"
},
{
"path": "DemoProject/addons/GameGUI/GGRichTextLabel.gd",
"chars": 11823,
"preview": "@tool\n\nclass_name GGRichTextLabel\nextends RichTextLabel\n\n#--------------------------------------------------------------"
},
{
"path": "DemoProject/addons/GameGUI/GGTextureRect.gd",
"chars": 6757,
"preview": "@tool\n\n## Extends TextureRect and adapts it to work painlessly with the GameGUI layout system.\nclass_name GGTextureRect\n"
},
{
"path": "DemoProject/addons/GameGUI/GGVBox.gd",
"chars": 5847,
"preview": "@tool\n\n## A GameGUI layout that arranges its child elements in a vertical column.\nclass_name GGVBox\nextends GGComponent\n"
},
{
"path": "DemoProject/addons/GameGUI/plugin.cfg",
"chars": 194,
"preview": "[plugin]\n\nname=\"GameGUI\"\ndescription=\"A collection of dynamic layout nodes that provide a full-featured alternative to C"
},
{
"path": "DemoProject/addons/GameGUI/plugin.gd",
"chars": 2605,
"preview": "@tool\nextends EditorPlugin\n\nfunc _enter_tree():\n\t# Initialization of the plugin goes here.\n\tadd_custom_type( \"GGButton\","
},
{
"path": "DemoProject/project.godot",
"chars": 510,
"preview": "; Engine configuration file.\n; It's best edited using the editor UI and not directly,\n; since the parameters that go her"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2023 Brom Bresenham\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "Media/.gdignore",
"chars": 65,
"preview": "Godot will ignore this folder when installing the GameGUI addon.\n"
},
{
"path": "README.md",
"chars": 31324,
"preview": "# Godot-GameGUI\nA Godot 4.x plug-in that implements a rich and robust dynamic layout and sizing system for building user"
},
{
"path": "addons/GameGUI/GGButton.gd",
"chars": 9468,
"preview": "@tool\n\nclass_name GGButton\nextends Button\n\n#----------------------------------------------------------------------------"
},
{
"path": "addons/GameGUI/GGComponent.gd",
"chars": 23912,
"preview": "@tool\n\n## General-purpose layout box and base class to other GameGUI layout components. Children are\n## stacked in layer"
},
{
"path": "addons/GameGUI/GGFiller.gd",
"chars": 272,
"preview": "@tool\n\n## A GameGUI node that by default will expand to fill any extra space within a layout.\n## There is no functional "
},
{
"path": "addons/GameGUI/GGHBox.gd",
"chars": 5802,
"preview": "@tool\n\n## A GameGUI layout that arranges its child elements in a horizontal row.\nclass_name GGHBox\nextends GGComponent\n\n"
},
{
"path": "addons/GameGUI/GGInitialWindowSize.gd",
"chars": 1515,
"preview": "@tool\n\n## A control that sets the initial window size to be its own size if it is the base\n## node of the scene. Facilit"
},
{
"path": "addons/GameGUI/GGLabel.gd",
"chars": 10084,
"preview": "@tool\n\n## A Label with expanded layout and scaling capabilities. Make this a child of an extended\n## GGComponent, not a "
},
{
"path": "addons/GameGUI/GGLayoutConfig.gd",
"chars": 2443,
"preview": "@tool\n\n## Extend this node and override [method _on_begin_layout] to set GameGUI parameters\n## prior to each layout via "
},
{
"path": "addons/GameGUI/GGLimitedSizeComponent.gd",
"chars": 3021,
"preview": "@tool\n\n## A component that can limit its size to arbitrary minimum and/or maximum values.\nclass_name GGLimitedSizeCompon"
},
{
"path": "addons/GameGUI/GGMarginLayout.gd",
"chars": 8183,
"preview": "@tool\n\n## Lays out its children in a layered stack with pixel or proportional margins.\nclass_name GGMarginLayout\nextends"
},
{
"path": "addons/GameGUI/GGNinePatchRect.gd",
"chars": 4040,
"preview": "@tool\n\nclass_name GGNinePatchRect\nextends GGComponent\n\n@export var texture:Texture2D :\n\tset(value):\n\t\ttexture = value\n\t\t"
},
{
"path": "addons/GameGUI/GGOverlay.gd",
"chars": 5586,
"preview": "@tool\n\n## Positions its children at arbitrary coordinates within its own bounds, similiar to a sprite.\n## Not intended f"
},
{
"path": "addons/GameGUI/GGParameterSetter.gd",
"chars": 1264,
"preview": "@tool\n\n## A component that sizes normally and then sets subtree parameters to its own width and/or height.\nclass_name GG"
},
{
"path": "addons/GameGUI/GGRichTextLabel.gd",
"chars": 11823,
"preview": "@tool\n\nclass_name GGRichTextLabel\nextends RichTextLabel\n\n#--------------------------------------------------------------"
},
{
"path": "addons/GameGUI/GGTextureRect.gd",
"chars": 6757,
"preview": "@tool\n\n## Extends TextureRect and adapts it to work painlessly with the GameGUI layout system.\nclass_name GGTextureRect\n"
},
{
"path": "addons/GameGUI/GGVBox.gd",
"chars": 5847,
"preview": "@tool\n\n## A GameGUI layout that arranges its child elements in a vertical column.\nclass_name GGVBox\nextends GGComponent\n"
},
{
"path": "addons/GameGUI/plugin.cfg",
"chars": 194,
"preview": "[plugin]\n\nname=\"GameGUI\"\ndescription=\"A collection of dynamic layout nodes that provide a full-featured alternative to C"
},
{
"path": "addons/GameGUI/plugin.gd",
"chars": 2605,
"preview": "@tool\nextends EditorPlugin\n\nfunc _enter_tree():\n\t# Initialization of the plugin goes here.\n\tadd_custom_type( \"GGButton\","
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the brombres/Godot-GameGUI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (268.9 KB), approximately 71.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.