Browse Source

Merge pull request #69353 from YuriSizov/window-be-more-like-your-brother

Copy local theme overrides from `Control` to `Window`
Rémi Verschelde 2 years ago
parent
commit
597e0c0fb9
6 changed files with 815 additions and 151 deletions
  1. 2 1
      doc/classes/Control.xml
  2. 186 24
      doc/classes/Window.xml
  3. 79 79
      scene/gui/control.cpp
  4. 6 6
      scene/gui/control.h
  5. 491 31
      scene/main/window.cpp
  6. 51 10
      scene/main/window.h

+ 2 - 1
doc/classes/Control.xml

@@ -1071,7 +1071,8 @@
 			Tells the parent [Container] nodes how they should resize and place the node on the Y axis. Use one of the [enum SizeFlags] constants to change the flags. See the constants to learn what each does.
 		</member>
 		<member name="theme" type="Theme" setter="set_theme" getter="get_theme">
-			The [Theme] resource this node and all its [Control] children use. If a child node has its own [Theme] resource set, theme items are merged with child's definitions having higher priority.
+			The [Theme] resource this node and all its [Control] and [Window] children use. If a child node has its own [Theme] resource set, theme items are merged with child's definitions having higher priority.
+			[b]Note:[/b] [Window] styles will have no effect unless the window is embedded.
 		</member>
 		<member name="theme_type_variation" type="StringName" setter="set_theme_type_variation" getter="get_theme_type_variation" default="&amp;&quot;&quot;">
 			The name of a theme type variation used by this [Control] to look up its own theme items. When empty, the class name of the node is used (e.g. [code]Button[/code] for the [Button] control), as well as the class names of all parent classes (in order of inheritance).

+ 186 - 24
doc/classes/Window.xml

@@ -10,6 +10,66 @@
 	<tutorials>
 	</tutorials>
 	<methods>
+		<method name="add_theme_color_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="color" type="Color" />
+			<description>
+				Creates a local override for a theme [Color] with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_color_override].
+				See also [method get_theme_color] and [method Control.add_theme_color_override] for more details.
+			</description>
+		</method>
+		<method name="add_theme_constant_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="constant" type="int" />
+			<description>
+				Creates a local override for a theme constant with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_constant_override].
+				See also [method get_theme_constant].
+			</description>
+		</method>
+		<method name="add_theme_font_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="font" type="Font" />
+			<description>
+				Creates a local override for a theme [Font] with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_font_override].
+				See also [method get_theme_font].
+			</description>
+		</method>
+		<method name="add_theme_font_size_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="font_size" type="int" />
+			<description>
+				Creates a local override for a theme font size with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_font_size_override].
+				See also [method get_theme_font_size].
+			</description>
+		</method>
+		<method name="add_theme_icon_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="texture" type="Texture2D" />
+			<description>
+				Creates a local override for a theme icon with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_icon_override].
+				See also [method get_theme_icon].
+			</description>
+		</method>
+		<method name="add_theme_stylebox_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<param index="1" name="stylebox" type="StyleBox" />
+			<description>
+				Creates a local override for a theme [StyleBox] with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_stylebox_override].
+				See also [method get_theme_stylebox] and [method Control.add_theme_stylebox_override] for more details.
+			</description>
+		</method>
+		<method name="begin_bulk_theme_override">
+			<return type="void" />
+			<description>
+				Prevents [code]*_theme_*_override[/code] methods from emitting [constant NOTIFICATION_THEME_CHANGED] until [method end_bulk_theme_override] is called.
+			</description>
+		</method>
 		<method name="can_draw" qualifiers="const">
 			<return type="bool" />
 			<description>
@@ -22,6 +82,12 @@
 				Requests an update of the [Window] size to fit underlying [Control] nodes.
 			</description>
 		</method>
+		<method name="end_bulk_theme_override">
+			<return type="void" />
+			<description>
+				Ends a bulk theme override update. See [method begin_bulk_theme_override].
+			</description>
+		</method>
 		<method name="get_contents_minimum_size" qualifiers="const">
 			<return type="Vector2" />
 			<description>
@@ -58,7 +124,7 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns the [Color] at [param name] if the theme has [param theme_type].
+				Returns a [Color] from the first matching [Theme] in the tree if that [Theme] has a color item with the specified [param name] and [param theme_type].
 				See [method Control.get_theme_color] for more details.
 			</description>
 		</method>
@@ -67,29 +133,29 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns the constant at [param name] if the theme has [param theme_type].
+				Returns a constant from the first matching [Theme] in the tree if that [Theme] has a constant item with the specified [param name] and [param theme_type].
 				See [method Control.get_theme_color] for more details.
 			</description>
 		</method>
 		<method name="get_theme_default_base_scale" qualifiers="const">
 			<return type="float" />
 			<description>
-				Returns the default base scale defined in the attached [Theme].
-				See [member Theme.default_base_scale] for more details.
+				Returns the default base scale value from the first matching [Theme] in the tree if that [Theme] has a valid [member Theme.default_base_scale] value.
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="get_theme_default_font" qualifiers="const">
 			<return type="Font" />
 			<description>
-				Returns the default [Font] defined in the attached [Theme].
-				See [member Theme.default_font] for more details.
+				Returns the default font from the first matching [Theme] in the tree if that [Theme] has a valid [member Theme.default_font] value.
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="get_theme_default_font_size" qualifiers="const">
 			<return type="int" />
 			<description>
-				Returns the default font size defined in the attached [Theme].
-				See [member Theme.default_font_size] for more details.
+				Returns the default font size value from the first matching [Theme] in the tree if that [Theme] has a valid [member Theme.default_font_size] value.
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="get_theme_font" qualifiers="const">
@@ -97,8 +163,8 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns the [Font] at [param name] if the theme has [param theme_type].
-				See [method Control.get_theme_color] for more details.
+				Returns a [Font] from the first matching [Theme] in the tree if that [Theme] has a font item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="get_theme_font_size" qualifiers="const">
@@ -106,8 +172,8 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns the font size at [param name] if the theme has [param theme_type].
-				See [method Control.get_theme_color] for more details.
+				Returns a font size from the first matching [Theme] in the tree if that [Theme] has a font size item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="get_theme_icon" qualifiers="const">
@@ -115,8 +181,8 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns the icon at [param name] if the theme has [param theme_type].
-				See [method Control.get_theme_color] for more details.
+				Returns an icon from the first matching [Theme] in the tree if that [Theme] has an icon item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="get_theme_stylebox" qualifiers="const">
@@ -124,8 +190,8 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns the [StyleBox] at [param name] if the theme has [param theme_type].
-				See [method Control.get_theme_color] for more details.
+				Returns a [StyleBox] from the first matching [Theme] in the tree if that [Theme] has a stylebox item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
 			</description>
 		</method>
 		<method name="grab_focus">
