extends RefCounted static var BACKGROUND := Color("121827") static var SURFACE := Color("1d2638") static var SURFACE_DARK := Color("151c2b") static var SURFACE_LIGHT := Color("27334a") static var TEXT := Color("f6f8ff") static var MUTED := Color("aeb8ca") static var CYAN := Color("4de1ff") static var LIME := Color("a6ff5f") static var MAGENTA := Color("ff4fd8") static var AMBER := Color("ffc857") static var RED := Color("ff5f6d") static var BORDER := Color("33415c") static var is_dark := true const DARK_PALETTE := { "background": Color("121827"), "surface": Color("1d2638"), "surface_dark": Color("151c2b"), "surface_light": Color("27334a"), "text": Color("f6f8ff"), "muted": Color("aeb8ca"), "cyan": Color("4de1ff"), "lime": Color("a6ff5f"), "magenta": Color("ff4fd8"), "amber": Color("ffc857"), "red": Color("ff5f6d"), "border": Color("33415c") } const LIGHT_PALETTE := { "background": Color("f4f7fb"), "surface": Color("ffffff"), "surface_dark": Color("e8edf5"), "surface_light": Color("dce5f0"), "text": Color("172033"), "muted": Color("59677c"), "cyan": Color("007a99"), "lime": Color("347a18"), "magenta": Color("a32187"), "amber": Color("986400"), "red": Color("b42335"), "border": Color("aeb9c8") } const CUSTOM_DARK_TO_LIGHT := { Color("111a2a"): Color("f8fafc"), Color("172337"): Color("fffffe"), Color("182235"): Color("fefeff"), Color("26151a"): Color("fff1f2"), Color("263752"): Color("aeb9c8"), Color("344463"): Color("aeb9c8"), Color("405475"): Color("96a5b8"), Color("3b4964"): Color("aeb9c8"), Color("303c55"): Color("b9c4d2"), Color("33425d"): Color("d2dce8"), Color("26354f"): Color("e1e8f1"), Color("20384d"): Color("d8e8ed"), Color("18465a"): Color("d4f2f7"), Color("20647e"): Color("b9e7ef"), Color("153b4d"): Color("c6e8ed"), Color("21344a"): Color("d8e8ed"), Color("202637"): Color("e5e9ef"), Color("2c3445"): Color("c8d0dc"), Color("232b3d"): Color("e1e7ef"), Color("303b53"): Color("b7c2d0"), Color("596982"): Color("66758a"), Color("1f7a3a"): Color("dff4e5"), Color("8f1d2c"): Color("f8dce1") } static func set_dark_mode(value: bool) -> void: is_dark = value var palette: Dictionary = DARK_PALETTE if value else LIGHT_PALETTE BACKGROUND = palette.background SURFACE = palette.surface SURFACE_DARK = palette.surface_dark SURFACE_LIGHT = palette.surface_light TEXT = palette.text MUTED = palette.muted CYAN = palette.cyan LIME = palette.lime MAGENTA = palette.magenta AMBER = palette.amber RED = palette.red BORDER = palette.border static func apply_app_theme(root: Control) -> void: var theme := Theme.new() theme.set_color("font_color", "Label", TEXT) theme.set_color("font_color", "Button", TEXT) theme.set_color("font_hover_color", "Button", TEXT) theme.set_color("font_pressed_color", "Button", TEXT) theme.set_color("font_disabled_color", "Button", MUTED) theme.set_color("default_color", "RichTextLabel", TEXT) theme.set_color("font_selected_color", "RichTextLabel", TEXT) theme.set_color("selection_color", "RichTextLabel", Color(CYAN, 0.28)) theme.set_font_size("font_size", "Label", 17) theme.set_font_size("font_size", "Button", 16) theme.set_stylebox("normal", "Button", button_box(SURFACE_LIGHT, Color("3b4964"))) theme.set_stylebox("hover", "Button", button_box(Color("33425d"), CYAN)) theme.set_stylebox("pressed", "Button", button_box(Color("21344a"), LIME)) theme.set_stylebox("disabled", "Button", button_box(Color("202637"), Color("2c3445"))) theme.set_stylebox("focus", "Button", StyleBoxEmpty.new()) root.theme = theme static func panel_box(color: Color = SURFACE, border: Color = BORDER) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = _color_for_current_theme(color) box.border_color = _color_for_current_theme(border) box.set_border_width_all(1) box.corner_radius_top_left = 8 box.corner_radius_top_right = 8 box.corner_radius_bottom_left = 8 box.corner_radius_bottom_right = 8 box.content_margin_left = 18 box.content_margin_right = 18 box.content_margin_top = 16 box.content_margin_bottom = 16 return box static func button_box(color: Color, border: Color) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = _color_for_current_theme(color) box.border_color = _color_for_current_theme(border) box.set_border_width_all(1) box.corner_radius_top_left = 6 box.corner_radius_top_right = 6 box.corner_radius_bottom_left = 6 box.corner_radius_bottom_right = 6 box.content_margin_left = 14 box.content_margin_right = 14 box.content_margin_top = 10 box.content_margin_bottom = 10 return box static func set_label(label: Label, font_size: int, color: Color = TEXT) -> void: label.add_theme_font_size_override("font_size", font_size) label.add_theme_color_override("font_color", color) static func style_primary_button(button: Button) -> void: button.add_theme_stylebox_override("normal", button_box(Color("18465a"), CYAN)) button.add_theme_stylebox_override("hover", button_box(Color("20647e"), Color("8df1ff"))) button.add_theme_stylebox_override("pressed", button_box(Color("153b4d"), LIME)) button.add_theme_color_override("font_color", TEXT) static func style_answer_button(button: Button) -> void: button.add_theme_stylebox_override("normal", button_box(SURFACE_DARK, Color("303c55"))) button.add_theme_stylebox_override("hover", button_box(Color("26354f"), CYAN)) button.add_theme_stylebox_override("pressed", button_box(Color("20384d"), CYAN)) button.add_theme_color_override("font_color", TEXT) button.add_theme_color_override("font_disabled_color", MUTED) static func style_chip(button: Button, color: Color) -> void: button.add_theme_stylebox_override("normal", button_box(Color(color, 0.22), color)) button.add_theme_stylebox_override("hover", button_box(Color(color, 0.34), color)) button.add_theme_color_override("font_color", TEXT) button.add_theme_color_override("font_disabled_color", MUTED) static func refresh_tree(root: Control, was_dark: bool) -> void: apply_app_theme(root) _translate_control(root, DARK_PALETTE if was_dark else LIGHT_PALETTE, LIGHT_PALETTE if was_dark else DARK_PALETTE) static func _translate_control(control: Control, source: Dictionary, target: Dictionary) -> void: if control is ColorRect: control.color = _translate_color(control.color, source, target) for color_name in ["font_color", "font_hover_color", "font_pressed_color", "font_disabled_color", "font_selected_color", "default_color", "font_uneditable_color"]: if control.has_theme_color_override(color_name): control.add_theme_color_override(color_name, _translate_color(control.get_theme_color(color_name), source, target)) for style_name in ["panel", "normal", "hover", "pressed", "disabled", "focus", "fill", "background", "read_only"]: if control.has_theme_stylebox_override(style_name): var box := control.get_theme_stylebox(style_name) if box is StyleBoxFlat: var translated := box.duplicate() as StyleBoxFlat translated.bg_color = _translate_color(translated.bg_color, source, target) translated.border_color = _translate_color(translated.border_color, source, target) control.add_theme_stylebox_override(style_name, translated) for child in control.get_children(): if child is Control: _translate_control(child, source, target) static func _translate_color(color: Color, source: Dictionary, target: Dictionary) -> Color: for key in source: var candidate: Color = source[key] if color.is_equal_approx(candidate): var replacement: Color = target[key] replacement.a = color.a return replacement for dark_color in CUSTOM_DARK_TO_LIGHT: if color.is_equal_approx(dark_color): var replacement: Color = CUSTOM_DARK_TO_LIGHT[dark_color] replacement.a = color.a return replacement for dark_color in CUSTOM_DARK_TO_LIGHT: var light_color: Color = CUSTOM_DARK_TO_LIGHT[dark_color] if color.is_equal_approx(light_color): var replacement: Color = dark_color replacement.a = color.a return replacement return color static func _color_for_current_theme(color: Color) -> Color: if is_dark: return color for dark_color in CUSTOM_DARK_TO_LIGHT: if color.is_equal_approx(dark_color): var replacement: Color = CUSTOM_DARK_TO_LIGHT[dark_color] replacement.a = color.a return replacement return color