@@ -145,7 +211,16 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns [code]true[/code] if [Color] with [param name] is in [param theme_type].
+				Returns [code]true[/code] if there is a matching [Theme] in the tree that has a color item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
+			</description>
+		</method>
+		<method name="has_theme_color_override" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Returns [code]true[/code] if there is a local override for a theme [Color] with the specified [param name] in this [Control] node.
+				See [method add_theme_color_override].
 			</description>
 		</method>
 		<method name="has_theme_constant" qualifiers="const">
@@ -153,7 +228,16 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns [code]true[/code] if constant with [param name] is in [param theme_type].
+				Returns [code]true[/code] if there is a matching [Theme] in the tree that has a constant item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
+			</description>
+		</method>
+		<method name="has_theme_constant_override" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Returns [code]true[/code] if there is a local override for a theme constant with the specified [param name] in this [Control] node.
+				See [method add_theme_constant_override].
 			</description>
 		</method>
 		<method name="has_theme_font" qualifiers="const">
@@ -161,7 +245,16 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns [code]true[/code] if [Font] with [param name] is in [param theme_type].
+				Returns [code]true[/code] if there is a matching [Theme] in the tree that has a font item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
+			</description>
+		</method>
+		<method name="has_theme_font_override" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Returns [code]true[/code] if there is a local override for a theme [Font] with the specified [param name] in this [Control] node.
+				See [method add_theme_font_override].
 			</description>
 		</method>
 		<method name="has_theme_font_size" qualifiers="const">
@@ -169,7 +262,16 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns [code]true[/code] if font size with [param name] is in [param theme_type].
+				Returns [code]true[/code] if there is a matching [Theme] in the tree that has a font size item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
+			</description>
+		</method>
+		<method name="has_theme_font_size_override" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Returns [code]true[/code] if there is a local override for a theme font size with the specified [param name] in this [Control] node.
+				See [method add_theme_font_size_override].
 			</description>
 		</method>
 		<method name="has_theme_icon" qualifiers="const">
@@ -177,7 +279,16 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns [code]true[/code] if icon with [param name] is in [param theme_type].
+				Returns [code]true[/code] if there is a matching [Theme] in the tree that has an icon item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
+			</description>
+		</method>
+		<method name="has_theme_icon_override" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Returns [code]true[/code] if there is a local override for a theme icon with the specified [param name] in this [Control] node.
+				See [method add_theme_icon_override].
 			</description>
 		</method>
 		<method name="has_theme_stylebox" qualifiers="const">
@@ -185,7 +296,16 @@
 			<param index="0" name="name" type="StringName" />
 			<param index="1" name="theme_type" type="StringName" default="&quot;&quot;" />
 			<description>
-				Returns [code]true[/code] if [StyleBox] with [param name] is in [param theme_type].
+				Returns [code]true[/code] if there is a matching [Theme] in the tree that has a stylebox item with the specified [param name] and [param theme_type].
+				See [method Control.get_theme_color] for details.
+			</description>
+		</method>
+		<method name="has_theme_stylebox_override" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Returns [code]true[/code] if there is a local override for a theme [StyleBox] with the specified [param name] in this [Control] node.
+				See [method add_theme_stylebox_override].
 			</description>
 		</method>
 		<method name="hide">
@@ -264,6 +384,48 @@
 				If the [Window] is embedded, has the same effect as [method popup].
 			</description>
 		</method>
+		<method name="remove_theme_color_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Removes a local override for a theme [Color] with the specified [param name] previously added by [method add_theme_color_override] or via the Inspector dock.
+			</description>
+		</method>
+		<method name="remove_theme_constant_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Removes a local override for a theme constant with the specified [param name] previously added by [method add_theme_constant_override] or via the Inspector dock.
+			</description>
+		</method>
+		<method name="remove_theme_font_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Removes a local override for a theme [Font] with the specified [param name] previously added by [method add_theme_font_override] or via the Inspector dock.
+			</description>
+		</method>
+		<method name="remove_theme_font_size_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Removes a local override for a theme font size with the specified [param name] previously added by [method add_theme_font_size_override] or via the Inspector dock.
+			</description>
+		</method>
+		<method name="remove_theme_icon_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Removes a local override for a theme icon with the specified [param name] previously added by [method add_theme_icon_override] or via the Inspector dock.
+			</description>
+		</method>
+		<method name="remove_theme_stylebox_override">
+			<return type="void" />
+			<param index="0" name="name" type="StringName" />
+			<description>
+				Removes a local override for a theme [StyleBox] with the specified [param name] previously added by [method add_theme_stylebox_override] or via the Inspector dock.
+			</description>
+		</method>
 		<method name="request_attention">
 			<return type="void" />
 			<description>
@@ -373,8 +535,8 @@
 			The window's size in pixels.
 		</member>
 		<member name="theme" type="Theme" setter="set_theme" getter="get_theme">
-			The [Theme] resource that determines the style of the underlying [Control] nodes.
-			[Window] styles will have no effect unless the window is embedded.
+			The [Theme] resource this node and all its [Control] and [Window] children use. If a child node has its own [Theme] resource set, theme items are merged with child's definitions having higher priority.
+			[b]Note:[/b] [Window] styles will have no effect unless the window is embedded.
 		</member>
 		<member name="theme_type_variation" type="StringName" setter="set_theme_type_variation" getter="get_theme_type_variation" default="&amp;&quot;&quot;">
 			The name of a theme type variation used by this [Window] to look up its own theme items. See [member Control.theme_type_variation] for more details.

+ 79 - 79
scene/gui/control.cpp

@@ -257,36 +257,36 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) {
 	if (p_value.get_type() == Variant::NIL || (p_value.get_type() == Variant::OBJECT && (Object *)p_value == nullptr)) {
 		if (name.begins_with("theme_override_icons/")) {
 			String dname = name.get_slicec('/', 1);
-			if (data.icon_override.has(dname)) {
-				data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+			if (data.theme_icon_override.has(dname)) {
+				data.theme_icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 			}
-			data.icon_override.erase(dname);
+			data.theme_icon_override.erase(dname);
 			_notify_theme_override_changed();
 		} else if (name.begins_with("theme_override_styles/")) {
 			String dname = name.get_slicec('/', 1);
-			if (data.style_override.has(dname)) {
-				data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+			if (data.theme_style_override.has(dname)) {
+				data.theme_style_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 			}
-			data.style_override.erase(dname);
+			data.theme_style_override.erase(dname);
 			_notify_theme_override_changed();
 		} else if (name.begins_with("theme_override_fonts/")) {
 			String dname = name.get_slicec('/', 1);
-			if (data.font_override.has(dname)) {
-				data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+			if (data.theme_font_override.has(dname)) {
+				data.theme_font_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 			}
-			data.font_override.erase(dname);
+			data.theme_font_override.erase(dname);
 			_notify_theme_override_changed();
 		} else if (name.begins_with("theme_override_font_sizes/")) {
 			String dname = name.get_slicec('/', 1);
-			data.font_size_override.erase(dname);
+			data.theme_font_size_override.erase(dname);
 			_notify_theme_override_changed();
 		} else if (name.begins_with("theme_override_colors/")) {
 			String dname = name.get_slicec('/', 1);
-			data.color_override.erase(dname);
+			data.theme_color_override.erase(dname);
 			_notify_theme_override_changed();
 		} else if (name.begins_with("theme_override_constants/")) {
 			String dname = name.get_slicec('/', 1);
-			data.constant_override.erase(dname);
+			data.theme_constant_override.erase(dname);
 			_notify_theme_override_changed();
 		} else {
 			return false;
@@ -326,22 +326,22 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
 
 	if (sname.begins_with("theme_override_icons/")) {
 		String name = sname.get_slicec('/', 1);
-		r_ret = data.icon_override.has(name) ? Variant(data.icon_override[name]) : Variant();
+		r_ret = data.theme_icon_override.has(name) ? Variant(data.theme_icon_override[name]) : Variant();
 	} else if (sname.begins_with("theme_override_styles/")) {
 		String name = sname.get_slicec('/', 1);
-		r_ret = data.style_override.has(name) ? Variant(data.style_override[name]) : Variant();
+		r_ret = data.theme_style_override.has(name) ? Variant(data.theme_style_override[name]) : Variant();
 	} else if (sname.begins_with("theme_override_fonts/")) {
 		String name = sname.get_slicec('/', 1);
-		r_ret = data.font_override.has(name) ? Variant(data.font_override[name]) : Variant();
+		r_ret = data.theme_font_override.has(name) ? Variant(data.theme_font_override[name]) : Variant();
 	} else if (sname.begins_with("theme_override_font_sizes/")) {
 		String name = sname.get_slicec('/', 1);
-		r_ret = data.font_size_override.has(name) ? Variant(data.font_size_override[name]) : Variant();
+		r_ret = data.theme_font_size_override.has(name) ? Variant(data.theme_font_size_override[name]) : Variant();
 	} else if (sname.begins_with("theme_override_colors/")) {
 		String name = sname.get_slicec('/', 1);
-		r_ret = data.color_override.has(name) ? Variant(data.color_override[name]) : Variant();
+		r_ret = data.theme_color_override.has(name) ? Variant(data.theme_color_override[name]) : Variant();
 	} else if (sname.begins_with("theme_override_constants/")) {
 		String name = sname.get_slicec('/', 1);
-		r_ret = data.constant_override.has(name) ? Variant(data.constant_override[name]) : Variant();
+		r_ret = data.theme_constant_override.has(name) ? Variant(data.theme_constant_override[name]) : Variant();
 	} else {
 		return false;
 	}
@@ -350,16 +350,16 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const {
 }
 
 void Control::_get_property_list(List<PropertyInfo> *p_list) const {
-	Ref<Theme> theme = ThemeDB::get_singleton()->get_default_theme();
+	Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme();
 
 	p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP));
 
 	{
 		List<StringName> names;
-		theme->get_color_list(get_class_name(), &names);
+		default_theme->get_color_list(get_class_name(), &names);
 		for (const StringName &E : names) {
 			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
-			if (data.color_override.has(E)) {
+			if (data.theme_color_override.has(E)) {
 				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
 			}
 
@@ -368,10 +368,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
 	}
 	{
 		List<StringName> names;
-		theme->get_constant_list(get_class_name(), &names);
+		default_theme->get_constant_list(get_class_name(), &names);
 		for (const StringName &E : names) {
 			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
-			if (data.constant_override.has(E)) {
+			if (data.theme_constant_override.has(E)) {
 				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
 			}
 
@@ -380,10 +380,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
 	}
 	{
 		List<StringName> names;
-		theme->get_font_list(get_class_name(), &names);
+		default_theme->get_font_list(get_class_name(), &names);
 		for (const StringName &E : names) {
 			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
-			if (data.font_override.has(E)) {
+			if (data.theme_font_override.has(E)) {
 				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
 			}
 
@@ -392,10 +392,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
 	}
 	{
 		List<StringName> names;
-		theme->get_font_size_list(get_class_name(), &names);
+		default_theme->get_font_size_list(get_class_name(), &names);
 		for (const StringName &E : names) {
 			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
-			if (data.font_size_override.has(E)) {
+			if (data.theme_font_size_override.has(E)) {
 				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
 			}
 
@@ -404,10 +404,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
 	}
 	{
 		List<StringName> names;
-		theme->get_icon_list(get_class_name(), &names);
+		default_theme->get_icon_list(get_class_name(), &names);
 		for (const StringName &E : names) {
 			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
-			if (data.icon_override.has(E)) {
+			if (data.theme_icon_override.has(E)) {
 				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
 			}
 
@@ -416,10 +416,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const {
 	}
 	{
 		List<StringName> names;
-		theme->get_stylebox_list(get_class_name(), &names);
+		default_theme->get_stylebox_list(get_class_name(), &names);
 		for (const StringName &E : names) {
 			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
-			if (data.style_override.has(E)) {
+			if (data.theme_style_override.has(E)) {
 				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
 			}
 
@@ -2381,7 +2381,7 @@ StringName Control::get_theme_type_variation() const {
 
 Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
-		const Ref<Texture2D> *tex = data.icon_override.getptr(p_name);
+		const Ref<Texture2D> *tex = data.theme_icon_override.getptr(p_name);
 		if (tex) {
 			return *tex;
 		}
@@ -2400,7 +2400,7 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam
 
 Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
-		const Ref<StyleBox> *style = data.style_override.getptr(p_name);
+		const Ref<StyleBox> *style = data.theme_style_override.getptr(p_name);
 		if (style) {
 			return *style;
 		}
@@ -2419,7 +2419,7 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String
 
 Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
-		const Ref<Font> *font = data.font_override.getptr(p_name);
+		const Ref<Font> *font = data.theme_font_override.getptr(p_name);
 		if (font) {
 			return *font;
 		}
@@ -2438,7 +2438,7 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_
 
 int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
-		const int *font_size = data.font_size_override.getptr(p_name);
+		const int *font_size = data.theme_font_size_override.getptr(p_name);
 		if (font_size && (*font_size) > 0) {
 			return *font_size;
 		}
@@ -2457,7 +2457,7 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t
 
 Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
-		const Color *color = data.color_override.getptr(p_name);
+		const Color *color = data.theme_color_override.getptr(p_name);
 		if (color) {
 			return *color;
 		}
@@ -2476,7 +2476,7 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the
 
 int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
 	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) {
-		const int *constant = data.constant_override.getptr(p_name);
+		const int *constant = data.theme_constant_override.getptr(p_name);
 		if (constant) {
 			return *constant;
 		}
@@ -2570,123 +2570,123 @@ bool Control::has_theme_constant(const StringName &p_name, const StringName &p_t
 void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) {
 	ERR_FAIL_COND(!p_icon.is_valid());
 
-	if (data.icon_override.has(p_name)) {
-		data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+	if (data.theme_icon_override.has(p_name)) {
+		data.theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
-	data.icon_override[p_name] = p_icon;
-	data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
+	data.theme_icon_override[p_name] = p_icon;
+	data.theme_icon_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
 	_notify_theme_override_changed();
 }
 
 void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) {
 	ERR_FAIL_COND(!p_style.is_valid());
 
-	if (data.style_override.has(p_name)) {
-		data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+	if (data.theme_style_override.has(p_name)) {
+		data.theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
-	data.style_override[p_name] = p_style;
-	data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
+	data.theme_style_override[p_name] = p_style;
+	data.theme_style_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
 	_notify_theme_override_changed();
 }
 
 void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) {
 	ERR_FAIL_COND(!p_font.is_valid());
 
-	if (data.font_override.has(p_name)) {
-		data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+	if (data.theme_font_override.has(p_name)) {
+		data.theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
-	data.font_override[p_name] = p_font;
-	data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
+	data.theme_font_override[p_name] = p_font;
+	data.theme_font_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
 	_notify_theme_override_changed();
 }
 
 void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) {
-	data.font_size_override[p_name] = p_font_size;
+	data.theme_font_size_override[p_name] = p_font_size;
 	_notify_theme_override_changed();
 }
 
 void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) {
-	data.color_override[p_name] = p_color;
+	data.theme_color_override[p_name] = p_color;
 	_notify_theme_override_changed();
 }
 
 void Control::add_theme_constant_override(const StringName &p_name, int p_constant) {
-	data.constant_override[p_name] = p_constant;
+	data.theme_constant_override[p_name] = p_constant;
 	_notify_theme_override_changed();
 }
 
 void Control::remove_theme_icon_override(const StringName &p_name) {
-	if (data.icon_override.has(p_name)) {
-		data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+	if (data.theme_icon_override.has(p_name)) {
+		data.theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
-	data.icon_override.erase(p_name);
+	data.theme_icon_override.erase(p_name);
 	_notify_theme_override_changed();
 }
 
 void Control::remove_theme_style_override(const StringName &p_name) {
-	if (data.style_override.has(p_name)) {
-		data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+	if (data.theme_style_override.has(p_name)) {
+		data.theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
-	data.style_override.erase(p_name);
+	data.theme_style_override.erase(p_name);
 	_notify_theme_override_changed();
 }
 
 void Control::remove_theme_font_override(const StringName &p_name) {
-	if (data.font_override.has(p_name)) {
-		data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
+	if (data.theme_font_override.has(p_name)) {
+		data.theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
-	data.font_override.erase(p_name);
+	data.theme_font_override.erase(p_name);
 	_notify_theme_override_changed();
 }
 
 void Control::remove_theme_font_size_override(const StringName &p_name) {
-	data.font_size_override.erase(p_name);
+	data.theme_font_size_override.erase(p_name);
 	_notify_theme_override_changed();
 }
 
 void Control::remove_theme_color_override(const StringName &p_name) {
-	data.color_override.erase(p_name);
+	data.theme_color_override.erase(p_name);
 	_notify_theme_override_changed();
 }
 
 void Control::remove_theme_constant_override(const StringName &p_name) {
-	data.constant_override.erase(p_name);
+	data.theme_constant_override.erase(p_name);
 	_notify_theme_override_changed();
 }
 
 bool Control::has_theme_icon_override(const StringName &p_name) const {
-	const Ref<Texture2D> *tex = data.icon_override.getptr(p_name);
+	const Ref<Texture2D> *tex = data.theme_icon_override.getptr(p_name);
 	return tex != nullptr;
 }
 
 bool Control::has_theme_stylebox_override(const StringName &p_name) const {
-	const Ref<StyleBox> *style = data.style_override.getptr(p_name);
+	const Ref<StyleBox> *style = data.theme_style_override.getptr(p_name);
 	return style != nullptr;
 }
 
 bool Control::has_theme_font_override(const StringName &p_name) const {
-	const Ref<Font> *font = data.font_override.getptr(p_name);
+	const Ref<Font> *font = data.theme_font_override.getptr(p_name);
 	return font != nullptr;
 }
 
 bool Control::has_theme_font_size_override(const StringName &p_name) const {
-	const int *font_size = data.font_size_override.getptr(p_name);
+	const int *font_size = data.theme_font_size_override.getptr(p_name);
 	return font_size != nullptr;
 }
 
 bool Control::has_theme_color_override(const StringName &p_name) const {
-	const Color *color = data.color_override.getptr(p_name);
+	const Color *color = data.theme_color_override.getptr(p_name);
 	return color != nullptr;
 }
 
 bool Control::has_theme_constant_override(const StringName &p_name) const {
-	const int *constant = data.constant_override.getptr(p_name);
+	const int *constant = data.theme_constant_override.getptr(p_name);
 	return constant != nullptr;
 }
 
@@ -3359,21 +3359,21 @@ Control::~Control() {
 	memdelete(data.theme_owner);
 
 	// Resources need to be disconnected.
-	for (KeyValue<StringName, Ref<Texture2D>> &E : data.icon_override) {
+	for (KeyValue<StringName, Ref<Texture2D>> &E : data.theme_icon_override) {
 		E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
-	for (KeyValue<StringName, Ref<StyleBox>> &E : data.style_override) {
+	for (KeyValue<StringName, Ref<StyleBox>> &E : data.theme_style_override) {
 		E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
-	for (KeyValue<StringName, Ref<Font>> &E : data.font_override) {
+	for (KeyValue<StringName, Ref<Font>> &E : data.theme_font_override) {
 		E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed));
 	}
 
 	// Then override maps can be simply cleared.
-	data.icon_override.clear();
-	data.style_override.clear();
-	data.font_override.clear();
-	data.font_size_override.clear();
-	data.color_override.clear();
-	data.constant_override.clear();
+	data.theme_icon_override.clear();
+	data.theme_style_override.clear();
+	data.theme_font_override.clear();
+	data.theme_font_size_override.clear();
+	data.theme_color_override.clear();
+	data.theme_constant_override.clear();
 }

+ 6 - 6
scene/gui/control.h

@@ -228,12 +228,12 @@ private:
 		StringName theme_type_variation;
 
 		bool bulk_theme_override = false;
-		Theme::ThemeIconMap icon_override;
-		Theme::ThemeStyleMap style_override;
-		Theme::ThemeFontMap font_override;
-		Theme::ThemeFontSizeMap font_size_override;
-		Theme::ThemeColorMap color_override;
-		Theme::ThemeConstantMap constant_override;
+		Theme::ThemeIconMap theme_icon_override;
+		Theme::ThemeStyleMap theme_style_override;
+		Theme::ThemeFontMap theme_font_override;
+		Theme::ThemeFontSizeMap theme_font_size_override;
+		Theme::ThemeColorMap theme_color_override;
+		Theme::ThemeConstantMap theme_constant_override;
 
 		mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache;
 		mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache;

+ 491 - 31
scene/main/window.cpp

@@ -40,6 +40,218 @@
 #include "scene/theme/theme_db.h"
 #include "scene/theme/theme_owner.h"
 
+// Dynamic properties.
+
+bool Window::_set(const StringName &p_name, const Variant &p_value) {
+	String name = p_name;
+	if (!name.begins_with("theme_override")) {
+		return false;
+	}
+
+	if (p_value.get_type() == Variant::NIL || (p_value.get_type() == Variant::OBJECT && (Object *)p_value == nullptr)) {
+		if (name.begins_with("theme_override_icons/")) {
+			String dname = name.get_slicec('/', 1);
+			if (theme_icon_override.has(dname)) {
+				theme_icon_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+			}
+			theme_icon_override.erase(dname);
+			_notify_theme_override_changed();
+		} else if (name.begins_with("theme_override_styles/")) {
+			String dname = name.get_slicec('/', 1);
+			if (theme_style_override.has(dname)) {
+				theme_style_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+			}
+			theme_style_override.erase(dname);
+			_notify_theme_override_changed();
+		} else if (name.begins_with("theme_override_fonts/")) {
+			String dname = name.get_slicec('/', 1);
+			if (theme_font_override.has(dname)) {
+				theme_font_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+			}
+			theme_font_override.erase(dname);
+			_notify_theme_override_changed();
+		} else if (name.begins_with("theme_override_font_sizes/")) {
+			String dname = name.get_slicec('/', 1);
+			theme_font_size_override.erase(dname);
+			_notify_theme_override_changed();
+		} else if (name.begins_with("theme_override_colors/")) {
+			String dname = name.get_slicec('/', 1);
+			theme_color_override.erase(dname);
+			_notify_theme_override_changed();
+		} else if (name.begins_with("theme_override_constants/")) {
+			String dname = name.get_slicec('/', 1);
+			theme_constant_override.erase(dname);
+			_notify_theme_override_changed();
+		} else {
+			return false;
+		}
+
+	} else {
+		if (name.begins_with("theme_override_icons/")) {
+			String dname = name.get_slicec('/', 1);
+			add_theme_icon_override(dname, p_value);
+		} else if (name.begins_with("theme_override_styles/")) {
+			String dname = name.get_slicec('/', 1);
+			add_theme_style_override(dname, p_value);
+		} else if (name.begins_with("theme_override_fonts/")) {
+			String dname = name.get_slicec('/', 1);
+			add_theme_font_override(dname, p_value);
+		} else if (name.begins_with("theme_override_font_sizes/")) {
+			String dname = name.get_slicec('/', 1);
+			add_theme_font_size_override(dname, p_value);
+		} else if (name.begins_with("theme_override_colors/")) {
+			String dname = name.get_slicec('/', 1);
+			add_theme_color_override(dname, p_value);
+		} else if (name.begins_with("theme_override_constants/")) {
+			String dname = name.get_slicec('/', 1);
+			add_theme_constant_override(dname, p_value);
+		} else {
+			return false;
+		}
+	}
+	return true;
+}
+
+bool Window::_get(const StringName &p_name, Variant &r_ret) const {
+	String sname = p_name;
+	if (!sname.begins_with("theme_override")) {
+		return false;
+	}
+
+	if (sname.begins_with("theme_override_icons/")) {
+		String name = sname.get_slicec('/', 1);
+		r_ret = theme_icon_override.has(name) ? Variant(theme_icon_override[name]) : Variant();
+	} else if (sname.begins_with("theme_override_styles/")) {
+		String name = sname.get_slicec('/', 1);
+		r_ret = theme_style_override.has(name) ? Variant(theme_style_override[name]) : Variant();
+	} else if (sname.begins_with("theme_override_fonts/")) {
+		String name = sname.get_slicec('/', 1);
+		r_ret = theme_font_override.has(name) ? Variant(theme_font_override[name]) : Variant();
+	} else if (sname.begins_with("theme_override_font_sizes/")) {
+		String name = sname.get_slicec('/', 1);
+		r_ret = theme_font_size_override.has(name) ? Variant(theme_font_size_override[name]) : Variant();
+	} else if (sname.begins_with("theme_override_colors/")) {
+		String name = sname.get_slicec('/', 1);
+		r_ret = theme_color_override.has(name) ? Variant(theme_color_override[name]) : Variant();
+	} else if (sname.begins_with("theme_override_constants/")) {
+		String name = sname.get_slicec('/', 1);
+		r_ret = theme_constant_override.has(name) ? Variant(theme_constant_override[name]) : Variant();
+	} else {
+		return false;
+	}
+
+	return true;
+}
+
+void Window::_get_property_list(List<PropertyInfo> *p_list) const {
+	Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme();
+
+	p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP));
+
+	{
+		List<StringName> names;
+		default_theme->get_color_list(get_class_name(), &names);
+		for (const StringName &E : names) {
+			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+			if (theme_color_override.has(E)) {
+				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+			}
+
+			p_list->push_back(PropertyInfo(Variant::COLOR, "theme_override_colors/" + E, PROPERTY_HINT_NONE, "", usage));
+		}
+	}
+	{
+		List<StringName> names;
+		default_theme->get_constant_list(get_class_name(), &names);
+		for (const StringName &E : names) {
+			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+			if (theme_constant_override.has(E)) {
+				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+			}
+
+			p_list->push_back(PropertyInfo(Variant::INT, "theme_override_constants/" + E, PROPERTY_HINT_RANGE, "-16384,16384", usage));
+		}
+	}
+	{
+		List<StringName> names;
+		default_theme->get_font_list(get_class_name(), &names);
+		for (const StringName &E : names) {
+			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+			if (theme_font_override.has(E)) {
+				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+			}
+
+			p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_fonts/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage));
+		}
+	}
+	{
+		List<StringName> names;
+		default_theme->get_font_size_list(get_class_name(), &names);
+		for (const StringName &E : names) {
+			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+			if (theme_font_size_override.has(E)) {
+				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+			}
+
+			p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage));
+		}
+	}
+	{
+		List<StringName> names;
+		default_theme->get_icon_list(get_class_name(), &names);
+		for (const StringName &E : names) {
+			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+			if (theme_icon_override.has(E)) {
+				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+			}
+
+			p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_icons/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage));
+		}
+	}
+	{
+		List<StringName> names;
+		default_theme->get_stylebox_list(get_class_name(), &names);
+		for (const StringName &E : names) {
+			uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE;
+			if (theme_style_override.has(E)) {
+				usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED;
+			}
+
+			p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_styles/" + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage));
+		}
+	}
+}
+
+void Window::_validate_property(PropertyInfo &p_property) const {
+	if (p_property.name == "theme_type_variation") {
+		List<StringName> names;
+
+		// Only the default theme and the project theme are used for the list of options.
+		// This is an imposed limitation to simplify the logic needed to leverage those options.
+		ThemeDB::get_singleton()->get_default_theme()->get_type_variation_list(get_class_name(), &names);
+		if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
+			ThemeDB::get_singleton()->get_project_theme()->get_type_variation_list(get_class_name(), &names);
+		}
+		names.sort_custom<StringName::AlphCompare>();
+
+		Vector<StringName> unique_names;
+		String hint_string;
+		for (const StringName &E : names) {
+			// Skip duplicate values.
+			if (unique_names.has(E)) {
+				continue;
+			}
+
+			hint_string += String(E) + ",";
+			unique_names.append(E);
+		}
+
+		p_property.hint_string = hint_string;
+	}
+}
+
+//
+
 void Window::set_title(const String &p_title) {
 	title = p_title;
 
@@ -1323,6 +1535,8 @@ void Window::remove_child_notify(Node *p_child) {
 	}
 }
 
+// Theming.
+
 void Window::set_theme_owner_node(Node *p_node) {
 	theme_owner->set_owner_node(p_node);
 }
@@ -1376,6 +1590,12 @@ void Window::_theme_changed() {
 	}
 }
 
+void Window::_notify_theme_override_changed() {
+	if (!bulk_theme_override && is_inside_tree()) {
+		notification(NOTIFICATION_THEME_CHANGED);
+	}
+}
+
 void Window::_invalidate_theme_cache() {
 	theme_icon_cache.clear();
 	theme_style_cache.clear();
@@ -1386,6 +1606,9 @@ void Window::_invalidate_theme_cache() {
 }
 
 void Window::_update_theme_item_cache() {
+	// Request an update on the next frame to reflect theme changes.
+	// Updating without a delay can cause a lot of lag.
+	child_controls_changed();
 }
 
 void Window::set_theme_type_variation(const StringName &p_theme_type) {
@@ -1399,7 +1622,16 @@ StringName Window::get_theme_type_variation() const {
 	return theme_type_variation;
 }
 
+/// Theme property lookup.
+
 Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		const Ref<Texture2D> *tex = theme_icon_override.getptr(p_name);
+		if (tex) {
+			return *tex;
+		}
+	}
+
 	if (theme_icon_cache.has(p_theme_type) && theme_icon_cache[p_theme_type].has(p_name)) {
 		return theme_icon_cache[p_theme_type][p_name];
 	}
@@ -1412,6 +1644,13 @@ Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName
 }
 
 Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		const Ref<StyleBox> *style = theme_style_override.getptr(p_name);
+		if (style) {
+			return *style;
+		}
+	}
+
 	if (theme_style_cache.has(p_theme_type) && theme_style_cache[p_theme_type].has(p_name)) {
 		return theme_style_cache[p_theme_type][p_name];
 	}
@@ -1424,6 +1663,13 @@ Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringN
 }
 
 Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		const Ref<Font> *font = theme_font_override.getptr(p_name);
+		if (font) {
+			return *font;
+		}
+	}
+
 	if (theme_font_cache.has(p_theme_type) && theme_font_cache[p_theme_type].has(p_name)) {
 		return theme_font_cache[p_theme_type][p_name];
 	}
@@ -1436,6 +1682,13 @@ Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_t
 }
 
 int Window::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		const int *font_size = theme_font_size_override.getptr(p_name);
+		if (font_size && (*font_size) > 0) {
+			return *font_size;
+		}
+	}
+
 	if (theme_font_size_cache.has(p_theme_type) && theme_font_size_cache[p_theme_type].has(p_name)) {
 		return theme_font_size_cache[p_theme_type][p_name];
 	}
@@ -1448,6 +1701,13 @@ int Window::get_theme_font_size(const StringName &p_name, const StringName &p_th
 }
 
 Color Window::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		const Color *color = theme_color_override.getptr(p_name);
+		if (color) {
+			return *color;
+		}
+	}
+
 	if (theme_color_cache.has(p_theme_type) && theme_color_cache[p_theme_type].has(p_name)) {
 		return theme_color_cache[p_theme_type][p_name];
 	}
@@ -1460,6 +1720,13 @@ Color Window::get_theme_color(const StringName &p_name, const StringName &p_them
 }
 
 int Window::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		const int *constant = theme_constant_override.getptr(p_name);
+		if (constant) {
+			return *constant;
+		}
+	}
+
 	if (theme_constant_cache.has(p_theme_type) && theme_constant_cache[p_theme_type].has(p_name)) {
 		return theme_constant_cache[p_theme_type][p_name];
 	}
@@ -1472,41 +1739,204 @@ int Window::get_theme_constant(const StringName &p_name, const StringName &p_the
 }
 
 bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		if (has_theme_icon_override(p_name)) {
+			return true;
+		}
+	}
+
 	List<StringName> theme_types;
 	theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types);
 	return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types);
 }
 
 bool Window::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		if (has_theme_stylebox_override(p_name)) {
+			return true;
+		}
+	}
+
 	List<StringName> theme_types;
 	theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types);
 	return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types);
 }
 
 bool Window::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		if (has_theme_font_override(p_name)) {
+			return true;
+		}
+	}
+
 	List<StringName> theme_types;
 	theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types);
 	return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types);
 }
 
 bool Window::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		if (has_theme_font_size_override(p_name)) {
+			return true;
+		}
+	}
+
 	List<StringName> theme_types;
 	theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types);
 	return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types);
 }
 
 bool Window::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		if (has_theme_color_override(p_name)) {
+			return true;
+		}
+	}
+
 	List<StringName> theme_types;
 	theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types);
 	return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types);
 }
 
 bool Window::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const {
+	if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) {
+		if (has_theme_constant_override(p_name)) {
+			return true;
+		}
+	}
+
 	List<StringName> theme_types;
 	theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types);
 	return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types);
 }
 
+/// Local property overrides.
+
+void Window::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) {
+	ERR_FAIL_COND(!p_icon.is_valid());
+
+	if (theme_icon_override.has(p_name)) {
+		theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	theme_icon_override[p_name] = p_icon;
+	theme_icon_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
+	_notify_theme_override_changed();
+}
+
+void Window::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) {
+	ERR_FAIL_COND(!p_style.is_valid());
+
+	if (theme_style_override.has(p_name)) {
+		theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	theme_style_override[p_name] = p_style;
+	theme_style_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
+	_notify_theme_override_changed();
+}
+
+void Window::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) {
+	ERR_FAIL_COND(!p_font.is_valid());
+
+	if (theme_font_override.has(p_name)) {
+		theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	theme_font_override[p_name] = p_font;
+	theme_font_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED);
+	_notify_theme_override_changed();
+}
+
+void Window::add_theme_font_size_override(const StringName &p_name, int p_font_size) {
+	theme_font_size_override[p_name] = p_font_size;
+	_notify_theme_override_changed();
+}
+
+void Window::add_theme_color_override(const StringName &p_name, const Color &p_color) {
+	theme_color_override[p_name] = p_color;
+	_notify_theme_override_changed();
+}
+
+void Window::add_theme_constant_override(const StringName &p_name, int p_constant) {
+	theme_constant_override[p_name] = p_constant;
+	_notify_theme_override_changed();
+}
+
+void Window::remove_theme_icon_override(const StringName &p_name) {
+	if (theme_icon_override.has(p_name)) {
+		theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	theme_icon_override.erase(p_name);
+	_notify_theme_override_changed();
+}
+
+void Window::remove_theme_style_override(const StringName &p_name) {
+	if (theme_style_override.has(p_name)) {
+		theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	theme_style_override.erase(p_name);
+	_notify_theme_override_changed();
+}
+
+void Window::remove_theme_font_override(const StringName &p_name) {
+	if (theme_font_override.has(p_name)) {
+		theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	theme_font_override.erase(p_name);
+	_notify_theme_override_changed();
+}
+
+void Window::remove_theme_font_size_override(const StringName &p_name) {
+	theme_font_size_override.erase(p_name);
+	_notify_theme_override_changed();
+}
+
+void Window::remove_theme_color_override(const StringName &p_name) {
+	theme_color_override.erase(p_name);
+	_notify_theme_override_changed();
+}
+
+void Window::remove_theme_constant_override(const StringName &p_name) {
+	theme_constant_override.erase(p_name);
+	_notify_theme_override_changed();
+}
+
+bool Window::has_theme_icon_override(const StringName &p_name) const {
+	const Ref<Texture2D> *tex = theme_icon_override.getptr(p_name);
+	return tex != nullptr;
+}
+
+bool Window::has_theme_stylebox_override(const StringName &p_name) const {
+	const Ref<StyleBox> *style = theme_style_override.getptr(p_name);
+	return style != nullptr;
+}
+
+bool Window::has_theme_font_override(const StringName &p_name) const {
+	const Ref<Font> *font = theme_font_override.getptr(p_name);
+	return font != nullptr;
+}
+
+bool Window::has_theme_font_size_override(const StringName &p_name) const {
+	const int *font_size = theme_font_size_override.getptr(p_name);
+	return font_size != nullptr;
+}
+
+bool Window::has_theme_color_override(const StringName &p_name) const {
+	const Color *color = theme_color_override.getptr(p_name);
+	return color != nullptr;
+}
+
+bool Window::has_theme_constant_override(const StringName &p_name) const {
+	const int *constant = theme_constant_override.getptr(p_name);
+	return constant != nullptr;
+}
+
+/// Default theme properties.
+
 float Window::get_theme_default_base_scale() const {
 	return theme_owner->get_theme_default_base_scale();
 }
@@ -1519,6 +1949,21 @@ int Window::get_theme_default_font_size() const {
 	return theme_owner->get_theme_default_font_size();
 }
 
+/// Bulk actions.
+
+void Window::begin_bulk_theme_override() {
+	bulk_theme_override = true;
+}
+
+void Window::end_bulk_theme_override() {
+	ERR_FAIL_COND(!bulk_theme_override);
+
+	bulk_theme_override = false;
+	_notify_theme_override_changed();
+}
+
+//
+
 Rect2i Window::get_parent_rect() const {
 	ERR_FAIL_COND_V(!is_inside_tree(), Rect2i());
 	if (is_embedded()) {
@@ -1611,34 +2056,6 @@ bool Window::is_auto_translating() const {
 	return auto_translate;
 }
 
-void Window::_validate_property(PropertyInfo &p_property) const {
-	if (p_property.name == "theme_type_variation") {
-		List<StringName> names;
-
-		// Only the default theme and the project theme are used for the list of options.
-		// This is an imposed limitation to simplify the logic needed to leverage those options.
-		ThemeDB::get_singleton()->get_default_theme()->get_type_variation_list(get_class_name(), &names);
-		if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
-			ThemeDB::get_singleton()->get_project_theme()->get_type_variation_list(get_class_name(), &names);
-		}
-		names.sort_custom<StringName::AlphCompare>();
-
-		Vector<StringName> unique_names;
-		String hint_string;
-		for (const StringName &E : names) {
-			// Skip duplicate values.
-			if (unique_names.has(E)) {
-				continue;
-			}
-
-			hint_string += String(E) + ",";
-			unique_names.append(E);
-		}
-
-		p_property.hint_string = hint_string;
-	}
-}
-
 Transform2D Window::get_screen_transform() const {
 	Transform2D embedder_transform;
 	if (_get_embedder()) {
@@ -1733,6 +2150,23 @@ void Window::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_theme_type_variation", "theme_type"), &Window::set_theme_type_variation);
 	ClassDB::bind_method(D_METHOD("get_theme_type_variation"), &Window::get_theme_type_variation);
 
+	ClassDB::bind_method(D_METHOD("begin_bulk_theme_override"), &Window::begin_bulk_theme_override);
+	ClassDB::bind_method(D_METHOD("end_bulk_theme_override"), &Window::end_bulk_theme_override);
+
+	ClassDB::bind_method(D_METHOD("add_theme_icon_override", "name", "texture"), &Window::add_theme_icon_override);
+	ClassDB::bind_method(D_METHOD("add_theme_stylebox_override", "name", "stylebox"), &Window::add_theme_style_override);
+	ClassDB::bind_method(D_METHOD("add_theme_font_override", "name", "font"), &Window::add_theme_font_override);
+	ClassDB::bind_method(D_METHOD("add_theme_font_size_override", "name", "font_size"), &Window::add_theme_font_size_override);
+	ClassDB::bind_method(D_METHOD("add_theme_color_override", "name", "color"), &Window::add_theme_color_override);
+	ClassDB::bind_method(D_METHOD("add_theme_constant_override", "name", "constant"), &Window::add_theme_constant_override);
+
+	ClassDB::bind_method(D_METHOD("remove_theme_icon_override", "name"), &Window::remove_theme_icon_override);
+	ClassDB::bind_method(D_METHOD("remove_theme_stylebox_override", "name"), &Window::remove_theme_style_override);
+	ClassDB::bind_method(D_METHOD("remove_theme_font_override", "name"), &Window::remove_theme_font_override);
+	ClassDB::bind_method(D_METHOD("remove_theme_font_size_override", "name"), &Window::remove_theme_font_size_override);
+	ClassDB::bind_method(D_METHOD("remove_theme_color_override", "name"), &Window::remove_theme_color_override);
+	ClassDB::bind_method(D_METHOD("remove_theme_constant_override", "name"), &Window::remove_theme_constant_override);
+
 	ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Window::get_theme_icon, DEFVAL(""));
 	ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Window::get_theme_stylebox, DEFVAL(""));
 	ClassDB::bind_method(D_METHOD("get_theme_font", "name", "theme_type"), &Window::get_theme_font, DEFVAL(""));
@@ -1740,6 +2174,13 @@ void Window::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_theme_color", "name", "theme_type"), &Window::get_theme_color, DEFVAL(""));
 	ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Window::get_theme_constant, DEFVAL(""));
 
+	ClassDB::bind_method(D_METHOD("has_theme_icon_override", "name"), &Window::has_theme_icon_override);
+	ClassDB::bind_method(D_METHOD("has_theme_stylebox_override", "name"), &Window::has_theme_stylebox_override);
+	ClassDB::bind_method(D_METHOD("has_theme_font_override", "name"), &Window::has_theme_font_override);
+	ClassDB::bind_method(D_METHOD("has_theme_font_size_override", "name"), &Window::has_theme_font_size_override);
+	ClassDB::bind_method(D_METHOD("has_theme_color_override", "name"), &Window::has_theme_color_override);
+	ClassDB::bind_method(D_METHOD("has_theme_constant_override", "name"), &Window::has_theme_constant_override);
+
 	ClassDB::bind_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Window::has_theme_icon, DEFVAL(""));
 	ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Window::has_theme_stylebox, DEFVAL(""));
 	ClassDB::bind_method(D_METHOD("has_theme_font", "name", "theme_type"), &Window::has_theme_font, DEFVAL(""));
@@ -1793,13 +2234,13 @@ void Window::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_aspect", PROPERTY_HINT_ENUM, "Ignore,Keep,Keep Width,Keep Height,Expand"), "set_content_scale_aspect", "get_content_scale_aspect");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor"), "set_content_scale_factor", "get_content_scale_factor");
 
+	ADD_GROUP("Localization", "");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating");
+
 	ADD_GROUP("Theme", "theme_");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation");
 
-	ADD_GROUP("Auto Translate", "");
-	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating");
-
 	ADD_SIGNAL(MethodInfo("window_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
 	ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files")));
 	ADD_SIGNAL(MethodInfo("mouse_entered"));
@@ -1859,4 +2300,23 @@ Window::Window() {
 
 Window::~Window() {
 	memdelete(theme_owner);
+
+	// Resources need to be disconnected.
+	for (KeyValue<StringName, Ref<Texture2D>> &E : theme_icon_override) {
+		E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+	for (KeyValue<StringName, Ref<StyleBox>> &E : theme_style_override) {
+		E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+	for (KeyValue<StringName, Ref<Font>> &E : theme_font_override) {
+		E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed));
+	}
+
+	// Then override maps can be simply cleared.
+	theme_icon_override.clear();
+	theme_style_override.clear();
+	theme_font_override.clear();
+	theme_font_size_override.clear();
+	theme_color_override.clear();
+	theme_constant_override.clear();
 }

+ 51 - 10
scene/main/window.h

@@ -140,6 +140,14 @@ private:
 	Ref<Theme> theme;
 	StringName theme_type_variation;
 
+	bool bulk_theme_override = false;
+	Theme::ThemeIconMap theme_icon_override;
+	Theme::ThemeStyleMap theme_style_override;
+	Theme::ThemeFontMap theme_font_override;
+	Theme::ThemeFontSizeMap theme_font_size_override;
+	Theme::ThemeColorMap theme_color_override;
+	Theme::ThemeConstantMap theme_constant_override;
+
 	mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache;
 	mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache;
 	mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache;
@@ -148,6 +156,7 @@ private:
 	mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache;
 
 	void _theme_changed();
+	void _notify_theme_override_changed();
 	void _invalidate_theme_cache();
 
 	Viewport *embedder = nullptr;
@@ -173,6 +182,10 @@ protected:
 	virtual Size2 _get_contents_minimum_size() const;
 	static void _bind_methods();
 	void _notification(int p_what);
+
+	bool _set(const StringName &p_name, const Variant &p_value);
+	bool _get(const StringName &p_name, Variant &r_ret) const;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
 	void _validate_property(PropertyInfo &p_property) const;
 
 	virtual void add_child_notify(Node *p_child) override;
@@ -271,16 +284,6 @@ public:
 	void popup_centered(const Size2i &p_minsize = Size2i());
 	void popup_centered_clamped(const Size2i &p_size = Size2i(), float p_fallback_ratio = 0.75);
 
-	void set_theme_owner_node(Node *p_node);
-	Node *get_theme_owner_node() const;
-	bool has_theme_owner_node() const;
-
-	void set_theme(const Ref<Theme> &p_theme);
-	Ref<Theme> get_theme() const;
-
-	void set_theme_type_variation(const StringName &p_theme_type);
-	StringName get_theme_type_variation() const;
-
 	Size2 get_contents_minimum_size() const;
 
 	void grab_focus();
@@ -296,6 +299,35 @@ public:
 
 	Rect2i get_usable_parent_rect() const;
 
+	// Theming.
+
+	void set_theme_owner_node(Node *p_node);
+	Node *get_theme_owner_node() const;
+	bool has_theme_owner_node() const;
+
+	void set_theme(const Ref<Theme> &p_theme);
+	Ref<Theme> get_theme() const;
+
+	void set_theme_type_variation(const StringName &p_theme_type);
+	StringName get_theme_type_variation() const;
+
+	void begin_bulk_theme_override();
+	void end_bulk_theme_override();
+
+	void add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon);
+	void add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style);
+	void add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font);
+	void add_theme_font_size_override(const StringName &p_name, int p_font_size);
+	void add_theme_color_override(const StringName &p_name, const Color &p_color);
+	void add_theme_constant_override(const StringName &p_name, int p_constant);
+
+	void remove_theme_icon_override(const StringName &p_name);
+	void remove_theme_style_override(const StringName &p_name);
+	void remove_theme_font_override(const StringName &p_name);
+	void remove_theme_font_size_override(const StringName &p_name);
+	void remove_theme_color_override(const StringName &p_name);
+	void remove_theme_constant_override(const StringName &p_name);
+
 	Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
 	Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
 	Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
@@ -303,6 +335,13 @@ public:
 	Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
 	int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
 
+	bool has_theme_icon_override(const StringName &p_name) const;
+	bool has_theme_stylebox_override(const StringName &p_name) const;
+	bool has_theme_font_override(const StringName &p_name) const;
+	bool has_theme_font_size_override(const StringName &p_name) const;
+	bool has_theme_color_override(const StringName &p_name) const;
+	bool has_theme_constant_override(const StringName &p_name) const;
+
 	bool has_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
 	bool has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
 	bool has_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
@@ -314,6 +353,8 @@ public:
 	Ref<Font> get_theme_default_font() const;
 	int get_theme_default_font_size() const;
 
+	//
+
 	virtual Transform2D get_screen_transform() const override;
 
 	Rect2i get_parent_rect() const;