├── app
├── scripts
│ ├── app.gd.uid
│ ├── url.gd.uid
│ ├── data_saver.gd.uid
│ ├── navigation.gd.uid
│ ├── platform.gd.uid
│ ├── afk_manager.gd.uid
│ ├── api
│ │ ├── backend.gd.uid
│ │ ├── discover_gate.gd.uid
│ │ ├── featured_gates.gd.uid
│ │ ├── analytics
│ │ │ ├── analytics.gd.uid
│ │ │ ├── analytics_events.gd.uid
│ │ │ ├── analytics_sender.gd.uid
│ │ │ ├── analytics_sender_afk.gd.uid
│ │ │ ├── analytics_sender_app.gd.uid
│ │ │ ├── analytics_sender_gate.gd.uid
│ │ │ ├── analytics_sender_link.gd.uid
│ │ │ ├── analytics_sender_bookmark.gd.uid
│ │ │ ├── analytics_sender_error.gd.uid
│ │ │ ├── analytics_sender_onboarding.gd.uid
│ │ │ ├── analytics_sender.gd
│ │ │ ├── analytics_sender_error.gd
│ │ │ ├── analytics_sender_link.gd
│ │ │ ├── analytics_sender_bookmark.gd
│ │ │ ├── analytics_sender_afk.gd
│ │ │ ├── analytics_sender_onboarding.gd
│ │ │ ├── analytics_sender_app.gd
│ │ │ ├── analytics.gd
│ │ │ └── analytics_sender_gate.gd
│ │ ├── discover_gate.gd
│ │ ├── featured_gates.gd
│ │ └── backend.gd
│ ├── bookmark_saver.gd.uid
│ ├── debug_log
│ │ ├── debug.gd.uid
│ │ ├── debug_log.gd.uid
│ │ ├── debug_window.gd.uid
│ │ ├── debug.gd
│ │ ├── debug_log.gd
│ │ └── debug_window.gd
│ ├── renderer
│ │ ├── unzip.gd.uid
│ │ ├── command_sync.gd.uid
│ │ ├── input_sync.gd.uid
│ │ ├── process_checker.gd.uid
│ │ ├── render_result.gd.uid
│ │ ├── renderer_logger.gd.uid
│ │ ├── renderer_manager.gd.uid
│ │ ├── renderer_executable.gd.uid
│ │ ├── unzip.gd
│ │ ├── process_checker.gd
│ │ ├── input_sync.gd
│ │ ├── renderer_manager.gd
│ │ └── command_sync.gd
│ ├── resources
│ │ ├── gate.gd.uid
│ │ ├── app_events.gd.uid
│ │ ├── bookmarks.gd.uid
│ │ ├── ui_events.gd.uid
│ │ ├── api_settings.gd.uid
│ │ ├── command_events.gd.uid
│ │ ├── gate_events.gd.uid
│ │ ├── app_events.gd
│ │ ├── command_events.gd
│ │ ├── gate.gd
│ │ ├── api_settings.gd
│ │ ├── ui_events.gd
│ │ └── bookmarks.gd
│ ├── string_tools.gd.uid
│ ├── ui
│ │ ├── menu
│ │ │ ├── history.gd.uid
│ │ │ ├── menu.gd.uid
│ │ │ ├── star.gd.uid
│ │ │ ├── bookmark_ui.gd.uid
│ │ │ ├── help_button.gd.uid
│ │ │ ├── round_button.gd.uid
│ │ │ ├── window_drag.gd.uid
│ │ │ ├── menu_navigation.gd.uid
│ │ │ ├── scroll_container.gd.uid
│ │ │ ├── window_buttons.gd.uid
│ │ │ ├── bookmark_container.gd.uid
│ │ │ ├── bookmark_jump_animation.gd.uid
│ │ │ ├── help_button.gd
│ │ │ ├── bookmark_container.gd
│ │ │ ├── menu_navigation.gd
│ │ │ ├── scroll_container.gd
│ │ │ ├── history.gd
│ │ │ ├── bookmark_jump_animation.gd
│ │ │ ├── window_buttons.gd
│ │ │ ├── round_button.gd
│ │ │ ├── star.gd
│ │ │ └── bookmark_ui.gd
│ │ ├── search
│ │ │ ├── search.gd.uid
│ │ │ ├── prompt.gd.uid
│ │ │ ├── result.gd.uid
│ │ │ ├── suggestion.gd.uid
│ │ │ ├── one_line_text.gd.uid
│ │ │ ├── open_meta_link.gd.uid
│ │ │ ├── prompt_results.gd.uid
│ │ │ ├── search_results.gd.uid
│ │ │ ├── search_status.gd.uid
│ │ │ ├── download_animation.gd.uid
│ │ │ ├── fix_promt_position.gd.uid
│ │ │ ├── prompt_navigation.gd.uid
│ │ │ ├── search_results_header.gd.uid
│ │ │ ├── one_line_text.gd
│ │ │ ├── open_meta_link.gd
│ │ │ ├── suggestion.gd
│ │ │ ├── search_results_header.gd
│ │ │ ├── download_animation.gd
│ │ │ ├── prompt.gd
│ │ │ ├── fix_promt_position.gd
│ │ │ ├── result.gd
│ │ │ ├── search_status.gd
│ │ │ ├── search.gd
│ │ │ ├── prompt_navigation.gd
│ │ │ └── search_results.gd
│ │ ├── onboarding
│ │ │ ├── board.gd.uid
│ │ │ ├── carousel.gd.uid
│ │ │ ├── close_button.gd.uid
│ │ │ ├── onboarding.gd.uid
│ │ │ ├── close_button.gd
│ │ │ ├── board.gd
│ │ │ └── onboarding.gd
│ │ ├── tabs
│ │ │ ├── tab_icon.gd.uid
│ │ │ └── tab_icon.gd
│ │ ├── ui_mode_animation.gd.uid
│ │ ├── world
│ │ │ ├── foreground.gd.uid
│ │ │ ├── gate_info.gd.uid
│ │ │ ├── world_canvas.gd.uid
│ │ │ ├── world_ui.gd.uid
│ │ │ ├── gate_description.gd.uid
│ │ │ ├── loading_status.gd.uid
│ │ │ ├── not_responding.gd.uid
│ │ │ ├── release_focus.gd.uid
│ │ │ ├── vignette_blur.gd.uid
│ │ │ ├── release_focus.gd
│ │ │ ├── gate_description.gd
│ │ │ ├── vignette_blur.gd
│ │ │ ├── world_canvas.gd
│ │ │ ├── foreground.gd
│ │ │ ├── gate_info.gd
│ │ │ └── not_responding.gd
│ │ ├── notification
│ │ │ ├── notification.gd.uid
│ │ │ ├── notifier_base.gd.uid
│ │ │ ├── notification_bar.gd.uid
│ │ │ ├── notification_manager.gd.uid
│ │ │ ├── notifier_mouse_captured.gd.uid
│ │ │ ├── notifier_base.gd
│ │ │ ├── notification_bar.gd
│ │ │ ├── notification_manager.gd
│ │ │ ├── notification.gd
│ │ │ └── notifier_mouse_captured.gd
│ │ └── ui_mode_animation.gd
│ ├── loading
│ │ ├── config_base.gd.uid
│ │ ├── config_gate.gd.uid
│ │ ├── file_tools.gd.uid
│ │ ├── gate_loader.gd.uid
│ │ ├── file_downloader.gd.uid
│ │ ├── file_tools.gd
│ │ ├── config_base.gd
│ │ └── config_gate.gd
│ ├── networking
│ │ ├── http_cache.gd.uid
│ │ ├── http_endpoint.gd.uid
│ │ ├── http_client_pool.gd.uid
│ │ ├── http_date_utils.gd.uid
│ │ ├── http_request_pooled.gd.uid
│ │ ├── http_pool_maintainer.gd.uid
│ │ ├── http_endpoint.gd
│ │ ├── http_request_pooled.gd
│ │ ├── http_pool_maintainer.gd
│ │ └── http_date_utils.gd
│ ├── data_saver.gd
│ ├── app.gd
│ ├── string_tools.gd
│ ├── navigation.gd
│ ├── platform.gd
│ ├── afk_manager.gd
│ ├── bookmark_saver.gd
│ └── url.gd
├── shaders
│ ├── render_result.gdshader.uid
│ ├── spinning_border.gdshader.uid
│ └── render_result.gdshader
├── addons
│ └── max_size_container
│ │ ├── plugin.gd.uid
│ │ ├── max_size_container.gd.uid
│ │ ├── icon.png
│ │ ├── plugin.cfg
│ │ ├── plugin.gd
│ │ └── icon.png.import
├── app_icon
│ ├── icon.ico
│ ├── icon_256.png
│ ├── toolbar_icon.ico
│ ├── README.md
│ ├── icon_256.png.import
│ └── icon.svg.import
├── .gitattributes
├── resources
│ ├── app_events.res
│ ├── gate_events.res
│ ├── ui_events.res
│ ├── command_events.res
│ ├── history.tres
│ ├── bookmarks.tres
│ ├── api_settings.tres
│ └── renderer_executable.tres
├── assets
│ ├── fonts
│ │ ├── Inter-Bold.otf
│ │ ├── Monospace.ttf
│ │ ├── Inter-Italic.otf
│ │ ├── Inter-Regular.otf
│ │ ├── MonospaceBold.ttf
│ │ ├── Inter-BoldItalic.otf
│ │ ├── LICENSE
│ │ ├── Inter-Bold.otf.import
│ │ ├── Monospace.ttf.import
│ │ ├── Inter-Italic.otf.import
│ │ ├── Inter-BoldItalic.otf.import
│ │ ├── Inter-Regular.otf.import
│ │ └── MonospaceBold.ttf.import
│ ├── styles
│ │ ├── tab.stylebox
│ │ ├── button.stylebox
│ │ ├── panel.stylebox
│ │ ├── prompt.stylebox
│ │ ├── button_hover.stylebox
│ │ ├── panel_hover.stylebox
│ │ ├── text.tres
│ │ ├── text_small.tres
│ │ └── text_big.tres
│ └── textures
│ │ ├── icon_round_16.png
│ │ ├── icon_round_32.png
│ │ ├── onboarding
│ │ ├── 2_friends.png
│ │ ├── 3_browser.png
│ │ ├── 1_internet.png
│ │ ├── 4_tutorial.png
│ │ ├── 2_friends.png.import
│ │ ├── 3_browser.png.import
│ │ ├── 1_internet.png.import
│ │ └── 4_tutorial.png.import
│ │ ├── minimize.svg
│ │ ├── gate.svg
│ │ ├── arrow_right.svg
│ │ ├── arrow_left.svg
│ │ ├── close.svg
│ │ ├── maximaze.svg
│ │ ├── plus.svg
│ │ ├── icon_round_16.png.import
│ │ ├── icon_round_32.png.import
│ │ ├── close_tab.svg
│ │ ├── search.svg
│ │ ├── search_96.svg
│ │ ├── home.svg
│ │ ├── clock.svg
│ │ ├── empty_icon.svg.import
│ │ ├── menu.svg
│ │ ├── reload.svg
│ │ ├── home.svg.import
│ │ ├── menu.svg.import
│ │ ├── flag.svg.import
│ │ ├── gate.svg.import
│ │ ├── help.svg.import
│ │ ├── plus.svg.import
│ │ ├── star.svg.import
│ │ ├── clock.svg.import
│ │ ├── close.svg.import
│ │ ├── reload.svg.import
│ │ ├── search.svg.import
│ │ ├── starred.svg.import
│ │ ├── maximaze.svg.import
│ │ ├── minimize.svg.import
│ │ ├── close_tab.svg.import
│ │ ├── search_96.svg.import
│ │ ├── star_color.svg.import
│ │ ├── arrow_left.svg.import
│ │ ├── icon_round.svg.import
│ │ ├── arrow_right.svg.import
│ │ ├── star.svg
│ │ ├── starred.svg
│ │ ├── cursor.svg.import
│ │ ├── empty_icon.svg
│ │ ├── cursor.svg
│ │ ├── help.svg
│ │ └── star_color.svg
├── .gitignore
└── scenes
│ ├── autoloads
│ ├── url.tscn
│ ├── navigation.tscn
│ └── http_client_pool.tscn
│ └── components
│ ├── notification
│ ├── notification_bar.tscn
│ └── notification.tscn
│ ├── search
│ ├── suggestion.tscn
│ └── prompt.tscn
│ └── round_button.tscn
├── screenshots
├── 1-home.png
├── 2-loading.png
└── 3-in-game-ui.png
├── .gitmodules
├── .gitignore
├── .vscode
└── settings.json
├── .cursorignore
├── README.md
├── LICENSE
└── .cursor
└── rules
└── godot-cursorrules-LICENSE
/app/scripts/app.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cp0xkloif6sa0
2 |
--------------------------------------------------------------------------------
/app/scripts/url.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cdr6shkc3kbwq
2 |
--------------------------------------------------------------------------------
/app/scripts/data_saver.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dox2b3cuo6snp
2 |
--------------------------------------------------------------------------------
/app/scripts/navigation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ddwd0y2g4uqnh
2 |
--------------------------------------------------------------------------------
/app/scripts/platform.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bf3kfdbs5m452
2 |
--------------------------------------------------------------------------------
/app/scripts/afk_manager.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dp0w5keejxmbi
2 |
--------------------------------------------------------------------------------
/app/scripts/api/backend.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c5bl3e4ltfibm
2 |
--------------------------------------------------------------------------------
/app/scripts/bookmark_saver.gd.uid:
--------------------------------------------------------------------------------
1 | uid://be28kxxsjys3q
2 |
--------------------------------------------------------------------------------
/app/scripts/debug_log/debug.gd.uid:
--------------------------------------------------------------------------------
1 | uid://db7css1s86rpy
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/unzip.gd.uid:
--------------------------------------------------------------------------------
1 | uid://54nj24yabkui
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/gate.gd.uid:
--------------------------------------------------------------------------------
1 | uid://75501ijgaa18
2 |
--------------------------------------------------------------------------------
/app/scripts/string_tools.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dydhmdww8226j
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/history.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b72ppohv1oxg7
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/menu.gd.uid:
--------------------------------------------------------------------------------
1 | uid://darfa6nm5toj8
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/star.gd.uid:
--------------------------------------------------------------------------------
1 | uid://mt1ftp8euquh
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search.gd.uid:
--------------------------------------------------------------------------------
1 | uid://q034488v3hn0
2 |
--------------------------------------------------------------------------------
/app/scripts/api/discover_gate.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8vqsgpqnci7t
2 |
--------------------------------------------------------------------------------
/app/scripts/api/featured_gates.gd.uid:
--------------------------------------------------------------------------------
1 | uid://0ba2mosykb51
2 |
--------------------------------------------------------------------------------
/app/scripts/debug_log/debug_log.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ctop6ixvl36cs
2 |
--------------------------------------------------------------------------------
/app/scripts/loading/config_base.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dw1klt0vt5ggu
2 |
--------------------------------------------------------------------------------
/app/scripts/loading/config_gate.gd.uid:
--------------------------------------------------------------------------------
1 | uid://m220xgev1foh
2 |
--------------------------------------------------------------------------------
/app/scripts/loading/file_tools.gd.uid:
--------------------------------------------------------------------------------
1 | uid://sw2c1crvksx4
2 |
--------------------------------------------------------------------------------
/app/scripts/loading/gate_loader.gd.uid:
--------------------------------------------------------------------------------
1 | uid://chdajg03b7u04
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/command_sync.gd.uid:
--------------------------------------------------------------------------------
1 | uid://6ctlw23psrgy
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/input_sync.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c12yghi0nsye7
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/app_events.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cqcfgs1a6qcri
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/bookmarks.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cym7xmencyt68
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/ui_events.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cfmvwrj0skpu3
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/bookmark_ui.gd.uid:
--------------------------------------------------------------------------------
1 | uid://p7lshk4bl7sg
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/help_button.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cr4ba7mu31ekk
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/round_button.gd.uid:
--------------------------------------------------------------------------------
1 | uid://chtefdtvq5j01
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/window_drag.gd.uid:
--------------------------------------------------------------------------------
1 | uid://brfe86qp4qxjm
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/board.gd.uid:
--------------------------------------------------------------------------------
1 | uid://caw41kfhy6sm
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/prompt.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c2sowtufpb1vs
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/result.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dxs1odv526wci
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/suggestion.gd.uid:
--------------------------------------------------------------------------------
1 | uid://x5v7vstd6da6
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/tabs/tab_icon.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cerhyb267j7p6
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/ui_mode_animation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bmqka8eh3us3
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/foreground.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dbnurdt5m2qoa
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/gate_info.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bsg3p2j1pj2pd
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/world_canvas.gd.uid:
--------------------------------------------------------------------------------
1 | uid://jl4euafajr25
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/world_ui.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d0ne8le6y71x5
2 |
--------------------------------------------------------------------------------
/app/shaders/render_result.gdshader.uid:
--------------------------------------------------------------------------------
1 | uid://1vyei3mf3oyp
2 |
--------------------------------------------------------------------------------
/app/shaders/spinning_border.gdshader.uid:
--------------------------------------------------------------------------------
1 | uid://k50h5xhmltlj
2 |
--------------------------------------------------------------------------------
/app/addons/max_size_container/plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dg27ycepflben
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dc24inl2k13qd
2 |
--------------------------------------------------------------------------------
/app/scripts/debug_log/debug_window.gd.uid:
--------------------------------------------------------------------------------
1 | uid://drtyjcj7olgfx
2 |
--------------------------------------------------------------------------------
/app/scripts/loading/file_downloader.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dipt1w1cplivp
2 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_cache.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b8g6q8mg18bkr
2 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_endpoint.gd.uid:
--------------------------------------------------------------------------------
1 | uid://df4dj5a6cnf7w
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/process_checker.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bxiq26i721bv3
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/render_result.gd.uid:
--------------------------------------------------------------------------------
1 | uid://csukkr8ml58po
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/renderer_logger.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b6ntyylfuji8f
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/renderer_manager.gd.uid:
--------------------------------------------------------------------------------
1 | uid://8krktytsxii2
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/api_settings.gd.uid:
--------------------------------------------------------------------------------
1 | uid://clehdpmm4ca03
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/command_events.gd.uid:
--------------------------------------------------------------------------------
1 | uid://mm6n1xpsqdgi
2 |
--------------------------------------------------------------------------------
/app/scripts/resources/gate_events.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8id2o0gpghwy
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/menu_navigation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://4ainpxjyto5d
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/scroll_container.gd.uid:
--------------------------------------------------------------------------------
1 | uid://l3738k1ij6ce
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/window_buttons.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dx5y1ggt4idkm
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/carousel.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dyvyst62af5lb
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/close_button.gd.uid:
--------------------------------------------------------------------------------
1 | uid://o2im2u1i2vxo
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/onboarding.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b3sf4l5ohxo1l
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/one_line_text.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ddnjdcn8u1pyu
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/open_meta_link.gd.uid:
--------------------------------------------------------------------------------
1 | uid://g6rm08yvfnwc
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/prompt_results.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dn4uudt3klmqs
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search_results.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cbhov2ptmjii2
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search_status.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b77oevqkschwo
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/gate_description.gd.uid:
--------------------------------------------------------------------------------
1 | uid://1cmhxlv8up1u
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/loading_status.gd.uid:
--------------------------------------------------------------------------------
1 | uid://s5hodmwpxkmt
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/not_responding.gd.uid:
--------------------------------------------------------------------------------
1 | uid://btu6plklkbh0y
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/release_focus.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cylmi0wcawaqu
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/vignette_blur.gd.uid:
--------------------------------------------------------------------------------
1 | uid://crtwlvhtrq7x6
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_events.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dgo27bjkqnhdu
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ddvad6ir1i7hi
2 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_client_pool.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ywqluobp70qb
2 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_date_utils.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cw5ecyf5h5muu
2 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_request_pooled.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c3w08xe07dmr4
2 |
--------------------------------------------------------------------------------
/app/scripts/renderer/renderer_executable.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d10tol3dqm8hy
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/bookmark_container.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bpjvvsx5l8uij
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/bookmark_jump_animation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://4v1xemg4t266
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notification.gd.uid:
--------------------------------------------------------------------------------
1 | uid://6p4mottomv8l
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notifier_base.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c0fviuqlp62cq
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/download_animation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c08fwx0mkx0vs
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/fix_promt_position.gd.uid:
--------------------------------------------------------------------------------
1 | uid://2hpv4d5wi773
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/prompt_navigation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c5rv12bcypf77
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_afk.gd.uid:
--------------------------------------------------------------------------------
1 | uid://h4kvv32q3mo3
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_app.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cxxxems5gu4e0
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_gate.gd.uid:
--------------------------------------------------------------------------------
1 | uid://db7x3ltfwyi4b
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_link.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dxn7uom4yxwa3
2 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_pool_maintainer.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8dgkvbswd3mn
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notification_bar.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ck5x1jvxte7yl
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search_results_header.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bwxcq65yynmnt
2 |
--------------------------------------------------------------------------------
/app/addons/max_size_container/max_size_container.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cy8sr5n3g4ys2
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_bookmark.gd.uid:
--------------------------------------------------------------------------------
1 | uid://duwvf8sq1pkd
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_error.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bfpocd40pnxaj
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notification_manager.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dfrnyf0xausu8
2 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notifier_mouse_captured.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dgh62228rvknj
2 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_onboarding.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dqhjbwthno7qh
2 |
--------------------------------------------------------------------------------
/app/app_icon/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/app_icon/icon.ico
--------------------------------------------------------------------------------
/app/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize EOL for all files that Git considers text files.
2 | * text=auto eol=lf
3 |
--------------------------------------------------------------------------------
/screenshots/1-home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/screenshots/1-home.png
--------------------------------------------------------------------------------
/app/app_icon/icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/app_icon/icon_256.png
--------------------------------------------------------------------------------
/screenshots/2-loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/screenshots/2-loading.png
--------------------------------------------------------------------------------
/app/app_icon/toolbar_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/app_icon/toolbar_icon.ico
--------------------------------------------------------------------------------
/app/resources/app_events.res:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/resources/app_events.res
--------------------------------------------------------------------------------
/app/resources/gate_events.res:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/resources/gate_events.res
--------------------------------------------------------------------------------
/app/resources/ui_events.res:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/resources/ui_events.res
--------------------------------------------------------------------------------
/screenshots/3-in-game-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/screenshots/3-in-game-ui.png
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/fonts/Inter-Bold.otf
--------------------------------------------------------------------------------
/app/assets/fonts/Monospace.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/fonts/Monospace.ttf
--------------------------------------------------------------------------------
/app/assets/styles/tab.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/styles/tab.stylebox
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-Italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/fonts/Inter-Italic.otf
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/fonts/Inter-Regular.otf
--------------------------------------------------------------------------------
/app/assets/fonts/MonospaceBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/fonts/MonospaceBold.ttf
--------------------------------------------------------------------------------
/app/assets/styles/button.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/styles/button.stylebox
--------------------------------------------------------------------------------
/app/assets/styles/panel.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/styles/panel.stylebox
--------------------------------------------------------------------------------
/app/assets/styles/prompt.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/styles/prompt.stylebox
--------------------------------------------------------------------------------
/app/resources/command_events.res:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/resources/command_events.res
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "godot"]
2 | path = godot
3 | url = https://github.com/thegatesbrowser/godot.git
4 | branch = tg-4.2
5 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # Godot 4+ specific ignores
2 | .godot/
3 |
4 | # for ipc files (inter process communication)
5 | ./renderer/
6 |
--------------------------------------------------------------------------------
/app/addons/max_size_container/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/addons/max_size_container/icon.png
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-BoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/fonts/Inter-BoldItalic.otf
--------------------------------------------------------------------------------
/app/assets/styles/button_hover.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/styles/button_hover.stylebox
--------------------------------------------------------------------------------
/app/assets/styles/panel_hover.stylebox:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/styles/panel_hover.stylebox
--------------------------------------------------------------------------------
/app/assets/textures/icon_round_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/textures/icon_round_16.png
--------------------------------------------------------------------------------
/app/assets/textures/icon_round_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/textures/icon_round_32.png
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/2_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/textures/onboarding/2_friends.png
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/3_browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/textures/onboarding/3_browser.png
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/1_internet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/textures/onboarding/1_internet.png
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/4_tutorial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thegatesbrowser/thegates/HEAD/app/assets/textures/onboarding/4_tutorial.png
--------------------------------------------------------------------------------
/app/assets/fonts/LICENSE:
--------------------------------------------------------------------------------
1 | OFL 1.1 (SIL Open Font License, Version 1.1)
2 |
3 | Inter UI and Inter is a trademark of rsms.
4 |
5 | Copyright 2017-2019 The Inter project authors
6 |
7 | // From https://font.download/font/inter
--------------------------------------------------------------------------------
/app/scripts/ui/search/one_line_text.gd:
--------------------------------------------------------------------------------
1 | extends RichTextLabel
2 |
3 |
4 | func _ready() -> void:
5 | finished.connect(to_line)
6 | to_line()
7 |
8 |
9 | func to_line() -> void:
10 | text = text.replace('\n', ' ')
11 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_endpoint.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name HTTPEndpoint
3 |
4 | @export var host: String = ""
5 | @export var port: int = 443
6 | @export var use_tls: bool = true
7 | @export var desired_connections: int
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # add to skip-worktree to ignore local changes
4 | .vscode/settings.json
5 |
6 | # for ipc files (inter process communication)
7 | app/renderer
8 |
9 | # for deployment
10 | deployment/upload_api.key
11 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/release_focus.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 |
4 | func _input(event: InputEvent) -> void:
5 | if (has_focus()
6 | and event is InputEventMouseButton
7 | and not get_global_rect().has_point(event.position)):
8 | release_focus()
9 |
--------------------------------------------------------------------------------
/app/scripts/resources/app_events.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name AppEvents
3 |
4 | signal open_link(url: String)
5 |
6 |
7 | func open_link_emit(url: String) -> void:
8 | OS.shell_open(url) # TODO: move somewhere else
9 | open_link.emit(url)
10 |
--------------------------------------------------------------------------------
/app/resources/history.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="Resource" script_class="History" load_steps=2 format=3 uid="uid://bqgikyax6jfqa"]
2 |
3 | [ext_resource type="Script" path="res://scripts/ui/menu/history.gd" id="1_oh8jq"]
4 |
5 | [resource]
6 | script = ExtResource("1_oh8jq")
7 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/gate_description.gd:
--------------------------------------------------------------------------------
1 | extends OpenMetaLink
2 |
3 |
4 | # From release_focus.gd
5 | func _input(event: InputEvent) -> void:
6 | if (has_focus()
7 | and event is InputEventMouseButton
8 | and not get_global_rect().has_point(event.position)):
9 | release_focus()
10 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name AnalyticsSender
3 |
4 | var analytics: Analytics
5 |
6 |
7 | func _enter_tree() -> void:
8 | analytics = get_parent()
9 | analytics.analytics_ready.connect(start)
10 |
11 |
12 | func start() -> void:
13 | pass
14 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/help_button.gd:
--------------------------------------------------------------------------------
1 | extends RoundButton
2 |
3 | @export var url: String
4 | @export var app_events: AppEvents
5 |
6 |
7 | func _ready() -> void:
8 | super._ready()
9 |
10 | pressed.connect(open_help_url)
11 |
12 |
13 | func open_help_url() -> void:
14 | app_events.open_link_emit(url)
15 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/open_meta_link.gd:
--------------------------------------------------------------------------------
1 | extends RichTextLabel
2 | class_name OpenMetaLink
3 |
4 | @export var app_events: AppEvents
5 |
6 |
7 | func _ready() -> void:
8 | meta_clicked.connect(on_meta_clicked)
9 |
10 |
11 | func on_meta_clicked(meta) -> void:
12 | app_events.open_link_emit(str(meta))
13 |
--------------------------------------------------------------------------------
/app/scripts/ui/tabs/tab_icon.gd:
--------------------------------------------------------------------------------
1 | extends Panel
2 |
3 | @export var icon: TextureRect
4 | @export var icon_hires: TextureRect
5 |
6 |
7 | func _ready() -> void:
8 | if DisplayServer.screen_get_scale() == 2.0:
9 | icon.hide()
10 | icon_hires.show()
11 | else:
12 | icon.show()
13 | icon_hires.hide()
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.defaultSolution": "disable",
3 | "godotTools.editorPath.godot4": "godot/bin/godot.linuxbsd.editor.dev.x86_64.llvm",
4 | "diffEditor.ignoreTrimWhitespace": false,
5 | "editor.trimAutoWhitespace": false,
6 | "files.exclude": {
7 | "**/*.uid": true
8 | }
9 | }
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_error.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderError
3 |
4 |
5 | func start() -> void:
6 | super.start()
7 |
8 | Debug.error.connect(send_error)
9 |
10 |
11 | func send_error(msg: String) -> void:
12 | analytics.send_event(AnalyticsEvents.error(msg))
13 |
--------------------------------------------------------------------------------
/.cursorignore:
--------------------------------------------------------------------------------
1 | # Exclude large third-party engine submodule and binaries
2 | godot/**
3 |
4 | # Godot caches and import artifacts
5 | **/.godot/**
6 | **/.import/**
7 | **/*.import
8 |
9 | # Compiled objects, libraries, and archives (keep ones not in default list)
10 | **/*.o
11 | **/*.a
12 | **/*.dylib
13 | **/*.class
14 |
--------------------------------------------------------------------------------
/app/scripts/data_saver.gd:
--------------------------------------------------------------------------------
1 | extends ConfigBase
2 | #class_name DataSaver
3 |
4 | var path: String = "user://resources/data_saver.cfg"
5 |
6 |
7 | func _init() -> void:
8 | super._init(path)
9 |
10 |
11 | func save_data() -> void:
12 | config.save(config_path)
13 |
14 |
15 | func _exit_tree() -> void:
16 | save_data()
17 |
--------------------------------------------------------------------------------
/app/assets/styles/text.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://bo2334w4lf3ug"]
2 |
3 | [ext_resource type="FontFile" uid="uid://do40418waa8w3" path="res://assets/fonts/Inter-Regular.otf" id="1_lfwid"]
4 |
5 | [resource]
6 | font = ExtResource("1_lfwid")
7 | font_size = 15
8 | font_color = Color(0.831373, 0.831373, 0.831373, 1)
9 |
--------------------------------------------------------------------------------
/app/assets/styles/text_small.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://85ms8ndcmbn0"]
2 |
3 | [ext_resource type="FontFile" uid="uid://c14w1y1r54wgi" path="res://assets/fonts/Inter-Bold.otf" id="1_eh4y8"]
4 |
5 | [resource]
6 | font = ExtResource("1_eh4y8")
7 | font_size = 14
8 | font_color = Color(0.831373, 0.831373, 0.831373, 1)
9 |
--------------------------------------------------------------------------------
/app/assets/styles/text_big.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://crt4elt055uhg"]
2 |
3 | [ext_resource type="FontFile" uid="uid://do40418waa8w3" path="res://assets/fonts/Inter-Regular.otf" id="1_4l6dr"]
4 |
5 | [resource]
6 | font = ExtResource("1_4l6dr")
7 | font_size = 20
8 | font_color = Color(0.831373, 0.831373, 0.831373, 1)
9 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/suggestion.gd:
--------------------------------------------------------------------------------
1 | extends Button
2 | class_name Suggestion
3 |
4 | @export var gate_events: GateEvents
5 | @export var prompt: String
6 |
7 |
8 | func fill(_prompt: String) -> void:
9 | prompt = _prompt
10 | text = _prompt
11 |
12 |
13 | func _on_button_pressed() -> void:
14 | if prompt.is_empty(): return
15 |
16 | gate_events.search_emit(prompt)
17 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_link.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderLink
3 |
4 | @export var app_events: AppEvents
5 |
6 |
7 | func start() -> void:
8 | super.start()
9 |
10 | app_events.open_link.connect(send_open_link)
11 |
12 |
13 | func send_open_link(url: String) -> void:
14 | analytics.send_event(AnalyticsEvents.open_link(url))
15 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notifier_base.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name NotifierBase
3 |
4 | signal show(message: String, icon: Texture2D)
5 | signal hide()
6 |
7 | @export_multiline var message: String
8 | @export var icon: Texture2D
9 |
10 |
11 | func show_notification() -> void:
12 | show.emit(message, icon)
13 |
14 |
15 | func hide_notification() -> void:
16 | hide.emit()
17 |
--------------------------------------------------------------------------------
/app/shaders/render_result.gdshader:
--------------------------------------------------------------------------------
1 | shader_type canvas_item;
2 |
3 | uniform bool ext_texture_is_bgra;
4 | uniform bool show_render;
5 |
6 | const vec4 zero = vec4(0);
7 |
8 | void fragment() {
9 | if (show_render) {
10 | COLOR = vec4(COLOR.rgb, 1);
11 | } else {
12 | COLOR = zero;
13 | }
14 |
15 | if (ext_texture_is_bgra) {
16 | COLOR = COLOR.bgra; // Swizzle BGRA to RGBA
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/resources/bookmarks.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="Resource" script_class="Bookmarks" load_steps=3 format=3 uid="uid://bewhdj6jugt6q"]
2 |
3 | [ext_resource type="Script" path="res://scripts/resources/bookmarks.gd" id="1_1h3wl"]
4 | [ext_resource type="Script" path="res://scripts/resources/gate.gd" id="2_4h04h"]
5 |
6 | [resource]
7 | script = ExtResource("1_1h3wl")
8 | starred_gates = Array[ExtResource("2_4h04h")]([])
9 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search_results_header.gd:
--------------------------------------------------------------------------------
1 | extends Label
2 | class_name SearchResultsHeader
3 |
4 | @export var gate_events: GateEvents
5 | @export var search_header: String
6 | @export var suggestion_header: String
7 |
8 |
9 | func set_search_header() -> void:
10 | text = "%s \"%s\"" % [search_header, gate_events.current_search_query]
11 |
12 |
13 | func set_suggestion_header() -> void:
14 | text = suggestion_header
15 |
--------------------------------------------------------------------------------
/app/addons/max_size_container/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="Max Size Container"
4 | description="This is a custom container that limits its child maximum size.
5 |
6 | 1. Add a Control node as a child
7 | 2. Set its maximum width and height
8 | 3. Choose how to align the child node when the maximum size is reached
9 |
10 | Note that the container can only accept 1 child."
11 | author="Brom Bresenham"
12 | version="1.3"
13 | script="plugin.gd"
14 |
--------------------------------------------------------------------------------
/app/scenes/autoloads/url.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://ibqfwrvrximj"]
2 |
3 | [ext_resource type="Script" uid="uid://cdr6shkc3kbwq" path="res://scripts/url.gd" id="1_jqily"]
4 | [ext_resource type="Resource" uid="uid://cjcdum6fm4ta0" path="res://resources/api_settings.tres" id="2_nw0pg"]
5 |
6 | [node name="Url" type="Node"]
7 | script = ExtResource("1_jqily")
8 | tld_list_file = "res://resources/tld_list.txt"
9 | api_settings = ExtResource("2_nw0pg")
10 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/bookmark_container.gd:
--------------------------------------------------------------------------------
1 | extends HFlowContainer
2 |
3 | @export var bookmarks: Bookmarks
4 | @export var bookmark_scene: PackedScene
5 |
6 |
7 | func _ready() -> void:
8 | bookmarks.on_star.connect(show_bookmark)
9 | for gate in bookmarks.gates.values():
10 | show_bookmark(gate)
11 |
12 |
13 | func show_bookmark(gate: Gate) -> void:
14 | var bookmark: BookmarkUI = bookmark_scene.instantiate()
15 | bookmark.fill(gate)
16 | add_child(bookmark)
17 | move_child(bookmark, 0)
18 |
--------------------------------------------------------------------------------
/app/addons/max_size_container/plugin.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | func _enter_tree():
5 | # Initialization of the plugin goes here.
6 | # Add the new type with a name, a parent type, a script and an icon.
7 | add_custom_type("MaxSizeContainer", "MarginContainer", preload("max_size_container.gd"), preload("icon.png"))
8 |
9 | func _exit_tree():
10 | # Clean-up of the plugin goes here.
11 | # Always remember to remove it from the engine when deactivated.
12 | remove_custom_type("MaxSizeContainer")
13 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notification_bar.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name NotificationBar
3 |
4 | @export var notification_scene: PackedScene
5 | @export var container: VBoxContainer
6 |
7 |
8 | func show_notification(message: String, icon: Texture2D) -> Notification:
9 | var ntf: Notification = notification_scene.instantiate()
10 | ntf.fill(message, icon)
11 |
12 | container.add_child(ntf)
13 | return ntf
14 |
15 |
16 | func hide_notification(ntf: Notification) -> void:
17 | await ntf.hide_notification()
18 | ntf.queue_free()
19 |
--------------------------------------------------------------------------------
/app/app_icon/README.md:
--------------------------------------------------------------------------------
1 | ## Application icon for Windows
2 |
3 | * To create *.ico file from icon_256.png use https://redketchup.io/icon-converter
4 | **Icon** in windows export preset
5 | `icon.ico - 16x16, 32x32, 48x48, 64x64, 128x128, 256x256`
6 |
7 | * You should use different icon for taskbar.
8 | Otherwise Windows will downscale 32x32 image to 24x24
9 | **Windows Native Icon** in project settings
10 | `toolbar_icon.ico - 16x16, 24x24, 32x32`
11 |
12 | Refer to: https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-icons
13 |
--------------------------------------------------------------------------------
/app/resources/api_settings.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="Resource" script_class="ApiSettings" load_steps=2 format=3 uid="uid://cjcdum6fm4ta0"]
2 |
3 | [ext_resource type="Script" path="res://scripts/resources/api_settings.gd" id="1_oiju7"]
4 |
5 | [resource]
6 | script = ExtResource("1_oiju7")
7 | local_url = "http://127.0.0.1:8000"
8 | remote_url = "https://app.thegates.io"
9 | host_type = 1
10 | trusted_urls = Array[String](["https://thegates.io", "https://app.thegates.io", "https://www.thegates.io", "http://thegates.io", "http://app.thegates.io", "http://www.thegates.io"])
11 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_request_pooled.gd:
--------------------------------------------------------------------------------
1 | extends HTTPRequest
2 | class_name HTTPRequestPooled
3 |
4 | var http_client: HTTPClient
5 |
6 |
7 | func _ready() -> void:
8 | request_cancelled.connect(on_request_done)
9 |
10 |
11 | func _get_http_client(host: String, port: int, use_tls: bool) -> HTTPClient:
12 | http_client = HTTPClientPool.acquire_client(self, host, port, use_tls)
13 | return http_client
14 |
15 |
16 | func on_request_done() -> void:
17 | if http_client == null: return
18 | HTTPClientPool.release_client(http_client)
19 | http_client = null
20 |
--------------------------------------------------------------------------------
/app/scenes/autoloads/navigation.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://b81h2v72dlgt5"]
2 |
3 | [ext_resource type="Script" uid="uid://ddwd0y2g4uqnh" path="res://scripts/navigation.gd" id="1_urxac"]
4 | [ext_resource type="Resource" uid="uid://b1xvdym0qh6td" path="res://resources/gate_events.res" id="2_1l00j"]
5 | [ext_resource type="Resource" uid="uid://bqgikyax6jfqa" path="res://resources/history.tres" id="3_qqkln"]
6 |
7 | [node name="Navigation" type="Node"]
8 | script = ExtResource("1_urxac")
9 | gate_events = ExtResource("2_1l00j")
10 | history = ExtResource("3_qqkln")
11 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_bookmark.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderBookmark
3 |
4 | @export var bookmarks: Bookmarks
5 |
6 |
7 | func _ready() -> void:
8 | super.start()
9 |
10 | bookmarks.on_star.connect(send_bookmark)
11 | bookmarks.on_unstar.connect(send_unbookmark)
12 |
13 |
14 | func send_bookmark(gate: Gate) -> void:
15 | if gate.featured: return
16 | analytics.send_event(AnalyticsEvents.bookmark(gate.url))
17 |
18 |
19 | func send_unbookmark(gate: Gate) -> void:
20 | analytics.send_event(AnalyticsEvents.unbookmark(gate.url))
21 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_afk.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderAfk
3 |
4 | var afk_started_tick: int
5 |
6 |
7 | func start() -> void:
8 | super.start()
9 |
10 | AfkManager.state_changed.connect(send_afk_state_changed)
11 |
12 |
13 | func send_afk_state_changed(is_afk: bool) -> void:
14 | if is_afk:
15 | afk_started_tick = Time.get_ticks_msec()
16 | analytics.send_event(AnalyticsEvents.enter_afk())
17 | else:
18 | var time_spent = Analytics.get_delta_sec_from_tick(afk_started_tick)
19 | analytics.send_event(AnalyticsEvents.leave_afk(time_spent))
20 |
--------------------------------------------------------------------------------
/app/scripts/app.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var gate_events: GateEvents
4 | @export var home: PackedScene
5 | @export var search_results: PackedScene
6 | @export var world_scene: PackedScene
7 | @export var scenes_root: Node
8 |
9 |
10 | func _ready() -> void:
11 | gate_events.search.connect(func(_query): switch_scene(search_results))
12 | gate_events.open_gate_app.connect(func(_url): switch_scene(world_scene))
13 | gate_events.exit_gate.connect(func(): switch_scene(home))
14 |
15 | switch_scene(home)
16 |
17 |
18 | func switch_scene(scene: PackedScene) -> void:
19 | for child in scenes_root.get_children(): child.queue_free()
20 | scenes_root.add_child(scene.instantiate())
21 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/download_animation.gd:
--------------------------------------------------------------------------------
1 | extends TextureRect
2 |
3 | @export var start_scale: float
4 | @export var end_scale: float
5 | @export var duration: float
6 |
7 | @onready var start := Vector2(start_scale, start_scale)
8 | @onready var end := Vector2(end_scale, end_scale)
9 | @onready var default = scale
10 |
11 |
12 | func _ready() -> void:
13 | animate()
14 |
15 |
16 | func _notification(what: int) -> void:
17 | if what == NOTIFICATION_ENABLED:
18 | animate()
19 |
20 |
21 | func animate() -> void:
22 | var tween = create_tween().set_loops()
23 | tween.tween_property(self, "scale", end, duration).from(start)
24 | tween.tween_property(self, "scale", start, duration).from(end)
25 |
--------------------------------------------------------------------------------
/app/scripts/debug_log/debug.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | signal logged(msg: String)
4 | signal error(msg: String)
5 |
6 | const ERROR_CLR = Color.RED
7 | const WARN_CLR = Color.YELLOW
8 | const SILENT_CLR = Color.DIM_GRAY
9 |
10 |
11 | func logr(msg: Variant) -> void:
12 | print_rich(str(msg))
13 | logged.emit(str(msg))
14 |
15 |
16 | func logerr(msg: Variant) -> void:
17 | printerr(str(msg))
18 | var rich_clr = "[color=%s]%s[/color]" % [Color.RED.to_html(), str(msg)]
19 | logged.emit(rich_clr)
20 | error.emit(msg)
21 |
22 |
23 | func logclr(msg: Variant, color: Color) -> void:
24 | var rich_clr = "[color=%s]%s[/color]" % [color.to_html(), str(msg)]
25 | print_rich(rich_clr)
26 | logged.emit(rich_clr)
27 |
--------------------------------------------------------------------------------
/app/scripts/debug_log/debug_log.gd:
--------------------------------------------------------------------------------
1 | extends RichTextLabel
2 |
3 | const APP_NAME: String = "TheGates"
4 | const WEB_SITE: String = "https://thegates.io"
5 |
6 |
7 | func _ready() -> void:
8 | Debug.logged.connect(add_log)
9 | meta_clicked.connect(on_meta_clicked)
10 |
11 | print_app_info()
12 |
13 |
14 | func add_log(msg: String) -> void:
15 | append_text(msg + "\n")
16 |
17 |
18 | func on_meta_clicked(meta) -> void:
19 | OS.shell_open(str(meta))
20 |
21 |
22 | func print_app_info() -> void:
23 | var version: String = ProjectSettings.get_setting("application/config/version")
24 | var platform: String = OS.get_name()
25 | Debug.logr("%s %s v%s - [url]%s[/url]" % [APP_NAME, platform, version, WEB_SITE])
26 |
--------------------------------------------------------------------------------
/app/scripts/ui/ui_mode_animation.gd:
--------------------------------------------------------------------------------
1 | extends AnimationPlayer
2 |
3 | @export var ui_events: UiEvents
4 | @export var gate_events: GateEvents
5 |
6 | const RESET := "RESET"
7 | const INITIAL := "initial"
8 | const FOCUSED := "focused"
9 |
10 | var focused := false
11 |
12 |
13 | func _ready() -> void:
14 | ui_events.ui_mode_changed.connect(on_ui_mode_changed)
15 | gate_events.open_gate.connect(func(_url): on_ui_mode_changed(UiEvents.UiMode.INITIAL))
16 |
17 |
18 | func on_ui_mode_changed(mode: UiEvents.UiMode) -> void:
19 | if mode == UiEvents.UiMode.INITIAL and focused:
20 | focused = false
21 | play(INITIAL)
22 |
23 | if mode == UiEvents.UiMode.FOCUSED and not focused:
24 | focused = true
25 | play(FOCUSED)
26 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/vignette_blur.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name VignetteBlur
3 |
4 | const BLUR_AMOUNT = &"BlurAmount"
5 | const UV_SCALE = &"UVScale"
6 |
7 | @export var blur_amount: float
8 | @export var blur_amount_started: float
9 | @export var uv_scale: Vector2
10 | @export var uv_scale_startd: Vector2
11 |
12 |
13 | func thumbnail_params() -> void:
14 | set_param(BLUR_AMOUNT, blur_amount)
15 | set_param(UV_SCALE, uv_scale)
16 |
17 |
18 | func gate_started_params() -> void:
19 | set_param(BLUR_AMOUNT, blur_amount_started)
20 | set_param(UV_SCALE, uv_scale_startd)
21 |
22 |
23 | func set_param(param: StringName, value: Variant) -> void:
24 | (material as ShaderMaterial).set_shader_parameter(param, value)
25 |
--------------------------------------------------------------------------------
/app/assets/textures/minimize.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/menu_navigation.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var gate_events: GateEvents
4 |
5 | @export var go_back: RoundButton
6 | @export var go_forw: RoundButton
7 | @export var reload: RoundButton
8 | @export var home: RoundButton
9 |
10 |
11 | func _ready() -> void:
12 | go_back.pressed.connect(Navigation.go_back)
13 | go_forw.pressed.connect(Navigation.go_forw)
14 | reload.pressed.connect(Navigation.reload)
15 | home.pressed.connect(Navigation.home)
16 |
17 | Navigation.updated.connect(update_buttons)
18 | update_buttons()
19 |
20 |
21 | func update_buttons() -> void:
22 | if Navigation.can_back(): go_back.enable()
23 | else: go_back.disable()
24 |
25 | if Navigation.can_forw(): go_forw.enable()
26 | else: go_forw.disable()
27 |
--------------------------------------------------------------------------------
/app/scripts/string_tools.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name StringTools
3 |
4 |
5 | static func to_alpha(text: String) -> String:
6 | var last_is_alpha = false
7 | var result = ""
8 |
9 | for symbol in text:
10 | if (symbol >= 'a' and symbol <= 'z') or (symbol >= 'A' and symbol <= 'Z'):
11 | result += symbol
12 | last_is_alpha = true
13 | elif last_is_alpha:
14 | result += " "
15 | last_is_alpha = false
16 |
17 | result = result.strip_edges()
18 | return result
19 |
20 |
21 | static func bytes_to_string(bytes: int) -> String:
22 | if bytes < 1024: return str(bytes) + "B"
23 |
24 | var kb = bytes / 1024
25 | if kb < 1024: return str(kb) + "KB"
26 |
27 | var mb = kb / 1024.0
28 | var text = "%.1fMB" if mb < 10.0 else "%.0fMB"
29 | return text % [mb]
30 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/prompt.gd:
--------------------------------------------------------------------------------
1 | extends Button
2 | class_name PromptResult
3 |
4 | @export var gate_events: GateEvents
5 | @export var prompt_text: Label
6 | @export var focus_style: StyleBox
7 |
8 | var normal_style: StyleBox
9 |
10 |
11 | func _ready() -> void:
12 | normal_style = get_theme_stylebox("normal", "")
13 |
14 |
15 | func fill(prompt: String) -> void:
16 | if prompt.is_empty(): return
17 | prompt_text.text = prompt.to_lower()
18 |
19 |
20 | func _on_button_pressed() -> void:
21 | if prompt_text.text.is_empty(): return
22 | gate_events.search_emit(prompt_text.text)
23 |
24 |
25 | func focus() -> void:
26 | add_theme_stylebox_override("normal", focus_style)
27 |
28 |
29 | func unfocus() -> void:
30 | add_theme_stylebox_override("normal", normal_style)
31 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/world_canvas.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var ui_events: UiEvents
4 | @export var interpolate: float:
5 | set(value):
6 | interpolate = value
7 | animate(value)
8 |
9 | var initial: int
10 | var full_screen: int
11 |
12 |
13 | func _ready() -> void:
14 | var viewport_width = ProjectSettings.get_setting("display/window/size/viewport_width", 1152)
15 | var scale_width = float(custom_minimum_size.x) / viewport_width
16 |
17 | full_screen = int(ui_events.current_ui_size.x)
18 | initial = int(full_screen * scale_width)
19 | custom_minimum_size.x = initial
20 | Debug.logclr("WorldCanvas initial: %d full_screen: %d" % [initial, full_screen], Color.DIM_GRAY)
21 |
22 |
23 | func animate(value: float) -> void:
24 | custom_minimum_size.x = lerp(initial, full_screen + 1, value)
25 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_pool_maintainer.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name HTTPPoolMaintainer
3 |
4 | @export var endpoints: Array[HTTPEndpoint]
5 | @export var check_interval_sec: float = 0.3
6 |
7 | var accumulator_sec: float
8 |
9 |
10 | func _enter_tree() -> void:
11 | maintain_connections()
12 |
13 |
14 | func _process(delta: float) -> void:
15 | accumulator_sec += delta
16 | if accumulator_sec < check_interval_sec: return
17 | accumulator_sec = 0.0
18 |
19 | maintain_connections()
20 |
21 |
22 | func maintain_connections() -> void:
23 | for endpoint in endpoints:
24 | var current = HTTPClientPool.get_connection_count(endpoint)
25 | var need = endpoint.desired_connections - current
26 | if need <= 0: continue
27 |
28 | for i in range(need):
29 | HTTPClientPool.spawn_idle_connection(endpoint)
30 |
--------------------------------------------------------------------------------
/app/scripts/resources/command_events.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name CommandEvents
3 |
4 | signal send_filehandle(filehandle_path: String)
5 | signal ext_texture_format(format: RenderingDevice.DataFormat)
6 | signal set_mouse_mode(mode: Input.MouseMode)
7 | signal heartbeat()
8 | signal highlight_button(button_id: String)
9 |
10 |
11 | func send_filehandle_emit(filehandle_path: String) -> void:
12 | send_filehandle.emit(filehandle_path)
13 |
14 |
15 | func ext_texture_format_emit(format: RenderingDevice.DataFormat) -> void:
16 | ext_texture_format.emit(format)
17 |
18 |
19 | func set_mouse_mode_emit(mode: Input.MouseMode) -> void:
20 | set_mouse_mode.emit(mode)
21 |
22 |
23 | func heartbeat_emit() -> void:
24 | heartbeat.emit()
25 |
26 |
27 | func highlight_button_emit(button_id: String) -> void:
28 | highlight_button.emit(button_id)
29 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/scroll_container.gd:
--------------------------------------------------------------------------------
1 | extends ScrollContainer
2 |
3 | @export var search: Search
4 | @export var scroll_speed: float
5 |
6 |
7 | func _input(event: InputEvent) -> void:
8 | if not search.has_focus(): return
9 | if event is not InputEventMouseButton and \
10 | event is not InputEventPanGesture: return
11 |
12 | if not get_global_rect().has_point(event.position): return
13 | if not search.prompt_panel.get_global_rect().has_point(event.position): return
14 |
15 | if event is InputEventMouseButton:
16 | if event.button_index == MOUSE_BUTTON_WHEEL_UP:
17 | scroll_vertical -= scroll_speed * event.factor
18 |
19 | if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
20 | scroll_vertical += scroll_speed * event.factor
21 |
22 | if event is InputEventPanGesture:
23 | scroll_vertical += scroll_speed * event.delta.y
24 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_onboarding.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderOnboarding
3 |
4 | @export var ui_events: UiEvents
5 |
6 | var onboarding_started_tick: int
7 |
8 |
9 | func start() -> void:
10 | super.start()
11 |
12 | ui_events.onboarding_started.connect(send_onboarding_started)
13 | ui_events.onboarding_finished.connect(send_onboarding_finished)
14 |
15 | if ui_events.is_onboarding_started:
16 | send_onboarding_started()
17 |
18 |
19 | func send_onboarding_started() -> void:
20 | onboarding_started_tick = Time.get_ticks_msec()
21 | analytics.send_event(AnalyticsEvents.onboarding_started())
22 |
23 |
24 | func send_onboarding_finished() -> void:
25 | var time_spent = Analytics.get_delta_sec_from_tick(onboarding_started_tick)
26 | analytics.send_event(AnalyticsEvents.onboarding_finished(time_spent))
27 |
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-Bold.otf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://c14w1y1r54wgi"
6 | path="res://.godot/imported/Inter-Bold.otf-fed3606970d0d1ec228f8cbb3326cebe.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/fonts/Inter-Bold.otf"
11 | dest_files=["res://.godot/imported/Inter-Bold.otf-fed3606970d0d1ec228f8cbb3326cebe.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=true
18 | disable_embedded_bitmaps=true
19 | multichannel_signed_distance_field=false
20 | msdf_pixel_range=8
21 | msdf_size=48
22 | allow_system_fallback=true
23 | force_autohinter=false
24 | hinting=1
25 | subpixel_positioning=1
26 | oversampling=0.0
27 | Fallbacks=null
28 | fallbacks=[]
29 | Compress=null
30 | compress=true
31 | preload=[]
32 | language_support={}
33 | script_support={}
34 | opentype_features={}
35 |
--------------------------------------------------------------------------------
/app/assets/fonts/Monospace.ttf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://bjeupg0ikc2kv"
6 | path="res://.godot/imported/Monospace.ttf-84d18bc2b9fbca262f60d9ae15d1ff11.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/fonts/Monospace.ttf"
11 | dest_files=["res://.godot/imported/Monospace.ttf-84d18bc2b9fbca262f60d9ae15d1ff11.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=true
18 | disable_embedded_bitmaps=true
19 | multichannel_signed_distance_field=false
20 | msdf_pixel_range=8
21 | msdf_size=48
22 | allow_system_fallback=true
23 | force_autohinter=false
24 | hinting=1
25 | subpixel_positioning=1
26 | oversampling=0.0
27 | Fallbacks=null
28 | fallbacks=[]
29 | Compress=null
30 | compress=true
31 | preload=[]
32 | language_support={}
33 | script_support={}
34 | opentype_features={}
35 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/fix_promt_position.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var search: Search
4 |
5 | var update_position: bool
6 |
7 |
8 | func _ready() -> void:
9 | search.resized.connect(change_size)
10 | search.focus_entered.connect(change_size)
11 |
12 |
13 | func change_size() -> void:
14 | global_position = get_parent().global_position
15 | size.x = search.size.x
16 |
17 |
18 | func _input(event: InputEvent) -> void:
19 | if not search.has_focus(): return
20 |
21 | if event is InputEventMouseButton and event.button_index in \
22 | [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]:
23 | update_position = true
24 |
25 | if event is InputEventPanGesture:
26 | update_position = true
27 |
28 |
29 | func _process(_delta: float) -> void:
30 | if not update_position: return
31 |
32 | global_position = get_parent().global_position
33 | update_position = false
34 |
--------------------------------------------------------------------------------
/app/assets/textures/gate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-Italic.otf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://b3xb1fpllhnf4"
6 | path="res://.godot/imported/Inter-Italic.otf-a2ee68ef97ef9010eaa7272e19db785b.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/fonts/Inter-Italic.otf"
11 | dest_files=["res://.godot/imported/Inter-Italic.otf-a2ee68ef97ef9010eaa7272e19db785b.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=true
18 | disable_embedded_bitmaps=true
19 | multichannel_signed_distance_field=false
20 | msdf_pixel_range=8
21 | msdf_size=48
22 | allow_system_fallback=true
23 | force_autohinter=false
24 | hinting=1
25 | subpixel_positioning=1
26 | oversampling=0.0
27 | Fallbacks=null
28 | fallbacks=[]
29 | Compress=null
30 | compress=true
31 | preload=[]
32 | language_support={}
33 | script_support={}
34 | opentype_features={}
35 |
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-BoldItalic.otf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://tfj3o1e1wytn"
6 | path="res://.godot/imported/Inter-BoldItalic.otf-fc2a2d62a96e28426d3bc0aff707ef82.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/fonts/Inter-BoldItalic.otf"
11 | dest_files=["res://.godot/imported/Inter-BoldItalic.otf-fc2a2d62a96e28426d3bc0aff707ef82.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=true
18 | disable_embedded_bitmaps=true
19 | multichannel_signed_distance_field=false
20 | msdf_pixel_range=8
21 | msdf_size=48
22 | allow_system_fallback=true
23 | force_autohinter=false
24 | hinting=1
25 | subpixel_positioning=1
26 | oversampling=0.0
27 | Fallbacks=null
28 | fallbacks=[]
29 | Compress=null
30 | compress=true
31 | preload=[]
32 | language_support={}
33 | script_support={}
34 | opentype_features={}
35 |
--------------------------------------------------------------------------------
/app/scripts/resources/gate.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name Gate
3 |
4 | @export var url: String:
5 | set(value): url = Url.fix_gate_url(value)
6 |
7 | @export var title: String
8 | @export var description: String
9 | @export var icon_url: String
10 | @export var image_url: String
11 | @export var icon: String
12 | @export var image: String
13 |
14 | # Only for featured gates. Cleared when opened
15 | @export var featured: bool
16 | @export var is_special: bool
17 |
18 | var resource_pack: String
19 | var shared_libs_dir: String # local path where libs downloaded
20 | var renderer: String
21 |
22 |
23 | static func create(_url: String, _title: String, _description: String, _icon_url: String, _image_url: String) -> Gate:
24 | var gate = Gate.new()
25 | gate.url = _url
26 | gate.title = _title
27 | gate.description = _description
28 | gate.icon_url = _icon_url
29 | gate.image_url = _image_url
30 | return gate
31 |
--------------------------------------------------------------------------------
/app/scripts/debug_log/debug_window.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var ui_events: UiEvents
4 | @export var window: Window
5 |
6 | var window_visible
7 |
8 |
9 | func _ready() -> void:
10 | window_hide()
11 |
12 |
13 | func _input(event: InputEvent) -> void:
14 | if event.is_action_pressed("open_debug") and not event.is_echo():
15 | if window_visible:
16 | window_hide()
17 | else:
18 | window_show()
19 |
20 |
21 | func _on_window_window_input(event: InputEvent) -> void:
22 | _input(event)
23 |
24 |
25 | func _on_window_close_requested() -> void:
26 | window_hide()
27 |
28 |
29 | func _on_window_focus_exited() -> void:
30 | window_hide()
31 |
32 |
33 | func window_show() -> void:
34 | window.show()
35 | window_visible = true
36 | ui_events.debug_window_opened_emit()
37 |
38 |
39 | func window_hide() -> void:
40 | window.hide()
41 | window_visible = false
42 | ui_events.debug_window_closed_emit()
43 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/result.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name SearchResult
3 |
4 | const KEY_URL = "url"
5 | const KEY_TITLE = "title"
6 | const KEY_DESCRIPTION = "description"
7 | const KEY_ICON = "icon"
8 | const KEY_IMAGE = "image"
9 |
10 | @export var gate_events: GateEvents
11 |
12 | @export var url: Label
13 | @export var title: Label
14 | @export var description: RichTextLabel
15 | @export var icon: TextureRect
16 |
17 |
18 | func fill(gate: Dictionary) -> void:
19 | if gate == null: return
20 |
21 | url.text = gate[KEY_URL]
22 | title.text = "Unnamed" if gate[KEY_TITLE].is_empty() else gate[KEY_TITLE]
23 | description.text = gate[KEY_DESCRIPTION]
24 |
25 | var icon_path = await FileDownloader.download(gate[KEY_ICON])
26 | icon.texture = FileTools.load_external_tex(icon_path)
27 |
28 |
29 | func _on_button_pressed() -> void:
30 | if url.text.is_empty(): return
31 | gate_events.open_gate_emit(url.text)
32 |
--------------------------------------------------------------------------------
/app/assets/textures/arrow_right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
--------------------------------------------------------------------------------
/app/app_icon/icon_256.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://df1poxvhgjkqc"
6 | path="res://.godot/imported/icon_256.png-f15d0e566f1aa7fd612a4e9288f230c6.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://app_icon/icon_256.png"
14 | dest_files=["res://.godot/imported/icon_256.png-f15d0e566f1aa7fd612a4e9288f230c6.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/app/assets/textures/arrow_left.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
--------------------------------------------------------------------------------
/app/assets/textures/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
28 |
--------------------------------------------------------------------------------
/app/assets/textures/maximaze.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
29 |
--------------------------------------------------------------------------------
/app/assets/textures/plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
28 |
--------------------------------------------------------------------------------
/app/assets/textures/icon_round_16.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://db7adnvbsxena"
6 | path="res://.godot/imported/icon_round_16.png-430714a6908da12e5e353f06dbe82aa0.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/textures/icon_round_16.png"
14 | dest_files=["res://.godot/imported/icon_round_16.png-430714a6908da12e5e353f06dbe82aa0.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/app/assets/textures/icon_round_32.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cwclokgfijavb"
6 | path="res://.godot/imported/icon_round_32.png-94be4e4ec9838a368ee616a959216155.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/textures/icon_round_32.png"
14 | dest_files=["res://.godot/imported/icon_round_32.png-94be4e4ec9838a368ee616a959216155.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/app/assets/fonts/Inter-Regular.otf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://do40418waa8w3"
6 | path="res://.godot/imported/Inter-Regular.otf-f352c7616b1c2d50404f5bb3f4876582.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/fonts/Inter-Regular.otf"
11 | dest_files=["res://.godot/imported/Inter-Regular.otf-f352c7616b1c2d50404f5bb3f4876582.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=true
18 | disable_embedded_bitmaps=true
19 | multichannel_signed_distance_field=false
20 | msdf_pixel_range=8
21 | msdf_size=48
22 | allow_system_fallback=true
23 | force_autohinter=false
24 | hinting=1
25 | subpixel_positioning=1
26 | oversampling=0.0
27 | Fallbacks=null
28 | fallbacks=[]
29 | Compress=null
30 | compress=true
31 | preload=[{
32 | "chars": [],
33 | "glyphs": [],
34 | "name": "New Configuration",
35 | "size": Vector2i(16, 0)
36 | }]
37 | language_support={}
38 | script_support={}
39 | opentype_features={}
40 |
--------------------------------------------------------------------------------
/app/assets/fonts/MonospaceBold.ttf.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="font_data_dynamic"
4 | type="FontFile"
5 | uid="uid://k88yrpapfi5e"
6 | path="res://.godot/imported/MonospaceBold.ttf-85e6bb30ac22368fdf6bd988771bb3ef.fontdata"
7 |
8 | [deps]
9 |
10 | source_file="res://assets/fonts/MonospaceBold.ttf"
11 | dest_files=["res://.godot/imported/MonospaceBold.ttf-85e6bb30ac22368fdf6bd988771bb3ef.fontdata"]
12 |
13 | [params]
14 |
15 | Rendering=null
16 | antialiasing=1
17 | generate_mipmaps=true
18 | disable_embedded_bitmaps=true
19 | multichannel_signed_distance_field=false
20 | msdf_pixel_range=8
21 | msdf_size=48
22 | allow_system_fallback=true
23 | force_autohinter=false
24 | hinting=1
25 | subpixel_positioning=1
26 | oversampling=0.0
27 | Fallbacks=null
28 | fallbacks=[]
29 | Compress=null
30 | compress=true
31 | preload=[{
32 | "chars": [],
33 | "glyphs": [],
34 | "name": "New Configuration",
35 | "size": Vector2i(16, 0)
36 | }]
37 | language_support={}
38 | script_support={}
39 | opentype_features={}
40 |
--------------------------------------------------------------------------------
/app/assets/textures/close_tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
28 |
--------------------------------------------------------------------------------
/app/assets/textures/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
30 |
--------------------------------------------------------------------------------
/app/resources/renderer_executable.tres:
--------------------------------------------------------------------------------
1 | [gd_resource type="Resource" script_class="RendererExecutable" load_steps=3 format=3 uid="uid://cmb7xvbue74qa"]
2 |
3 | [ext_resource type="Resource" uid="uid://cjcdum6fm4ta0" path="res://resources/api_settings.tres" id="1_l5jt7"]
4 | [ext_resource type="Script" uid="uid://d10tol3dqm8hy" path="res://scripts/renderer/renderer_executable.gd" id="1_q0dqh"]
5 |
6 | [resource]
7 | script = ExtResource("1_q0dqh")
8 | api_settings = ExtResource("1_l5jt7")
9 | supported_godot_versions = Array[String](["4.3", "4.5"])
10 | current_godot_version = "4.5"
11 | linux = "renderer/Renderer-godot_v%s.x86_64"
12 | linux_debug = "godot.linuxbsd.template_debug.dev.renderer.x86_64.llvm"
13 | windows = "renderer/Renderer-godot_v%s.exe"
14 | windows_debug = "godot.windows.template_debug.dev.renderer.x86_64.llvm.exe"
15 | macos = "renderer/Renderer-godot_v%s.universal"
16 | macos_framework = "../Frameworks/Renderer-godot_v%s.universal"
17 | macos_debug = "godot.macos.template_debug.dev.renderer.arm64"
18 |
--------------------------------------------------------------------------------
/app/app_icon/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dyenb2x6xyguv"
6 | path="res://.godot/imported/icon.svg-f3959d09c40a20b6aa50d6b4ce0c6c4f.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://app_icon/icon.svg"
14 | dest_files=["res://.godot/imported/icon.svg-f3959d09c40a20b6aa50d6b4ce0c6c4f.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=4
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=true
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=0
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/app/assets/textures/search_96.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TheGates browser
2 |
3 | Free and open-source 3D internet browser build with Godot Engine
4 | It connects game experiences together like world wide web and allows you to easily access them without installing
5 |
6 | [Documentation](https://thegates.readthedocs.io)
7 | [Other links](https://lnk.bio/thegates)
8 |
9 | ## Screenshots
10 |
11 |
12 |
13 |
14 |
15 | ## Build
16 |
17 | #### 1. Build godot submodule:
18 |
19 | Editor:
20 | ```
21 | scons -j $(nproc) dev_build=yes tg_renderer=no compiledb=yes use_llvm=yes linker=lld disable_exceptions=no
22 | ```
23 |
24 | Renderer:
25 | ```
26 | scons -j $(nproc) dev_build=yes target=template_debug tg_renderer=yes compiledb=yes use_llvm=yes linker=lld disable_exceptions=no
27 | ```
28 |
29 | #### 2. Run project
30 |
31 | Start compiled editor and open godot project inside **app** folder
32 |
--------------------------------------------------------------------------------
/app/assets/textures/home.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
24 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/history.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name History
3 |
4 | var history: Array[String] = [""]
5 | var index := 0
6 |
7 |
8 | func get_current() -> String:
9 | if index == -1: return ""
10 | return history[index]
11 |
12 |
13 | func can_forw() -> bool:
14 | return index + 1 < history.size()
15 |
16 |
17 | func can_back() -> bool:
18 | return index > 0
19 |
20 |
21 | func add(url: String) -> void:
22 | if url == get_current(): return
23 |
24 | index += 1
25 | history.resize(index)
26 | history.push_back(url)
27 | print_history()
28 |
29 |
30 | func forw() -> String:
31 | index += 1
32 | print_history()
33 | return history[index]
34 |
35 |
36 | func back() -> String:
37 | index -= 1
38 | print_history()
39 | if index == -1:
40 | return ""
41 | return history[index]
42 |
43 |
44 | func clear() -> void:
45 | index = -1
46 | history.clear()
47 | print_history()
48 |
49 |
50 | func print_history() -> void:
51 | # Debug.logclr("History: " + str(history) + " Current: " + str(index), Color.DIM_GRAY)
52 | pass
53 |
--------------------------------------------------------------------------------
/app/assets/textures/clock.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
31 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/foreground.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var gate_events: GateEvents
4 | @export var ui_events: UiEvents
5 | @export var splash_screen: TextureRect
6 | @export var vignette_blur: VignetteBlur
7 |
8 |
9 | func _ready() -> void:
10 | vignette_blur.hide()
11 |
12 | gate_events.call_or_subscribe(GateEvents.Early.IMAGE_LOADED, show_thumbnail)
13 | gate_events.first_frame.connect(on_first_frame)
14 | ui_events.ui_mode_changed.connect(on_ui_mode_changed)
15 |
16 |
17 | func show_thumbnail(gate: Gate) -> void:
18 | splash_screen.texture = FileTools.load_external_tex(gate.image)
19 | if not is_instance_valid(splash_screen.texture): return
20 | vignette_blur.show()
21 | vignette_blur.thumbnail_params()
22 |
23 |
24 | func on_first_frame() -> void:
25 | splash_screen.hide()
26 | vignette_blur.show()
27 | vignette_blur.gate_started_params()
28 |
29 |
30 | func on_ui_mode_changed(mode: UiEvents.UiMode) -> void:
31 | if mode == UiEvents.UiMode.INITIAL:
32 | show()
33 |
34 | if mode == UiEvents.UiMode.FOCUSED:
35 | hide()
36 |
--------------------------------------------------------------------------------
/app/assets/textures/empty_icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://6k1ia4pidwrq"
6 | path="res://.godot/imported/empty_icon.svg-b721453304feda680f1ac1f81d30ae65.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://assets/textures/empty_icon.svg"
14 | dest_files=["res://.godot/imported/empty_icon.svg-b721453304feda680f1ac1f81d30ae65.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/app/scenes/autoloads/http_client_pool.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=6 format=3 uid="uid://c1gmu0pvh6032"]
2 |
3 | [ext_resource type="Script" uid="uid://ywqluobp70qb" path="res://scripts/networking/http_client_pool.gd" id="1_ajfkt"]
4 | [ext_resource type="Script" uid="uid://c8dgkvbswd3mn" path="res://scripts/networking/http_pool_maintainer.gd" id="2_g7qo3"]
5 | [ext_resource type="Script" uid="uid://df4dj5a6cnf7w" path="res://scripts/networking/http_endpoint.gd" id="3_3e4dj"]
6 |
7 | [sub_resource type="Resource" id="Resource_lnpva"]
8 | script = ExtResource("3_3e4dj")
9 | host = "app.thegates.io"
10 | desired_connections = 5
11 |
12 | [sub_resource type="Resource" id="Resource_jp50e"]
13 | script = ExtResource("3_3e4dj")
14 | host = "thegates.io"
15 | desired_connections = 7
16 |
17 | [node name="HTTPClientPool" type="Node"]
18 | script = ExtResource("1_ajfkt")
19 |
20 | [node name="HTTPPoolMaintainer" type="Node" parent="."]
21 | script = ExtResource("2_g7qo3")
22 | endpoints = Array[ExtResource("3_3e4dj")]([SubResource("Resource_lnpva"), SubResource("Resource_jp50e")])
23 |
--------------------------------------------------------------------------------
/app/scripts/resources/api_settings.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name ApiSettings
3 |
4 | enum HostType {
5 | Local,
6 | Remote
7 | }
8 |
9 | @export var local_url: String
10 | @export var remote_url: String
11 | @export var host_type: HostType
12 | @export var trusted_urls: Array[String]
13 |
14 | var url: String :
15 | get: return local_url if host_type == HostType.Local else remote_url
16 |
17 | var analytics_event: String :
18 | get: return url + "/api/analytics_event"
19 |
20 | var create_user_id: String :
21 | get: return url + "/api/create_user_id?device_id="
22 |
23 | var discover_gate: String :
24 | get: return url + "/api/discover_gate"
25 |
26 | var featured_gates: String :
27 | get: return url + "/api/featured_gates"
28 |
29 | var search: String :
30 | get: return url + "/api/search?query="
31 |
32 | var prompt: String :
33 | get: return url + "/api/prompt?query="
34 |
35 | var send_logs: String :
36 | get: return url + "/api/send_logs?url="
37 |
38 | var download_renderer: String :
39 | get: return url + "/api/download_renderer/%s-%s" # platform-godot_version
40 |
--------------------------------------------------------------------------------
/app/scenes/components/notification/notification_bar.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://b5fqa6box556e"]
2 |
3 | [ext_resource type="PackedScene" uid="uid://1a4h8k7mfv1e" path="res://scenes/components/notification/notification.tscn" id="1_gead4"]
4 | [ext_resource type="Script" uid="uid://ck5x1jvxte7yl" path="res://scripts/ui/notification/notification_bar.gd" id="1_rgplm"]
5 |
6 | [node name="NotificationBar" type="Control" node_paths=PackedStringArray("container")]
7 | top_level = true
8 | z_index = 20
9 | layout_mode = 3
10 | anchors_preset = 1
11 | anchor_left = 1.0
12 | anchor_right = 1.0
13 | offset_left = -312.0
14 | offset_top = 125.0
15 | offset_right = -12.0
16 | offset_bottom = 638.0
17 | grow_horizontal = 0
18 | mouse_filter = 2
19 | mouse_behavior_recursive = 1
20 | script = ExtResource("1_rgplm")
21 | notification_scene = ExtResource("1_gead4")
22 | container = NodePath("VBoxContainer")
23 |
24 | [node name="VBoxContainer" type="VBoxContainer" parent="."]
25 | layout_mode = 1
26 | anchors_preset = 15
27 | anchor_right = 1.0
28 | anchor_bottom = 1.0
29 | grow_horizontal = 2
30 | grow_vertical = 2
31 |
--------------------------------------------------------------------------------
/app/scripts/api/discover_gate.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var api: ApiSettings
4 | @export var gate_events: GateEvents
5 |
6 |
7 | func _ready() -> void:
8 | gate_events.gate_config_loaded.connect(send_discover_gate)
9 |
10 |
11 | func send_discover_gate(c_url: String, c_gate: ConfigGate) -> void:
12 | if not c_gate.discoverable:
13 | Debug.logclr("Gate is not discoverable", Color.DIM_GRAY)
14 | return
15 |
16 | var body = {}
17 | body.url = c_url
18 | body.title = c_gate.title
19 | body.description = c_gate.description
20 | body.icon = c_gate.icon_url
21 | body.image = c_gate.image_url
22 | body.resource_pack = c_gate.resource_pack_url
23 | body.godot_version = c_gate.godot_version
24 | body.libraries = c_gate.libraries
25 |
26 | var url = api.discover_gate
27 | var callback = func(_result, code, _headers, _body):
28 | if code != 200: Debug.logclr("Request send_discover_gate failed. Code " + str(code), Color.RED)
29 |
30 | var err = await Backend.request(url, callback, body, HTTPClient.METHOD_POST)
31 | if err != OK: Debug.logclr("Cannot send request send_discover_gate", Color.RED)
32 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/close_button.gd:
--------------------------------------------------------------------------------
1 | extends Button
2 |
3 | @export var content: Control
4 | @export var tween_duration: float
5 | @export var normal_modulate: Color
6 | @export var hover_scale: float
7 |
8 | var tween: Tween
9 |
10 |
11 | func _ready() -> void:
12 | mouse_entered.connect(on_mouse_entered)
13 | mouse_exited.connect(on_mouse_exited)
14 | on_mouse_exited()
15 |
16 |
17 | func on_mouse_entered() -> void:
18 | if is_instance_valid(tween): tween.stop()
19 | tween = create_tween()
20 | tween.set_parallel(true)
21 |
22 | tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
23 | tween.tween_property(content, "scale", Vector2.ONE * hover_scale, tween_duration)
24 | tween.tween_property(content, "modulate", Color.WHITE, tween_duration)
25 |
26 |
27 | func on_mouse_exited() -> void:
28 | if is_instance_valid(tween): tween.stop()
29 | tween = create_tween()
30 | tween.set_parallel(true)
31 |
32 | tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
33 | tween.tween_property(content, "scale", Vector2.ONE, tween_duration)
34 | tween.tween_property(content, "modulate", normal_modulate, tween_duration)
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-2024 Nordup Ondar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/app/scripts/navigation.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | #class_name Navigation
3 |
4 | signal updated()
5 |
6 | @export var gate_events: GateEvents
7 | @export var history: History
8 |
9 |
10 | func _ready() -> void:
11 | gate_events.open_gate.connect(new)
12 | gate_events.search.connect(new)
13 | gate_events.exit_gate.connect(new.bind(""))
14 |
15 |
16 | func can_forw() -> bool:
17 | return history.can_forw()
18 |
19 |
20 | func can_back() -> bool:
21 | return history.can_back()
22 |
23 |
24 | func new(location: String) -> void:
25 | history.add(location)
26 | updated.emit()
27 |
28 |
29 | func go_back() -> void:
30 | open(history.back())
31 | updated.emit()
32 |
33 |
34 | func go_forw() -> void:
35 | open(history.forw())
36 | updated.emit()
37 |
38 |
39 | func reload() -> void:
40 | open(history.get_current())
41 | updated.emit()
42 |
43 |
44 | func home() -> void:
45 | gate_events.exit_gate_emit()
46 |
47 |
48 | func open(location: String) -> void:
49 | if location == "":
50 | gate_events.exit_gate_emit()
51 | elif Url.is_valid(location):
52 | gate_events.open_gate_emit(location)
53 | else:
54 | gate_events.search_emit(location)
55 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/bookmark_jump_animation.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name BookmarkJumpAnimation
3 |
4 | var base_position: Vector2
5 | var base_z_index: int
6 | var tween: Tween
7 |
8 |
9 | func start_jump_animation() -> void:
10 | base_position = position
11 | base_z_index = z_index
12 | z_index = 1
13 |
14 | var up_position: Vector2 = base_position + Vector2(0, -6)
15 | var down_position: Vector2 = base_position + Vector2(0, 6)
16 |
17 | if is_instance_valid(tween): tween.stop()
18 | tween = create_tween()
19 | tween.set_loops()
20 |
21 | tween.tween_interval(1.0)
22 | tween.tween_property(self, "position", down_position, 0.15).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
23 | tween.tween_property(self, "position", up_position, 0.2).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
24 | tween.tween_property(self, "position", base_position, 0.15).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
25 |
26 |
27 | func stop_jump_animation() -> void:
28 | if is_instance_valid(tween): tween.stop()
29 | position = base_position
30 | z_index = base_z_index
31 |
32 |
33 | func _exit_tree() -> void:
34 | stop_jump_animation()
35 |
--------------------------------------------------------------------------------
/.cursor/rules/godot-cursorrules-LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 BlueBirdBack ✨
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/app/addons/max_size_container/icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d34gqxl8s8vne"
6 | path.s3tc="res://.godot/imported/icon.png-a7092b8839b5f5cb0339bb8ff99972fb.s3tc.ctex"
7 | path.etc2="res://.godot/imported/icon.png-a7092b8839b5f5cb0339bb8ff99972fb.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://addons/max_size_container/icon.png"
16 | dest_files=["res://.godot/imported/icon.png-a7092b8839b5f5cb0339bb8ff99972fb.s3tc.ctex", "res://.godot/imported/icon.png-a7092b8839b5f5cb0339bb8ff99972fb.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=false
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 |
--------------------------------------------------------------------------------
/app/scripts/loading/file_tools.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name FileTools
3 |
4 |
5 | static func remove_recursive(path: String) -> void:
6 | if not DirAccess.dir_exists_absolute(path) and not FileAccess.file_exists(path): return
7 |
8 | var dir = DirAccess.open(path)
9 | if dir:
10 | # List directory content
11 | var err : Error
12 | dir.list_dir_begin()
13 | var file_name = dir.get_next()
14 | while file_name != "":
15 | if dir.current_is_dir():
16 | remove_recursive(path + "/" + file_name)
17 | else:
18 | err = dir.remove(file_name)
19 | if err != OK: Debug.logerr("Error removing: " + path + "/" + file_name)
20 | file_name = dir.get_next()
21 |
22 | # Remove current path
23 | err = dir.remove(path)
24 | if err != OK: Debug.logerr("Error removing: " + path)
25 | else:
26 | Debug.logerr("Error removing " + path)
27 |
28 |
29 | static func load_external_tex(path: String) -> Texture2D:
30 | if path.begins_with("res://"): return load(path)
31 | if not FileAccess.file_exists(path): return null
32 |
33 | var image = Image.load_from_file(path)
34 | if not is_instance_valid(image): return null
35 | return ImageTexture.create_from_image(image) as Texture2D
36 |
--------------------------------------------------------------------------------
/app/assets/textures/menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
32 |
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/2_friends.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://jfikupu1mgfb"
6 | path.bptc="res://.godot/imported/2_friends.png-b4376faf98cc7c5328c488e6491a0a23.bptc.ctex"
7 | path.astc="res://.godot/imported/2_friends.png-b4376faf98cc7c5328c488e6491a0a23.astc.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/onboarding/2_friends.png"
16 | dest_files=["res://.godot/imported/2_friends.png-b4376faf98cc7c5328c488e6491a0a23.bptc.ctex", "res://.godot/imported/2_friends.png-b4376faf98cc7c5328c488e6491a0a23.astc.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=true
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 |
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/3_browser.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://oppo8nj5bqrf"
6 | path.bptc="res://.godot/imported/3_browser.png-f7fc07114932c62d06151dbc368b0e60.bptc.ctex"
7 | path.astc="res://.godot/imported/3_browser.png-f7fc07114932c62d06151dbc368b0e60.astc.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/onboarding/3_browser.png"
16 | dest_files=["res://.godot/imported/3_browser.png-f7fc07114932c62d06151dbc368b0e60.bptc.ctex", "res://.godot/imported/3_browser.png-f7fc07114932c62d06151dbc368b0e60.astc.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=true
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 |
--------------------------------------------------------------------------------
/app/scripts/platform.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name Platform
3 |
4 | enum {
5 | WINDOWS,
6 | MACOS,
7 | LINUX_BSD,
8 | ANDROID,
9 | IOS,
10 | WEB
11 | }
12 |
13 | static var platform_to_string: Dictionary = {
14 | WINDOWS: "windows",
15 | MACOS: "macos",
16 | LINUX_BSD: "linux",
17 | ANDROID: "android",
18 | IOS: "ios",
19 | WEB: "web"
20 | }
21 |
22 |
23 | static func is_windows() -> bool:
24 | return get_platform() == WINDOWS
25 |
26 |
27 | static func is_linux() -> bool:
28 | return get_platform() == LINUX_BSD
29 |
30 |
31 | static func is_macos() -> bool:
32 | return get_platform() == MACOS
33 |
34 |
35 | static func is_debug() -> bool:
36 | return OS.is_debug_build()
37 |
38 |
39 | static func get_platform() -> int:
40 | match OS.get_name():
41 | "Windows", "UWP":
42 | return WINDOWS
43 | "macOS":
44 | return MACOS
45 | "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD":
46 | return LINUX_BSD
47 | "Android":
48 | return ANDROID
49 | "iOS":
50 | return IOS
51 | "Web":
52 | return WEB
53 | _:
54 | assert(false, "No such platform")
55 | return -1
56 |
57 |
58 | static func get_platform_string() -> String:
59 | return platform_to_string[get_platform()]
60 |
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/1_internet.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://xvojwttib03s"
6 | path.bptc="res://.godot/imported/1_internet.png-54c4d2724e516132d150e3b8d2d69baa.bptc.ctex"
7 | path.astc="res://.godot/imported/1_internet.png-54c4d2724e516132d150e3b8d2d69baa.astc.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/onboarding/1_internet.png"
16 | dest_files=["res://.godot/imported/1_internet.png-54c4d2724e516132d150e3b8d2d69baa.bptc.ctex", "res://.godot/imported/1_internet.png-54c4d2724e516132d150e3b8d2d69baa.astc.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=true
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 |
--------------------------------------------------------------------------------
/app/assets/textures/reload.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
28 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/window_buttons.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var minimize: BaseButton
4 | @export var maximize: BaseButton
5 | @export var exit: BaseButton
6 | @export var restored_window_ratio: float = 0.75
7 |
8 |
9 | func _ready() -> void:
10 | minimize.pressed.connect(on_minimize)
11 | maximize.pressed.connect(on_maximize)
12 | exit.pressed.connect(on_exit)
13 |
14 |
15 | func on_minimize() -> void:
16 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
17 |
18 |
19 | func on_maximize() -> void:
20 | var mode = DisplayServer.window_get_mode()
21 | if mode == DisplayServer.WINDOW_MODE_WINDOWED:
22 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MAXIMIZED)
23 | else:
24 | restore_from_maximized()
25 |
26 |
27 | func on_exit() -> void:
28 | get_tree().quit()
29 |
30 |
31 | func restore_from_maximized() -> void:
32 | var usable: Rect2i = DisplayServer.screen_get_usable_rect(DisplayServer.window_get_current_screen())
33 | var target_size: Vector2i = Vector2i(int(usable.size.x * restored_window_ratio), int(usable.size.y * restored_window_ratio))
34 | DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
35 | DisplayServer.window_set_size(target_size)
36 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notification_manager.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name NotificationManager
3 |
4 | @export var notification_bar: NotificationBar
5 |
6 | var notifier_to_notification: Dictionary = {}
7 |
8 |
9 | func _ready() -> void:
10 | for child in get_children():
11 | register_notifier(child)
12 |
13 |
14 | func register_notifier(node: Node) -> void:
15 | var notifier: NotifierBase = node
16 | notifier.show.connect(on_notification_show.bind(notifier))
17 | notifier.hide.connect(on_notification_hide.bind(notifier))
18 |
19 |
20 | func on_notification_show(message: String, icon: Texture2D, notifier: NotifierBase) -> void:
21 | hide_for_notification(notifier)
22 |
23 | var ntf: Notification = notification_bar.show_notification(message, icon)
24 | notifier_to_notification[notifier] = ntf
25 |
26 |
27 | func on_notification_hide(notifier: NotifierBase) -> void:
28 | hide_for_notification(notifier)
29 |
30 |
31 | func hide_for_notification(notifier: NotifierBase) -> void:
32 | if not notifier_to_notification.has(notifier): return
33 |
34 | var ntf: Notification = notifier_to_notification[notifier]
35 | notification_bar.hide_notification(ntf)
36 | notifier_to_notification.erase(notifier)
37 |
--------------------------------------------------------------------------------
/app/assets/textures/onboarding/4_tutorial.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c86h7yd3klvo8"
6 | path.bptc="res://.godot/imported/4_tutorial.png-e6cfef9c569f635a4688d5e2fd23f3be.bptc.ctex"
7 | path.astc="res://.godot/imported/4_tutorial.png-e6cfef9c569f635a4688d5e2fd23f3be.astc.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/onboarding/4_tutorial.png"
16 | dest_files=["res://.godot/imported/4_tutorial.png-e6cfef9c569f635a4688d5e2fd23f3be.bptc.ctex", "res://.godot/imported/4_tutorial.png-e6cfef9c569f635a4688d5e2fd23f3be.astc.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=true
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 |
--------------------------------------------------------------------------------
/app/assets/textures/home.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://mgtj316adcja"
6 | path.s3tc="res://.godot/imported/home.svg-7020e35469bd96bdbace0b1620fa75bf.s3tc.ctex"
7 | path.etc2="res://.godot/imported/home.svg-7020e35469bd96bdbace0b1620fa75bf.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/home.svg"
16 | dest_files=["res://.godot/imported/home.svg-7020e35469bd96bdbace0b1620fa75bf.s3tc.ctex", "res://.godot/imported/home.svg-7020e35469bd96bdbace0b1620fa75bf.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/menu.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://yn1l3ybpr7qv"
6 | path.s3tc="res://.godot/imported/menu.svg-c73b5f81f5b5e8b51e5be4983b436a57.s3tc.ctex"
7 | path.etc2="res://.godot/imported/menu.svg-c73b5f81f5b5e8b51e5be4983b436a57.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/menu.svg"
16 | dest_files=["res://.godot/imported/menu.svg-c73b5f81f5b5e8b51e5be4983b436a57.s3tc.ctex", "res://.godot/imported/menu.svg-c73b5f81f5b5e8b51e5be4983b436a57.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/flag.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://casdqby073onf"
6 | path.s3tc="res://.godot/imported/flag.svg-03474bc81b8cfd6a5a0482509e90869d.s3tc.ctex"
7 | path.etc2="res://.godot/imported/flag.svg-03474bc81b8cfd6a5a0482509e90869d.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/flag.svg"
16 | dest_files=["res://.godot/imported/flag.svg-03474bc81b8cfd6a5a0482509e90869d.s3tc.ctex", "res://.godot/imported/flag.svg-03474bc81b8cfd6a5a0482509e90869d.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/gate.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cx3xxaqo3s3eq"
6 | path.s3tc="res://.godot/imported/gate.svg-b8b56b580c46301f31585d4c8a52c812.s3tc.ctex"
7 | path.etc2="res://.godot/imported/gate.svg-b8b56b580c46301f31585d4c8a52c812.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/gate.svg"
16 | dest_files=["res://.godot/imported/gate.svg-b8b56b580c46301f31585d4c8a52c812.s3tc.ctex", "res://.godot/imported/gate.svg-b8b56b580c46301f31585d4c8a52c812.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/help.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dy5lmm2egk4w1"
6 | path.s3tc="res://.godot/imported/help.svg-5d318400ba147d34234c00bf256aa56a.s3tc.ctex"
7 | path.etc2="res://.godot/imported/help.svg-5d318400ba147d34234c00bf256aa56a.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/help.svg"
16 | dest_files=["res://.godot/imported/help.svg-5d318400ba147d34234c00bf256aa56a.s3tc.ctex", "res://.godot/imported/help.svg-5d318400ba147d34234c00bf256aa56a.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/plus.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c80732g67qmvx"
6 | path.s3tc="res://.godot/imported/plus.svg-1e729e0158e3f0f3340726d96bd7fa20.s3tc.ctex"
7 | path.etc2="res://.godot/imported/plus.svg-1e729e0158e3f0f3340726d96bd7fa20.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/plus.svg"
16 | dest_files=["res://.godot/imported/plus.svg-1e729e0158e3f0f3340726d96bd7fa20.s3tc.ctex", "res://.godot/imported/plus.svg-1e729e0158e3f0f3340726d96bd7fa20.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/star.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c7ljriip50hte"
6 | path.s3tc="res://.godot/imported/star.svg-8291fc775f0fdf63f866e08da04fee34.s3tc.ctex"
7 | path.etc2="res://.godot/imported/star.svg-8291fc775f0fdf63f866e08da04fee34.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/star.svg"
16 | dest_files=["res://.godot/imported/star.svg-8291fc775f0fdf63f866e08da04fee34.s3tc.ctex", "res://.godot/imported/star.svg-8291fc775f0fdf63f866e08da04fee34.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/clock.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d05w6jtfy01w2"
6 | path.s3tc="res://.godot/imported/clock.svg-f120a2f8f26bcc69c0d6661da365ab74.s3tc.ctex"
7 | path.etc2="res://.godot/imported/clock.svg-f120a2f8f26bcc69c0d6661da365ab74.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/clock.svg"
16 | dest_files=["res://.godot/imported/clock.svg-f120a2f8f26bcc69c0d6661da365ab74.s3tc.ctex", "res://.godot/imported/clock.svg-f120a2f8f26bcc69c0d6661da365ab74.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/close.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bevejhgdw7mey"
6 | path.s3tc="res://.godot/imported/close.svg-2bbf7d638476fa1634995c035148cdeb.s3tc.ctex"
7 | path.etc2="res://.godot/imported/close.svg-2bbf7d638476fa1634995c035148cdeb.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/close.svg"
16 | dest_files=["res://.godot/imported/close.svg-2bbf7d638476fa1634995c035148cdeb.s3tc.ctex", "res://.godot/imported/close.svg-2bbf7d638476fa1634995c035148cdeb.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/gate_info.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var gate_events: GateEvents
4 |
5 | @export var image: TextureRect
6 | @export var image_darken: Control
7 | @export var title: RichTextLabel
8 | @export var description: RichTextLabel
9 | @export var gate_status: Array[Control]
10 |
11 | var gate: Gate
12 |
13 |
14 | func _ready() -> void:
15 | clear_info()
16 |
17 | gate_events.call_or_subscribe(GateEvents.Early.IMAGE_LOADED, display_info)
18 | gate_events.first_frame.connect(on_first_frame)
19 | gate_events.gate_error.connect(on_gate_error)
20 |
21 |
22 | func display_info(_gate: Gate) -> void:
23 | gate = _gate
24 | title.text = "Unnamed" if gate.title.is_empty() else gate.title
25 | description.text = "No description" if gate.description.is_empty() else gate.description
26 | image.texture = FileTools.load_external_tex(gate.image)
27 | if is_instance_valid(image.texture): image_darken.show()
28 |
29 |
30 | func clear_info() -> void:
31 | gate = null
32 | title.text = ""
33 | description.text = ""
34 | image.texture = null
35 | image_darken.hide()
36 |
37 |
38 | func on_first_frame() -> void:
39 | for node in gate_status:
40 | node.hide()
41 |
42 |
43 | func on_gate_error(_code: GateEvents.GateError) -> void:
44 | description.set_text("")
45 |
--------------------------------------------------------------------------------
/app/assets/textures/reload.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dapysvexbecnd"
6 | path.s3tc="res://.godot/imported/reload.svg-49e4e4e7f844f8800574945a0b3ed387.s3tc.ctex"
7 | path.etc2="res://.godot/imported/reload.svg-49e4e4e7f844f8800574945a0b3ed387.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/reload.svg"
16 | dest_files=["res://.godot/imported/reload.svg-49e4e4e7f844f8800574945a0b3ed387.s3tc.ctex", "res://.godot/imported/reload.svg-49e4e4e7f844f8800574945a0b3ed387.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/search.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://byrcelmfy6r3l"
6 | path.s3tc="res://.godot/imported/search.svg-e1c1dd5f4f7968f8ab58b74915019629.s3tc.ctex"
7 | path.etc2="res://.godot/imported/search.svg-e1c1dd5f4f7968f8ab58b74915019629.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/search.svg"
16 | dest_files=["res://.godot/imported/search.svg-e1c1dd5f4f7968f8ab58b74915019629.s3tc.ctex", "res://.godot/imported/search.svg-e1c1dd5f4f7968f8ab58b74915019629.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/starred.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bal06qnhvdupj"
6 | path.s3tc="res://.godot/imported/starred.svg-36a0f7cc2494fdef5752549b66f18b79.s3tc.ctex"
7 | path.etc2="res://.godot/imported/starred.svg-36a0f7cc2494fdef5752549b66f18b79.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/starred.svg"
16 | dest_files=["res://.godot/imported/starred.svg-36a0f7cc2494fdef5752549b66f18b79.s3tc.ctex", "res://.godot/imported/starred.svg-36a0f7cc2494fdef5752549b66f18b79.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/maximaze.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://csjqc48oqb0kc"
6 | path.s3tc="res://.godot/imported/maximaze.svg-1abee8513fb0a5381e9c922be2a32ac6.s3tc.ctex"
7 | path.etc2="res://.godot/imported/maximaze.svg-1abee8513fb0a5381e9c922be2a32ac6.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/maximaze.svg"
16 | dest_files=["res://.godot/imported/maximaze.svg-1abee8513fb0a5381e9c922be2a32ac6.s3tc.ctex", "res://.godot/imported/maximaze.svg-1abee8513fb0a5381e9c922be2a32ac6.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/minimize.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d1ehc1xs5hwyn"
6 | path.s3tc="res://.godot/imported/minimize.svg-da0875c5f49c7e85702b1ddd89b760c9.s3tc.ctex"
7 | path.etc2="res://.godot/imported/minimize.svg-da0875c5f49c7e85702b1ddd89b760c9.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/minimize.svg"
16 | dest_files=["res://.godot/imported/minimize.svg-da0875c5f49c7e85702b1ddd89b760c9.s3tc.ctex", "res://.godot/imported/minimize.svg-da0875c5f49c7e85702b1ddd89b760c9.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/close_tab.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://cli88m5w3op6l"
6 | path.s3tc="res://.godot/imported/close_tab.svg-08bfa5eeac7645dd90356ed53f9b93fe.s3tc.ctex"
7 | path.etc2="res://.godot/imported/close_tab.svg-08bfa5eeac7645dd90356ed53f9b93fe.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/close_tab.svg"
16 | dest_files=["res://.godot/imported/close_tab.svg-08bfa5eeac7645dd90356ed53f9b93fe.s3tc.ctex", "res://.godot/imported/close_tab.svg-08bfa5eeac7645dd90356ed53f9b93fe.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/search_96.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://ckkmluvnrorek"
6 | path.s3tc="res://.godot/imported/search_96.svg-7bac5ed7ed38b01b3e5625e37f70928d.s3tc.ctex"
7 | path.etc2="res://.godot/imported/search_96.svg-7bac5ed7ed38b01b3e5625e37f70928d.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/search_96.svg"
16 | dest_files=["res://.godot/imported/search_96.svg-7bac5ed7ed38b01b3e5625e37f70928d.s3tc.ctex", "res://.godot/imported/search_96.svg-7bac5ed7ed38b01b3e5625e37f70928d.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/star_color.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://itloduvrh00o"
6 | path.s3tc="res://.godot/imported/star_color.svg-27a593cde53c1e3bcdef9f671418b3df.s3tc.ctex"
7 | path.etc2="res://.godot/imported/star_color.svg-27a593cde53c1e3bcdef9f671418b3df.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/star_color.svg"
16 | dest_files=["res://.godot/imported/star_color.svg-27a593cde53c1e3bcdef9f671418b3df.s3tc.ctex", "res://.godot/imported/star_color.svg-27a593cde53c1e3bcdef9f671418b3df.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=true
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/arrow_left.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://c4dxov80fjvaq"
6 | path.s3tc="res://.godot/imported/arrow_left.svg-5ab98e077f2f35ab7f75f3a533d0c912.s3tc.ctex"
7 | path.etc2="res://.godot/imported/arrow_left.svg-5ab98e077f2f35ab7f75f3a533d0c912.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/arrow_left.svg"
16 | dest_files=["res://.godot/imported/arrow_left.svg-5ab98e077f2f35ab7f75f3a533d0c912.s3tc.ctex", "res://.godot/imported/arrow_left.svg-5ab98e077f2f35ab7f75f3a533d0c912.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/icon_round.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bw1cffhlt112i"
6 | path.s3tc="res://.godot/imported/icon_round.svg-c9fe5f8cab792f01a3e80f83a21fd50a.s3tc.ctex"
7 | path.etc2="res://.godot/imported/icon_round.svg-c9fe5f8cab792f01a3e80f83a21fd50a.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/icon_round.svg"
16 | dest_files=["res://.godot/imported/icon_round.svg-c9fe5f8cab792f01a3e80f83a21fd50a.s3tc.ctex", "res://.godot/imported/icon_round.svg-c9fe5f8cab792f01a3e80f83a21fd50a.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=0.75
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/assets/textures/arrow_right.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://bo1pkhkdscf6v"
6 | path.s3tc="res://.godot/imported/arrow_right.svg-6afd2f42625bae75970ec95e7c1d3305.s3tc.ctex"
7 | path.etc2="res://.godot/imported/arrow_right.svg-6afd2f42625bae75970ec95e7c1d3305.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/arrow_right.svg"
16 | dest_files=["res://.godot/imported/arrow_right.svg-6afd2f42625bae75970ec95e7c1d3305.s3tc.ctex", "res://.godot/imported/arrow_right.svg-6afd2f42625bae75970ec95e7c1d3305.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/hdr_compression=1
24 | compress/normal_map=0
25 | compress/channel_pack=0
26 | mipmaps/generate=true
27 | mipmaps/limit=-1
28 | roughness/mode=0
29 | roughness/src_normal=""
30 | process/fix_alpha_border=true
31 | process/premult_alpha=false
32 | process/normal_map_invert_y=false
33 | process/hdr_as_srgb=false
34 | process/hdr_clamp_exposure=false
35 | process/size_limit=0
36 | detect_3d/compress_to=1
37 | svg/scale=2.0
38 | editor/scale_with_editor_scale=false
39 | editor/convert_colors_with_editor_theme=false
40 |
--------------------------------------------------------------------------------
/app/scripts/renderer/unzip.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name UnZip
3 |
4 |
5 | static func extract_renderer_files(renderer_zip: String, renderer_path: String) -> bool:
6 | var reader = ZIPReader.new()
7 | var err = reader.open(renderer_zip)
8 | if err != OK: Debug.logclr("Cannot open file %s to unzip" % [renderer_zip], Color.RED); return false
9 |
10 | if not reader.file_exists(renderer_path.get_file()):
11 | Debug.logclr("Renderer file %s not found in zip %s" % [renderer_path.get_file(), renderer_zip], Color.RED); return false
12 | DirAccess.make_dir_recursive_absolute(renderer_path.get_base_dir())
13 |
14 | for filename in reader.get_files():
15 | if filename.contains("__MACOSX"): continue
16 |
17 | var file_path = renderer_path.get_base_dir() + "/" + filename
18 | var file = FileAccess.open(file_path, FileAccess.WRITE)
19 | if file == null: Debug.logclr("Cannot open file %s to write" % [file_path], Color.RED); return false
20 |
21 | var buffer = reader.read_file(filename)
22 | if not file.store_buffer(buffer): Debug.logclr("Cannot write to file %s" % [file_path], Color.RED); return false
23 | file.close()
24 |
25 | FileAccess.set_unix_permissions(renderer_path, FileAccess.get_unix_permissions(renderer_path) | FileAccess.UNIX_EXECUTE_OWNER)
26 | reader.close()
27 |
28 | return true
29 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search_status.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var gate_events: GateEvents
4 | @export var search_line_edit: LineEdit
5 |
6 | @export var search: Control
7 | @export var downloading: Control
8 | @export var success: Control
9 | @export var error: Control
10 |
11 | @export var white: Color
12 | @export var gray: Color
13 |
14 |
15 | func _ready() -> void:
16 | search_line_edit.text_changed.connect(func(_text): switch_to(search))
17 | gate_events.search.connect(func(_url): switch_to(search))
18 | gate_events.exit_gate.connect(func(): switch_to(search))
19 | gate_events.open_gate.connect(func(_url): switch_to(downloading))
20 | gate_events.gate_entered.connect(func(): switch_to(success))
21 | gate_events.gate_error.connect(func(_code): switch_to(error))
22 |
23 | switch_to(search)
24 |
25 |
26 | func switch_to(_state: Control) -> void:
27 | disable([search, downloading, success, error])
28 | _state.visible = true
29 | _state.process_mode = Node.PROCESS_MODE_INHERIT
30 | change_color.call_deferred()
31 |
32 |
33 | func change_color() -> void:
34 | modulate = gray if search_line_edit.text.is_empty() else white
35 |
36 |
37 | func disable(states: Array[Control]) -> void:
38 | for state in states:
39 | state.visible = false
40 | state.process_mode = Node.PROCESS_MODE_DISABLED
41 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/board.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name OnboardingBoard
3 |
4 | signal request_focus
5 |
6 | @export var focus_button: Button
7 | @export var unfocus_color: Color
8 | @export var unfocus_scale: Vector2
9 |
10 | var tween: Tween
11 |
12 |
13 | func _ready() -> void:
14 | focus_button.pressed.connect(func(): request_focus.emit())
15 | focus_button.visible = false
16 |
17 |
18 | func focus(tween_duration: float) -> void:
19 | if is_instance_valid(tween): tween.stop()
20 | tween = create_tween()
21 | tween.set_parallel(true)
22 |
23 | tween.tween_property(self, "scale", Vector2.ONE, tween_duration).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
24 | tween.tween_property(self, "modulate", Color.WHITE, tween_duration).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
25 |
26 | focus_button.visible = false
27 |
28 |
29 | func unfocus(tween_duration: float) -> void:
30 | if is_instance_valid(tween): tween.stop()
31 | tween = create_tween()
32 | tween.set_parallel(true)
33 |
34 | tween.tween_property(self, "scale", unfocus_scale, tween_duration).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
35 | tween.tween_property(self, "modulate", unfocus_color, tween_duration).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
36 |
37 | focus_button.visible = true
38 |
--------------------------------------------------------------------------------
/app/assets/textures/star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
--------------------------------------------------------------------------------
/app/assets/textures/starred.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_app.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderApp
3 |
4 | const HEARTBEAT_DELAY = 60
5 |
6 | var heartbeat_timer: Timer
7 |
8 |
9 | func start() -> void:
10 | super.start()
11 |
12 | send_saved_app_exit()
13 |
14 | analytics.send_event(AnalyticsEvents.app_open())
15 |
16 | AfkManager.state_changed.connect(on_state_changed)
17 | start_heartbeat()
18 |
19 |
20 | func start_heartbeat() -> void:
21 | heartbeat_timer = Timer.new()
22 | add_child(heartbeat_timer)
23 | heartbeat_timer.timeout.connect(send_hearbeat)
24 | heartbeat_timer.start(HEARTBEAT_DELAY)
25 |
26 |
27 | func send_hearbeat() -> void:
28 | var time_spent = AfkManager.get_active_sec()
29 | analytics.send_event(AnalyticsEvents.heartbeat(time_spent))
30 |
31 |
32 | func on_state_changed(is_afk: bool) -> void:
33 | if is_afk:
34 | heartbeat_timer.stop()
35 | return
36 |
37 | heartbeat_timer.start(HEARTBEAT_DELAY)
38 |
39 |
40 | func send_saved_app_exit() -> void:
41 | var json: String = DataSaver.get_string("analytics", "app_exit")
42 | if json.is_empty(): return
43 | DataSaver.set_value("analytics", "app_exit", "")
44 | analytics.send_event(JSON.parse_string(json))
45 |
46 |
47 | func _exit_tree() -> void:
48 | # Save to send on open
49 | var time_spent = AfkManager.get_active_sec()
50 | var event = AnalyticsEvents.app_exit(time_spent)
51 | DataSaver.set_value("analytics", "app_exit", JSON.stringify(event))
52 |
--------------------------------------------------------------------------------
/app/assets/textures/cursor.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://wuvhs6f87wud"
6 | path.s3tc="res://.godot/imported/cursor.svg-d54a5fc4568ba641d637af7b58b8cd6b.s3tc.ctex"
7 | path.etc2="res://.godot/imported/cursor.svg-d54a5fc4568ba641d637af7b58b8cd6b.etc2.ctex"
8 | metadata={
9 | "imported_formats": ["s3tc_bptc", "etc2_astc"],
10 | "vram_texture": true
11 | }
12 |
13 | [deps]
14 |
15 | source_file="res://assets/textures/cursor.svg"
16 | dest_files=["res://.godot/imported/cursor.svg-d54a5fc4568ba641d637af7b58b8cd6b.s3tc.ctex", "res://.godot/imported/cursor.svg-d54a5fc4568ba641d637af7b58b8cd6b.etc2.ctex"]
17 |
18 | [params]
19 |
20 | compress/mode=2
21 | compress/high_quality=false
22 | compress/lossy_quality=0.7
23 | compress/uastc_level=0
24 | compress/rdo_quality_loss=0.0
25 | compress/hdr_compression=1
26 | compress/normal_map=0
27 | compress/channel_pack=0
28 | mipmaps/generate=true
29 | mipmaps/limit=-1
30 | roughness/mode=0
31 | roughness/src_normal=""
32 | process/channel_remap/red=0
33 | process/channel_remap/green=1
34 | process/channel_remap/blue=2
35 | process/channel_remap/alpha=3
36 | process/fix_alpha_border=true
37 | process/premult_alpha=false
38 | process/normal_map_invert_y=false
39 | process/hdr_as_srgb=false
40 | process/hdr_clamp_exposure=false
41 | process/size_limit=0
42 | detect_3d/compress_to=1
43 | svg/scale=2.0
44 | editor/scale_with_editor_scale=false
45 | editor/convert_colors_with_editor_theme=false
46 |
--------------------------------------------------------------------------------
/app/scripts/loading/config_base.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name ConfigBase
3 |
4 | var config: ConfigFile
5 | var config_path: String
6 | var load_result: Error
7 |
8 |
9 | func _init(path: String) -> void:
10 | config = ConfigFile.new()
11 | load_result = config.load(path)
12 | config_path = path
13 |
14 |
15 | func get_string(section: String, key: String) -> String:
16 | var value: String
17 | if config.has_section_key(section, key):
18 | value = config.get_value(section, key)
19 | # Debug.logr(key + "=" + value)
20 | else: Debug.logclr("don't have section " + section + ", key " + key, Color.GRAY)
21 | return value
22 |
23 |
24 | func get_value(section: String, key: String, default: Variant = null) -> Variant:
25 | var value: Variant = default
26 | if config.has_section_key(section, key):
27 | value = config.get_value(section, key)
28 | # Debug.logr(key + "=" + str(value))
29 | else: Debug.logclr("don't have section " + section + ", key " + key, Color.GRAY)
30 | return value
31 |
32 |
33 | func get_sections() -> PackedStringArray:
34 | return config.get_sections()
35 |
36 |
37 | func get_section_keys(section: String) -> PackedStringArray:
38 | var keys: PackedStringArray
39 | if config.has_section(section):
40 | keys = config.get_section_keys(section)
41 | # Debug.logr(keys)
42 | else: Debug.logclr("don't have section " + section, Color.GRAY)
43 | return keys
44 |
45 |
46 | func set_value(section: String, key: String, value: Variant) -> void:
47 | config.set_value(section, key, value)
48 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notification.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name Notification
3 |
4 | @export var icon: TextureRect
5 | @export var message: Label
6 | @export var root: Control
7 | @export var appear_duration: float = 0.4
8 | @export var hide_duration: float = 0.3
9 | @export var start_offset: Vector2 = Vector2(312.0, 0.0)
10 |
11 | var tween: Tween
12 |
13 |
14 | func _ready() -> void:
15 | show_notification()
16 |
17 |
18 | func fill(_message: String, _icon: Texture2D) -> void:
19 | icon.texture = _icon
20 | message.text = _message
21 |
22 |
23 | func show_notification() -> void:
24 | var target_position: Vector2 = root.position
25 | root.position = target_position + start_offset
26 | root.modulate = Color(1.0, 1.0, 1.0, 0.0)
27 |
28 | if is_instance_valid(tween): tween.stop()
29 | tween = create_tween()
30 | tween.set_parallel(true)
31 |
32 | tween.set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
33 | tween.tween_property(root, "position", target_position, appear_duration)
34 | tween.tween_property(root, "modulate:a", 1.0, appear_duration)
35 |
36 |
37 | func hide_notification() -> void:
38 | var target_position: Vector2 = root.position + start_offset
39 |
40 | if is_instance_valid(tween): tween.stop()
41 | tween = create_tween()
42 | tween.set_parallel(true)
43 |
44 | tween.set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN)
45 | tween.tween_property(root, "position", target_position, hide_duration)
46 | tween.tween_property(root, "modulate:a", 0.0, hide_duration)
47 |
48 | await tween.finished
49 |
--------------------------------------------------------------------------------
/app/scripts/ui/world/not_responding.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | @export var gate_events: GateEvents
4 | @export var history: History
5 | @export var root: TextureButton
6 | @export var reload: Button
7 | @export var back: Button
8 | @export var fade_in: float = 1.0
9 | @export var fade_out: float = 0.2
10 |
11 | const SHOWN = Color(1, 1, 1, 1)
12 | const HIDDEN = Color(1, 1, 1, 0)
13 |
14 | var tween: Tween
15 |
16 |
17 | func _ready() -> void:
18 | gate_events.not_responding.connect(show_message)
19 | reload.pressed.connect(reload_gate)
20 | root.pressed.connect(hide_message)
21 | back.pressed.connect(Navigation.go_back)
22 |
23 | visible = true
24 | root.hide()
25 | root.modulate = HIDDEN
26 | root.mouse_filter = Control.MOUSE_FILTER_PASS
27 |
28 |
29 | func show_message() -> void:
30 | if root.visible: return
31 |
32 | root.show()
33 |
34 | if is_instance_valid(tween): tween.stop()
35 | tween = get_tree().create_tween()
36 | tween.tween_property(root, "modulate", SHOWN, fade_in)
37 | await tween.finished
38 |
39 | root.mouse_filter = Control.MOUSE_FILTER_STOP
40 |
41 |
42 | func hide_message() -> void:
43 | if not root.visible: return
44 |
45 | root.mouse_filter = Control.MOUSE_FILTER_PASS
46 |
47 | if is_instance_valid(tween): tween.stop()
48 | tween = get_tree().create_tween()
49 | tween.tween_property(root, "modulate", HIDDEN, fade_out)
50 | await tween.finished
51 |
52 | root.hide()
53 |
54 |
55 | func reload_gate() -> void:
56 | var location = history.get_current()
57 | gate_events.open_gate_emit(location)
58 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/round_button.gd:
--------------------------------------------------------------------------------
1 | extends Button
2 | class_name RoundButton
3 |
4 | @export var gate_events: GateEvents
5 | @export var command_events: CommandEvents
6 | @export var special_effect: Panel
7 |
8 | var button_id: String
9 | var is_highlighted: bool
10 |
11 |
12 | func _ready() -> void:
13 | if disabled: disable()
14 | else: enable()
15 |
16 | button_id = name.to_lower()
17 | special_effect.visible = false
18 | command_events.highlight_button.connect(highlight)
19 |
20 |
21 | func disable() -> void:
22 | disabled = true
23 | mouse_default_cursor_shape = Control.CURSOR_ARROW
24 | unhighlight()
25 |
26 |
27 | func enable() -> void:
28 | disabled = false
29 | mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
30 | unhighlight()
31 |
32 |
33 | func highlight(_button_id: String) -> void:
34 | if disabled or is_highlighted: return
35 | if button_id != _button_id: return
36 | if not Url.is_trusted_url(gate_events.current_gate_url): return
37 |
38 | special_effect.visible = true
39 | is_highlighted = true
40 |
41 | pressed.connect(unhighlight)
42 | gate_events.search.connect(unhighlight)
43 | gate_events.open_gate.connect(unhighlight)
44 | gate_events.exit_gate.connect(unhighlight)
45 |
46 |
47 | func unhighlight(_unbind: String = "") -> void:
48 | if not is_highlighted: return
49 |
50 | special_effect.visible = false
51 | is_highlighted = false
52 |
53 | pressed.disconnect(unhighlight)
54 | gate_events.search.disconnect(unhighlight)
55 | gate_events.open_gate.disconnect(unhighlight)
56 | gate_events.exit_gate.disconnect(unhighlight)
57 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/star.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var gate_events: GateEvents
4 | @export var bookmarks: Bookmarks
5 |
6 | @export var star: Control
7 | @export var unstar: Control
8 |
9 | var gate: Gate
10 | var url: String
11 |
12 |
13 | func _ready() -> void:
14 | star.visible = false
15 | unstar.visible = false
16 |
17 | gate_events.open_gate.connect(show_buttons)
18 | gate_events.open_gate.connect(update_gate_order)
19 | gate_events.search.connect(func(_query): hide_buttons())
20 | gate_events.exit_gate.connect(hide_buttons)
21 | gate_events.gate_info_loaded.connect(update_info)
22 | gate_events.gate_icon_loaded.connect(update_info)
23 | gate_events.gate_image_loaded.connect(update_info)
24 |
25 |
26 | func show_buttons(_url: String) -> void:
27 | url = _url
28 | if bookmarks.gates.has(url):
29 | star.visible = false
30 | unstar.visible = true
31 | else:
32 | star.visible = true
33 | unstar.visible = false
34 |
35 |
36 | func hide_buttons() -> void:
37 | star.visible = false
38 | unstar.visible = false
39 | gate = null
40 |
41 |
42 | func update_gate_order(_url: String) -> void:
43 | bookmarks.make_first(_url)
44 |
45 |
46 | func update_info(_gate: Gate) -> void:
47 | gate = _gate
48 | bookmarks.update(gate)
49 |
50 |
51 | func _on_star_pressed() -> void:
52 | if gate == null: return
53 |
54 | bookmarks.star(gate)
55 | star.visible = false
56 | unstar.visible = true
57 |
58 |
59 | func _on_unstar_pressed() -> void:
60 | if gate == null: return
61 |
62 | bookmarks.unstar(gate)
63 | star.visible = true
64 | unstar.visible = false
65 |
--------------------------------------------------------------------------------
/app/scripts/afk_manager.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | #class_name AfkManager
3 |
4 | signal state_changed(is_afk: bool)
5 |
6 | const AFK_TIMEOUT_SEC = 180
7 |
8 | var afk_check_timer: Timer
9 |
10 | var session_start_tick: int
11 | var last_key_tick: int
12 | var cumulative_afk_msec: int
13 | var afk_start_tick: int
14 |
15 |
16 | func _ready() -> void:
17 | session_start_tick = Time.get_ticks_msec()
18 | last_key_tick = session_start_tick
19 |
20 | afk_check_timer = Timer.new()
21 | afk_check_timer.one_shot = false
22 | afk_check_timer.wait_time = 1.0
23 | add_child(afk_check_timer)
24 |
25 | afk_check_timer.timeout.connect(check_afk)
26 | afk_check_timer.start()
27 |
28 |
29 | func _input(_event: InputEvent) -> void:
30 | var now := Time.get_ticks_msec()
31 | last_key_tick = now
32 | if afk_start_tick != 0:
33 | leave_afk(now)
34 |
35 |
36 | func check_afk() -> void:
37 | var now := Time.get_ticks_msec()
38 | if afk_start_tick == 0 and now - last_key_tick >= AFK_TIMEOUT_SEC * 1000:
39 | enter_afk(now)
40 |
41 |
42 | func enter_afk(now: int) -> void:
43 | afk_start_tick = now
44 | state_changed.emit(true)
45 |
46 |
47 | func leave_afk(now: int) -> void:
48 | if afk_start_tick == 0:
49 | return
50 |
51 | cumulative_afk_msec += now - afk_start_tick
52 | afk_start_tick = 0
53 | state_changed.emit(false)
54 |
55 |
56 | func get_active_sec() -> float:
57 | var now := Time.get_ticks_msec()
58 | var afk_current := (now - afk_start_tick) if afk_start_tick != 0 else 0
59 | var active_msec := (now - session_start_tick) - cumulative_afk_msec - afk_current
60 | return max(0.0, float(active_msec) / 1000.0)
61 |
--------------------------------------------------------------------------------
/app/scripts/ui/onboarding/onboarding.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 |
3 | const SECTION = "onboarding"
4 | const KEY = "shown"
5 |
6 | const INITIAL_DELAY = 1.0
7 | const SHOWN = Color(1, 1, 1, 1)
8 | const HIDDEN = Color(1, 1, 1, 0)
9 |
10 | @export var ui_events: UiEvents
11 | @export var gate_events: GateEvents
12 |
13 | @export var root: Control
14 | @export var close: Button
15 | @export var fade_in: float = 0.2
16 | @export var fade_out: float = 0.2
17 |
18 | @export var tutorial_url: String
19 |
20 | @export_group("Debug")
21 | @export var show_always: bool
22 |
23 | var tween: Tween
24 |
25 |
26 | func _ready() -> void:
27 | close.pressed.connect(hide_onboarding)
28 |
29 | visible = true
30 | root.visible = false
31 | root.modulate = HIDDEN
32 |
33 | try_show_onboarding()
34 |
35 |
36 | func try_show_onboarding() -> void:
37 | var is_shown = DataSaver.get_value(SECTION, KEY, false)
38 | if is_shown and not show_always: return
39 |
40 | ui_events.onboarding_requested_emit()
41 |
42 | await get_tree().create_timer(INITIAL_DELAY).timeout
43 | show_onboarding()
44 |
45 |
46 | func show_onboarding() -> void:
47 | if root.visible: return
48 |
49 | root.visible = true
50 |
51 | if is_instance_valid(tween): tween.stop()
52 | tween = create_tween()
53 | tween.tween_property(root, "modulate", SHOWN, fade_in)
54 |
55 | ui_events.onboarding_started_emit()
56 |
57 |
58 | func hide_onboarding() -> void:
59 | if not root.visible: return
60 |
61 | ui_events.onboarding_finished_emit()
62 |
63 | DataSaver.set_value(SECTION, KEY, true)
64 | DataSaver.save_data()
65 |
66 | gate_events.open_gate_emit(tutorial_url)
67 |
--------------------------------------------------------------------------------
/app/scripts/ui/menu/bookmark_ui.gd:
--------------------------------------------------------------------------------
1 | extends Control
2 | class_name BookmarkUI
3 |
4 | @export var gate_events: GateEvents
5 | @export var ui_events: UiEvents
6 | @export var icon: TextureRect
7 | @export var title: Label
8 | @export var button: Button
9 | @export var special_effect: Panel
10 | @export var jump_animation: BookmarkJumpAnimation
11 |
12 | var url: String
13 | var is_special: bool
14 |
15 |
16 | func _ready() -> void:
17 | button.pressed.connect(on_pressed)
18 | ui_events.onboarding_requested.connect(update_special_effects)
19 | ui_events.onboarding_started.connect(update_special_effects)
20 | ui_events.onboarding_finished.connect(update_special_effects)
21 |
22 |
23 | func fill(gate: Gate) -> void:
24 | if gate == null: return
25 |
26 | url = gate.url
27 | is_special = gate.is_special
28 | title.text = "Unnamed" if gate.title.is_empty() else gate.title
29 | update_special_effects()
30 |
31 | var icon_path = gate.icon
32 | if icon_path.is_empty(): icon_path = await FileDownloader.download(gate.icon_url)
33 |
34 | icon.texture = FileTools.load_external_tex(icon_path)
35 |
36 |
37 | func update_special_effects() -> void:
38 | if ui_events.is_onboarding_started or ui_events.is_onboarding_requested:
39 | special_effect.visible = false
40 | jump_animation.stop_jump_animation()
41 | return
42 |
43 | if is_special and gate_events.current_gate_url.is_empty():
44 | special_effect.visible = true
45 | jump_animation.start_jump_animation()
46 | else:
47 | special_effect.visible = false
48 | jump_animation.stop_jump_animation()
49 |
50 |
51 | func on_pressed() -> void:
52 | if url.is_empty(): return
53 | gate_events.open_gate_emit(url)
54 |
--------------------------------------------------------------------------------
/app/assets/textures/empty_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
49 |
--------------------------------------------------------------------------------
/app/scripts/loading/config_gate.gd:
--------------------------------------------------------------------------------
1 | extends ConfigBase
2 | class_name ConfigGate
3 |
4 | const SECTION = "gate"
5 |
6 | const KEY_TITLE = "title"
7 | const KEY_DESCRIPTION = "description"
8 | const KEY_ICON = "icon"
9 | const KEY_IMAGE = "image"
10 | const KEY_RESOURCE_PACK = "resource_pack"
11 | const KEY_GODOT_VERSION = "godot_version"
12 | const KEY_DISCOVERABLE = "discoverable"
13 |
14 | var title: String
15 | var description: String
16 | var icon_url: String
17 | var image_url: String
18 | var resource_pack_url: String
19 | var godot_version: String
20 | var discoverable: bool
21 | var libraries: PackedStringArray
22 |
23 |
24 | func _init(path: String, base_url: String) -> void:
25 | super._init(path)
26 |
27 | if not SECTION in get_sections():
28 | Debug.logclr("Invalid gate config file: don't have section " + SECTION, Color.YELLOW)
29 | load_result = ERR_INVALID_DATA
30 | return
31 |
32 | title = get_string(SECTION, KEY_TITLE)
33 | description = get_string(SECTION, KEY_DESCRIPTION)
34 | icon_url = Url.join(base_url, get_string(SECTION, KEY_ICON))
35 | image_url = Url.join(base_url, get_string(SECTION, KEY_IMAGE))
36 | resource_pack_url = Url.join(base_url, get_string(SECTION, KEY_RESOURCE_PACK))
37 | godot_version = get_string(SECTION, KEY_GODOT_VERSION)
38 | discoverable = get_value(SECTION, KEY_DISCOVERABLE, true)
39 | libraries = get_libraries(base_url)
40 |
41 |
42 | func get_libraries(base_url: String) -> PackedStringArray:
43 | var unsplit_libs = GDExtension.find_extension_library("", config)
44 | if unsplit_libs.is_empty(): return []
45 |
46 | var libs = unsplit_libs.split(";")
47 | for i in range(libs.size()): libs[i] = Url.join(base_url, libs[i])
48 | return libs
49 |
--------------------------------------------------------------------------------
/app/scripts/api/featured_gates.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | const KEY_URL = "url"
4 | const KEY_TITLE = "title"
5 | const KEY_DESCRIPTION = "description"
6 | const KEY_ICON = "icon"
7 | const KEY_IS_SPECIAL = "is_special"
8 |
9 | @export var api: ApiSettings
10 | @export var bookmarks: Bookmarks
11 |
12 | var result_str: String = "{}"
13 |
14 |
15 | func _ready() -> void:
16 | bookmarks.on_ready.connect(on_bookmarks_ready)
17 | if bookmarks.is_ready: on_bookmarks_ready()
18 |
19 |
20 | func on_bookmarks_ready() -> void:
21 | if bookmarks.gates.size() > 0: return
22 |
23 | await featured_gates_request()
24 | Debug.logclr("======== Featured gates ========", Color.LIGHT_SEA_GREEN)
25 |
26 | var gates = JSON.parse_string(result_str)
27 | if gates == null or gates.is_empty():
28 | Debug.logclr("No featured gates found", Color.YELLOW)
29 | return
30 |
31 | for gate in gates:
32 | Debug.logr(gate["url"])
33 | star_gate(gate)
34 |
35 |
36 | func featured_gates_request() -> void:
37 | var callback = func(_result, code, _headers, body):
38 | if code == 200:
39 | result_str = body.get_string_from_utf8()
40 | else: Debug.logclr("Featured gates request failed. Code " + str(code), Color.RED)
41 |
42 | var err = await Backend.request(api.featured_gates, callback)
43 | if err != OK: Debug.logclr("Cannot send featured gates request", Color.RED)
44 |
45 |
46 | func star_gate(gate_d: Dictionary) -> void:
47 | var gate = Gate.create(gate_d[KEY_URL], gate_d[KEY_TITLE], gate_d[KEY_DESCRIPTION], gate_d[KEY_ICON], "")
48 | gate.is_special = gate_d[KEY_IS_SPECIAL]
49 | gate.featured = true
50 | bookmarks.star(gate)
51 |
52 | var icon = await FileDownloader.download(gate.icon_url)
53 | bookmarks.update_icon(gate.url, icon)
54 |
--------------------------------------------------------------------------------
/app/assets/textures/cursor.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
41 |
--------------------------------------------------------------------------------
/app/assets/textures/help.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
37 |
--------------------------------------------------------------------------------
/app/scripts/renderer/process_checker.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var gate_events: GateEvents
4 | @export var command_events: CommandEvents
5 | @export var renderer_manager: RendererManager
6 |
7 | # Timeout intervals for child process responsiveness
8 | const BOOTUP_CHECK_SEC = 3
9 | const HEARTBEAT_INTERVAL_SEC = 10
10 | const WAIT_INTERVAL_SEC = 30
11 |
12 | var bootup_timer: Timer
13 | var heartbeat_timer: Timer
14 |
15 |
16 | func _ready() -> void:
17 | bootup_timer = Timer.new()
18 | heartbeat_timer = Timer.new()
19 | add_child(bootup_timer)
20 | add_child(heartbeat_timer)
21 |
22 | bootup_timer.timeout.connect(bootup_check)
23 | heartbeat_timer.timeout.connect(heartbeat_check)
24 |
25 | gate_events.first_frame.connect(start_heartbeat_timer)
26 | command_events.heartbeat.connect(restart_heartbeat_timer)
27 | gate_events.call_or_subscribe(GateEvents.Early.ENTERED, start_bootup_check)
28 |
29 |
30 | func start_bootup_check() -> void:
31 | bootup_timer.start(BOOTUP_CHECK_SEC)
32 |
33 |
34 | func bootup_check() -> void:
35 | if renderer_manager.is_process_running(): return
36 |
37 | bootup_timer.stop()
38 | on_timeout("Gate crashed on bootup")
39 |
40 |
41 | func start_heartbeat_timer() -> void:
42 | if not bootup_timer.is_stopped(): bootup_timer.stop()
43 | heartbeat_timer.start(HEARTBEAT_INTERVAL_SEC)
44 |
45 |
46 | func restart_heartbeat_timer() -> void:
47 | heartbeat_timer.start(HEARTBEAT_INTERVAL_SEC)
48 |
49 |
50 | func heartbeat_check() -> void:
51 | var error = "Gate is not responding" if renderer_manager.is_process_running() else "Gate crashed on heartbeat"
52 |
53 | heartbeat_timer.stop()
54 | on_timeout(error)
55 |
56 |
57 | func on_timeout(error: String) -> void:
58 | Debug.logerr(error)
59 | gate_events.not_responding_emit()
60 | heartbeat_timer.start(WAIT_INTERVAL_SEC)
61 |
--------------------------------------------------------------------------------
/app/scripts/ui/notification/notifier_mouse_captured.gd:
--------------------------------------------------------------------------------
1 | extends NotifierBase
2 |
3 | const SECTION: String = "notifications"
4 | const KEY: String = "mouse_mode_restored"
5 |
6 | @export var ui_events: UiEvents
7 | @export var show_delay_sec: float = 3.0
8 | @export var hide_delay_sec: float = 0.05
9 |
10 | var last_mode: Input.MouseMode
11 | var is_showing: bool
12 | var is_restored: bool
13 | var scheduled_action_sequence: int = 0
14 |
15 |
16 | func _ready() -> void:
17 | is_restored = DataSaver.get_value(SECTION, KEY, false)
18 | if is_restored: return
19 |
20 | ui_events.mouse_mode_changed.connect(on_mouse_mode_changed)
21 |
22 |
23 | func _input(event: InputEvent) -> void:
24 | if not is_showing or is_restored: return
25 |
26 | if event.is_action_pressed("show_ui"):
27 | is_restored = true
28 | DataSaver.set_value(SECTION, KEY, true)
29 | DataSaver.save_data()
30 |
31 |
32 | func on_mouse_mode_changed(mode: Input.MouseMode) -> void:
33 | if is_restored or last_mode == mode: return
34 | last_mode = mode
35 |
36 | if mode == Input.MOUSE_MODE_VISIBLE:
37 | schedule_hide_with_delay()
38 | else:
39 | schedule_show_with_delay()
40 |
41 |
42 | func schedule_show_with_delay() -> void:
43 | scheduled_action_sequence += 1
44 | var action_id: int = scheduled_action_sequence
45 | await get_tree().create_timer(show_delay_sec).timeout
46 |
47 | if action_id != scheduled_action_sequence: return
48 | if is_showing: return
49 | is_showing = true
50 |
51 | show_notification()
52 |
53 |
54 | func schedule_hide_with_delay() -> void:
55 | scheduled_action_sequence += 1
56 | var action_id: int = scheduled_action_sequence
57 | await get_tree().create_timer(hide_delay_sec).timeout
58 |
59 | if action_id != scheduled_action_sequence: return
60 | if not is_showing: return
61 | is_showing = false
62 |
63 | hide_notification()
64 |
--------------------------------------------------------------------------------
/app/scripts/renderer/input_sync.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export var gate_events: GateEvents
4 | @export var ui_events: UiEvents
5 | @export var render_result: RenderResult
6 |
7 | var scale: float
8 | var offset: Vector2
9 |
10 | var input_sync: InputSync
11 | var should_send := false
12 |
13 |
14 | func _ready() -> void:
15 | ui_events.ui_mode_changed.connect(on_ui_mode_changed)
16 | gate_events.call_or_subscribe(GateEvents.Early.ENTERED, start_server)
17 |
18 |
19 | func start_server() -> void:
20 | input_sync = InputSync.new()
21 | input_sync.socket_bind()
22 |
23 | scale = DisplayServer.screen_get_scale()
24 | offset = render_result.global_position
25 | Debug.logclr("Mouse position scale: %.2f. Offset: %.2f" % [scale, offset.y], Color.DIM_GRAY)
26 |
27 |
28 | func on_ui_mode_changed(mode: UiEvents.UiMode) -> void:
29 | should_send = mode == UiEvents.UiMode.FOCUSED
30 | if should_send: update_mouse_position()
31 |
32 |
33 | func _input(_event: InputEvent) -> void:
34 | if input_sync == null or not should_send: return
35 |
36 | var event = _event
37 | if event is InputEventMouse:
38 | event = _event.duplicate()
39 | event.position = get_scaled_mouse_pos(event.position)
40 | event.global_position = get_scaled_mouse_pos(event.global_position)
41 |
42 | input_sync.send_input_event(event)
43 |
44 |
45 | func update_mouse_position() -> void:
46 | var event = InputEventMouseMotion.new()
47 | var last_mouse_position = get_viewport().get_mouse_position()
48 | event.position = get_scaled_mouse_pos(last_mouse_position)
49 | event.global_position = get_scaled_mouse_pos(last_mouse_position)
50 |
51 | input_sync.send_input_event(event)
52 |
53 |
54 | func get_scaled_mouse_pos(position : Vector2) -> Vector2:
55 | return (position - offset) * scale
56 |
57 |
58 | func _exit_tree() -> void:
59 | if input_sync != null:
60 | input_sync.close()
61 | input_sync = null
62 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name Analytics
3 |
4 | signal analytics_ready
5 |
6 | @export var api: ApiSettings
7 |
8 |
9 | func _ready() -> void:
10 | get_app_version()
11 | await get_user_id()
12 | analytics_ready.emit()
13 |
14 |
15 | func send_event(body: Dictionary = {}) -> void:
16 | var url = api.analytics_event
17 | var callback = func(_result, code, _headers, _body):
18 | if code != 200: Debug.logclr("Request send_event failed. Code " + str(code), Color.RED)
19 |
20 | var err = await Backend.request(url, callback, body, HTTPClient.METHOD_POST)
21 | if err != OK: Debug.logclr("Cannot send request send_event", Color.RED)
22 |
23 |
24 | func get_user_id() -> void:
25 | AnalyticsEvents.user_id = DataSaver.get_string("analytics", "user_id")
26 | if not AnalyticsEvents.user_id.is_empty(): return
27 |
28 | var url = api.create_user_id + OS.get_unique_id()
29 | var callback = func(_result, code, _headers, body):
30 | if code == 200:
31 | AnalyticsEvents.user_id = body.get_string_from_utf8()
32 | DataSaver.set_value("analytics", "user_id", AnalyticsEvents.user_id)
33 | DataSaver.save_data()
34 | else: Debug.logclr("Request create_user_id failed. Code " + str(code), Color.RED)
35 |
36 | var err = await Backend.request(url, callback)
37 | if err != OK: Debug.logclr("Cannot send request create_user_id", Color.RED)
38 |
39 |
40 | func get_app_version() -> void:
41 | AnalyticsEvents.app_version = ProjectSettings.get_setting("application/config/version")
42 | AnalyticsEvents.app_version_code = version_to_int(AnalyticsEvents.app_version)
43 |
44 |
45 | func version_to_int(version: String) -> int:
46 | var parts = version.split(".")
47 | return int(parts[0]) * 10000 + int(parts[1]) * 100 + int(parts[2])
48 |
49 |
50 | static func get_delta_sec_from_tick(from_tick: int) -> float:
51 | return float(Time.get_ticks_msec() - from_tick) / 1000
52 |
--------------------------------------------------------------------------------
/app/scripts/resources/ui_events.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name UiEvents
3 |
4 | signal ui_mode_changed(mode: UiMode)
5 | signal ui_size_changed(size: Vector2)
6 | signal mouse_mode_changed(mode: Input.MouseMode)
7 |
8 | signal debug_window_opened()
9 | signal debug_window_closed()
10 |
11 | signal onboarding_requested()
12 | signal onboarding_started()
13 | signal onboarding_finished()
14 |
15 | enum UiMode
16 | {
17 | INITIAL,
18 | FOCUSED
19 | }
20 |
21 | var current_ui_size: Vector2
22 | var is_debug_window_opened: bool
23 | var is_onboarding_requested: bool
24 | var is_onboarding_started: bool
25 | var is_typing_search: bool
26 | var is_dragging_window: bool
27 |
28 |
29 | func ui_mode_changed_emit(mode: UiMode) -> void:
30 | ui_mode_changed.emit(mode)
31 |
32 |
33 | func ui_size_changed_emit(size: Vector2) -> void:
34 | current_ui_size = size
35 | ui_size_changed.emit(size)
36 |
37 |
38 | func mouse_mode_changed_emit(mode: Input.MouseMode) -> void:
39 | mouse_mode_changed.emit(mode)
40 |
41 |
42 | func debug_window_opened_emit() -> void:
43 | is_debug_window_opened = true
44 | debug_window_opened.emit()
45 |
46 |
47 | func debug_window_closed_emit() -> void:
48 | is_debug_window_opened = false
49 | debug_window_closed.emit()
50 |
51 |
52 | func onboarding_requested_emit() -> void:
53 | is_onboarding_requested = true
54 | onboarding_requested.emit()
55 |
56 |
57 | func onboarding_started_emit() -> void:
58 | is_onboarding_requested = false
59 | is_onboarding_started = true
60 | onboarding_started.emit()
61 |
62 |
63 | func onboarding_finished_emit() -> void:
64 | is_onboarding_requested = false
65 | is_onboarding_started = false
66 | onboarding_finished.emit()
67 |
68 |
69 | func set_typing_search(is_typing: bool) -> void:
70 | is_typing_search = is_typing
71 |
72 |
73 | func set_dragging_window(is_dragging: bool) -> void:
74 | is_dragging_window = is_dragging
75 |
--------------------------------------------------------------------------------
/app/scripts/bookmark_saver.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 |
3 | @export_dir var save_dir: String
4 | @export_dir var icon_save_dir: String
5 | @export var bookmarks: Bookmarks
6 |
7 | @onready var path = save_dir + "/" + bookmarks.resource_path.get_file()
8 |
9 |
10 | func _ready() -> void:
11 | load_bookmarks()
12 | bookmarks.ready()
13 |
14 | bookmarks.save_icon.connect(save_icon)
15 | bookmarks.on_star.connect(func(_gate): save_bookmarks())
16 | bookmarks.on_unstar.connect(func(_gate): save_bookmarks())
17 | bookmarks.on_update.connect(func(_gate): save_bookmarks())
18 |
19 |
20 | func load_bookmarks() -> void:
21 | if not FileAccess.file_exists(path): return
22 |
23 | var loaded = ResourceLoader.load(path) as Bookmarks
24 | if loaded == null: return
25 |
26 | bookmarks.starred_gates = loaded.starred_gates
27 |
28 |
29 | func save_bookmarks() -> void:
30 | if not DirAccess.dir_exists_absolute(save_dir):
31 | DirAccess.make_dir_recursive_absolute(save_dir)
32 | ResourceSaver.save(bookmarks, path)
33 |
34 |
35 | func save_icon(gate: Gate) -> void:
36 | if not FileAccess.file_exists(gate.icon): return
37 | if not DirAccess.dir_exists_absolute(icon_save_dir):
38 | DirAccess.make_dir_recursive_absolute(icon_save_dir)
39 |
40 | var new_path = icon_save_dir + "/" + gate.icon.get_file()
41 | if new_path == gate.icon: return
42 | DirAccess.copy_absolute(gate.icon, new_path)
43 | gate.icon = new_path
44 |
45 |
46 | func clear_icon_folder() -> void:
47 | if not DirAccess.dir_exists_absolute(icon_save_dir): return
48 |
49 | var used_icons: Array[String] = []
50 | for gate in bookmarks.gates.values():
51 | used_icons.append(gate.icon.get_file())
52 |
53 | for file in DirAccess.get_files_at(icon_save_dir):
54 | if not file in used_icons:
55 | DirAccess.remove_absolute(icon_save_dir + "/" + file)
56 |
57 |
58 | func _exit_tree() -> void:
59 | save_bookmarks()
60 | clear_icon_folder()
61 |
--------------------------------------------------------------------------------
/app/scripts/renderer/renderer_manager.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name RendererManager
3 |
4 | @export var gate_events: GateEvents
5 | @export var render_result: RenderResult
6 | @export var renderer_logger: RendererLogger
7 |
8 | const IPC_FOLDER := "renderer"
9 |
10 | var renderer_pid: int
11 |
12 |
13 | func _ready() -> void:
14 | gate_events.call_or_subscribe(GateEvents.Early.ALL_LOADED, start_renderer)
15 |
16 |
17 | func start_renderer(gate: Gate) -> void:
18 | var pipe = start_process(gate)
19 | if pipe.is_empty(): return
20 |
21 | renderer_pid = pipe["pid"]
22 | renderer_logger.call_thread_safe("start", pipe, gate)
23 | gate_events.gate_entered_emit()
24 |
25 |
26 | func start_process(gate: Gate) -> Dictionary:
27 | if not FileAccess.file_exists(gate.renderer):
28 | Debug.logerr("Renderer executable not found at " + gate.renderer); return {}
29 |
30 | if Platform.get_platform() == Platform.WINDOWS:
31 | DirAccess.make_dir_recursive_absolute(IPC_FOLDER)
32 |
33 | var pack_file = ProjectSettings.globalize_path(gate.resource_pack)
34 | var shared_libs = ProjectSettings.globalize_path(gate.shared_libs_dir)
35 | var args = [
36 | "--main-pack", pack_file,
37 | "--resolution", "%dx%d" % [render_result.width, render_result.height],
38 | "--url", gate.url,
39 | "--verbose"
40 | ]
41 | if not shared_libs.is_empty(): args += ["--gdext-libs-dir", shared_libs]
42 |
43 | Debug.logclr(gate.renderer + " " + " ".join(args), Color.DIM_GRAY)
44 | return OS.execute_with_pipe(gate.renderer, args)
45 |
46 |
47 | func kill_renderer() -> void:
48 | if OS.is_process_running(renderer_pid):
49 | OS.kill(renderer_pid)
50 | Debug.logclr("Process killed " + str(renderer_pid), Color.DIM_GRAY)
51 |
52 | renderer_logger.call_thread_safe("cleanup")
53 |
54 |
55 | func is_process_running() -> bool:
56 | return OS.is_process_running(renderer_pid)
57 |
58 |
59 | func _exit_tree() -> void:
60 | kill_renderer()
61 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search.gd:
--------------------------------------------------------------------------------
1 | extends LineEdit
2 | class_name Search
3 |
4 | signal on_release_focus
5 | signal on_navigation(event: int)
6 |
7 | @export var ui_events: UiEvents
8 | @export var gate_events: GateEvents
9 | @export var prompt_panel: Control
10 | @export var focus_on_ready: bool
11 |
12 |
13 | func _ready() -> void:
14 | gate_events.open_gate.connect(set_current_url)
15 | gate_events.search.connect(set_current_url)
16 | gate_events.exit_gate.connect(set_current_url.bind(""))
17 |
18 | if focus_on_ready: grab_focus()
19 |
20 |
21 | func set_current_url(_url: String) -> void:
22 | text = _url
23 |
24 | stop_typing()
25 |
26 |
27 | func _on_text_submitted(_url: String) -> void: # url might be empty
28 | open_gate()
29 |
30 |
31 | func open_gate() -> void:
32 | if text.is_empty(): return
33 |
34 | if Url.is_valid(text):
35 | gate_events.open_gate_emit(text)
36 | else:
37 | gate_events.search_emit(text)
38 |
39 | stop_typing()
40 |
41 |
42 | func _input(event: InputEvent) -> void:
43 | if not has_focus(): return
44 | if not ui_events.is_typing_search: ui_events.set_typing_search(true)
45 |
46 | if (event is InputEventMouseButton
47 | and not get_global_rect().has_point(event.position)
48 | and not prompt_panel.get_global_rect().has_point(event.position)
49 | and not event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]):
50 |
51 | stop_typing()
52 |
53 | if event.is_action_pressed("ui_text_clear_carets_and_selection"):
54 | stop_typing()
55 |
56 | if event.is_action_pressed("ui_text_caret_up"):
57 | on_navigation.emit(PromptNavigation.UP)
58 | get_viewport().set_input_as_handled()
59 | elif event.is_action_pressed("ui_text_caret_down"):
60 | on_navigation.emit(PromptNavigation.DOWN)
61 | get_viewport().set_input_as_handled()
62 |
63 |
64 | func stop_typing() -> void:
65 | if ui_events.is_typing_search: ui_events.set_typing_search(false)
66 | release_focus()
67 | on_release_focus.emit()
68 |
--------------------------------------------------------------------------------
/app/scripts/resources/bookmarks.gd:
--------------------------------------------------------------------------------
1 | extends Resource
2 | class_name Bookmarks
3 |
4 | signal on_ready()
5 | signal on_star(gate: Gate)
6 | signal on_unstar(gate: Gate)
7 | signal on_update(gate: Gate)
8 | signal save_icon(gate: Gate)
9 |
10 | @export var starred_gates: Array[Gate]
11 |
12 | var is_ready: bool
13 | var gates = {}
14 |
15 |
16 | func ready() -> void:
17 | for gate in starred_gates.duplicate():
18 | if not is_instance_valid(gate) or not Url.is_valid(gate.url) or gates.has(gate.url):
19 | starred_gates.erase(gate); continue # Remove invalid and duplicates
20 | gates[gate.url] = gate
21 |
22 | is_ready = true
23 | on_ready.emit()
24 |
25 |
26 | func make_first(url: String) -> void:
27 | if not gates.has(url): return
28 |
29 | var gate = gates[url]
30 | gates.erase(url)
31 | gates[url] = gate
32 |
33 |
34 | func update_icon(url: String, icon: String) -> void:
35 | if not gates.has(url): return
36 |
37 | var gate = gates[url]
38 | gate.icon = icon
39 |
40 | var index = starred_gates.find(gate)
41 | starred_gates[index].icon = icon
42 |
43 | save_icon.emit(gate)
44 |
45 |
46 | func update(gate: Gate) -> void:
47 | if not gates.has(gate.url): return
48 |
49 | var replace = gates[gate.url]
50 |
51 | if gate.icon_url == replace.icon_url: gate.icon = replace.icon
52 | if gate.image_url == replace.image_url: gate.image = replace.image
53 |
54 | gates.erase(gate.url)
55 | gates[gate.url] = gate
56 |
57 | starred_gates.erase(replace)
58 | starred_gates.append(gate)
59 |
60 | save_icon.emit(gate)
61 | on_update.emit(gate)
62 |
63 |
64 | func star(gate: Gate) -> void:
65 | if gates.has(gate.url): return
66 |
67 | gates[gate.url] = gate
68 | starred_gates.append(gate)
69 |
70 | save_icon.emit(gate)
71 | on_star.emit(gate)
72 |
73 |
74 | func unstar(gate: Gate) -> void:
75 | if not gates.has(gate.url): return
76 |
77 | var erase: Gate = gates[gate.url]
78 | gates.erase(erase.url)
79 | starred_gates.erase(erase)
80 |
81 | on_unstar.emit(gate)
82 |
--------------------------------------------------------------------------------
/app/scripts/api/backend.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | #class_name Backend
3 |
4 | var cancel_http_func: Callable = func(http: HTTPRequestPooled):
5 | if is_instance_valid(http):
6 | http.cancel_request()
7 | http.queue_free()
8 |
9 |
10 | func request(url: String, callback: Callable,
11 | body: Dictionary = {}, method: int = HTTPClient.METHOD_GET,
12 | cancel_callbacks: Array[Callable] = []) -> Error:
13 |
14 | var data = JSON.stringify(body)
15 | var headers = []
16 |
17 | var http = HTTPRequestPooled.new()
18 | http.use_threads = true
19 | add_child(http)
20 |
21 | var canceler: Callable = cancel_http_func.bind(http)
22 | cancel_callbacks.append(canceler)
23 |
24 | var start_ms = Time.get_ticks_msec()
25 | var err = http.request(url, headers, method, data)
26 | var res = await http.request_completed
27 | print("API request " + url + " code=" + str(res[1]) + " duration_ms=" + str(Time.get_ticks_msec() - start_ms))
28 |
29 | # If calling object is freed without canceling request
30 | if not callback.is_valid(): return ERR_INVALID_PARAMETER
31 |
32 | callback.call(res[0], res[1], res[2], res[3])
33 | cancel_callbacks.erase(canceler)
34 | http.queue_free()
35 |
36 | return err
37 |
38 |
39 | func request_raw(url: String, callback: Callable,
40 | data: PackedByteArray, method: int = HTTPClient.METHOD_GET,
41 | cancel_callbacks: Array[Callable] = []) -> Error:
42 |
43 | var headers = []
44 |
45 | var http = HTTPRequestPooled.new()
46 | http.use_threads = true
47 | add_child(http)
48 |
49 | var canceler: Callable = cancel_http_func.bind(http)
50 | cancel_callbacks.append(canceler)
51 |
52 | var start_ms = Time.get_ticks_msec()
53 | var err = http.request_raw(url, headers, method, data)
54 | var res = await http.request_completed
55 | print("API request " + url + " code=" + str(res[1]) + " duration_ms=" + str(Time.get_ticks_msec() - start_ms))
56 |
57 | # If calling object is freed without canceling request
58 | if not callback.is_valid(): return ERR_INVALID_PARAMETER
59 |
60 | callback.call(res[0], res[1], res[2], res[3])
61 | cancel_callbacks.erase(canceler)
62 | http.queue_free()
63 |
64 | return err
65 |
--------------------------------------------------------------------------------
/app/scenes/components/search/suggestion.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=6 format=3 uid="uid://dntnp0igpccdt"]
2 |
3 | [ext_resource type="FontFile" uid="uid://do40418waa8w3" path="res://assets/fonts/Inter-Regular.otf" id="1_ljf2m"]
4 | [ext_resource type="Script" uid="uid://x5v7vstd6da6" path="res://scripts/ui/search/suggestion.gd" id="2_rofb8"]
5 | [ext_resource type="Resource" uid="uid://b1xvdym0qh6td" path="res://resources/gate_events.res" id="3_l3ahe"]
6 |
7 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ix3db"]
8 | content_margin_left = 13.0
9 | content_margin_top = 6.0
10 | content_margin_right = 13.0
11 | content_margin_bottom = 6.0
12 | bg_color = Color(0.12549, 0.133333, 0.172549, 1)
13 | corner_radius_top_left = 15
14 | corner_radius_top_right = 15
15 | corner_radius_bottom_right = 15
16 | corner_radius_bottom_left = 15
17 | shadow_color = Color(0.0862745, 0.0901961, 0.117647, 0.784314)
18 | shadow_size = 4
19 |
20 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y60js"]
21 | content_margin_left = 13.0
22 | content_margin_top = 6.0
23 | content_margin_right = 13.0
24 | content_margin_bottom = 6.0
25 | bg_color = Color(0.32549, 0.14902, 0.8, 1)
26 | corner_radius_top_left = 15
27 | corner_radius_top_right = 15
28 | corner_radius_bottom_right = 15
29 | corner_radius_bottom_left = 15
30 | shadow_color = Color(0.0862745, 0.0901961, 0.117647, 0.784314)
31 | shadow_size = 4
32 |
33 | [node name="Suggestion" type="Button"]
34 | custom_minimum_size = Vector2(0, 26)
35 | focus_mode = 0
36 | mouse_default_cursor_shape = 2
37 | theme_override_colors/font_color = Color(0.831373, 0.831373, 0.831373, 1)
38 | theme_override_fonts/font = ExtResource("1_ljf2m")
39 | theme_override_font_sizes/font_size = 15
40 | theme_override_styles/normal = SubResource("StyleBoxFlat_ix3db")
41 | theme_override_styles/pressed = SubResource("StyleBoxFlat_ix3db")
42 | theme_override_styles/hover = SubResource("StyleBoxFlat_y60js")
43 | theme_override_styles/disabled = SubResource("StyleBoxFlat_ix3db")
44 | text = "suggestion"
45 | script = ExtResource("2_rofb8")
46 | gate_events = ExtResource("3_l3ahe")
47 |
48 | [connection signal="pressed" from="." to="." method="_on_button_pressed"]
49 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/prompt_navigation.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | class_name PromptNavigation
3 |
4 | enum {
5 | UP,
6 | DOWN
7 | }
8 |
9 | @export var search: Search
10 | @export var prompt_results: PromptResults
11 |
12 | var current_prompt: int = -1
13 | var actual_search_query: String
14 |
15 |
16 | func _ready() -> void:
17 | search.focus_entered.connect(on_focus_entered)
18 | search.on_release_focus.connect(on_release_focus)
19 | search.on_navigation.connect(on_navigation)
20 | search.text_changed.connect(func(_text): reset())
21 |
22 |
23 | func on_focus_entered() -> void:
24 | prompt_results._on_search_text_changed(search.text)
25 |
26 |
27 | func on_release_focus() -> void:
28 | prompt_results.clear()
29 | reset()
30 |
31 |
32 | func on_navigation(event: int) -> void:
33 | match event:
34 | UP:
35 | prompt_up()
36 | DOWN:
37 | prompt_down()
38 | _:
39 | printerr("Unhandled navigation event")
40 |
41 |
42 | func prompt_up() -> void:
43 | var from: int = current_prompt
44 | var to: int
45 |
46 | if from == -1:
47 | to = prompt_results.get_children().size() - 1
48 | else:
49 | to = from - 1
50 |
51 | if from != to:
52 | current_prompt = to
53 | switch_prompt(from, to)
54 |
55 |
56 | func prompt_down() -> void:
57 | var from: int = current_prompt
58 | var to: int
59 |
60 | if from == prompt_results.get_children().size() - 1:
61 | to = -1
62 | else:
63 | to = from + 1
64 |
65 | if from != to:
66 | current_prompt = to
67 | switch_prompt(from, to)
68 |
69 |
70 | func reset() -> void:
71 | current_prompt = -1
72 | actual_search_query = ""
73 |
74 |
75 | func switch_prompt(from: int, to: int) -> void:
76 | if from == -1:
77 | actual_search_query = search.text
78 | else:
79 | var from_prompt: PromptResult = prompt_results.get_children()[from]
80 | from_prompt.unfocus()
81 |
82 | if to == -1:
83 | search.text = actual_search_query
84 | search.caret_column = search.text.length()
85 | else:
86 | var to_prompt: PromptResult = prompt_results.get_children()[to]
87 | to_prompt.focus()
88 |
89 | search.text = to_prompt.prompt_text.text
90 | search.caret_column = search.text.length()
91 |
--------------------------------------------------------------------------------
/app/scripts/api/analytics/analytics_sender_gate.gd:
--------------------------------------------------------------------------------
1 | extends AnalyticsSender
2 | class_name AnalyticsSenderGate
3 |
4 | @export var gate_events: GateEvents
5 |
6 | var gate_open_tick: int
7 | var gate_load_tick: int
8 | var gate_url: String
9 |
10 |
11 | func start() -> void:
12 | super.start()
13 |
14 | send_saved_gate_exit()
15 |
16 | gate_events.search.connect(send_search)
17 | gate_events.open_gate.connect(send_gate_open)
18 | gate_events.gate_loaded.connect(func(_gate): send_gate_load())
19 | gate_events.first_frame.connect(send_gate_start)
20 | gate_events.exit_gate.connect(send_gate_exit)
21 |
22 |
23 | func send_search(query: String) -> void:
24 | send_gate_exit()
25 |
26 | analytics.send_event(AnalyticsEvents.search(query))
27 |
28 |
29 | func send_gate_open(url: String) -> void:
30 | send_gate_exit()
31 |
32 | gate_url = url
33 | gate_open_tick = Time.get_ticks_msec()
34 | analytics.send_event(AnalyticsEvents.gate_open(url))
35 |
36 |
37 | func send_gate_load() -> void:
38 | var download_time = Analytics.get_delta_sec_from_tick(gate_open_tick)
39 | gate_load_tick = Time.get_ticks_msec()
40 | analytics.send_event(AnalyticsEvents.gate_load(gate_url, download_time))
41 | Debug.logclr("Download time: %.3f" % [download_time], Color.AQUAMARINE)
42 |
43 |
44 | func send_gate_start() -> void:
45 | var bootup_time = Analytics.get_delta_sec_from_tick(gate_load_tick)
46 | analytics.send_event(AnalyticsEvents.gate_start(gate_url, bootup_time))
47 | Debug.logclr("Bootup time: %.3f" % [bootup_time], Color.AQUAMARINE)
48 |
49 |
50 | func send_gate_exit() -> void:
51 | if gate_url.is_empty(): return
52 |
53 | var time_spent = Analytics.get_delta_sec_from_tick(gate_open_tick)
54 | analytics.send_event(AnalyticsEvents.gate_exit(gate_url, time_spent))
55 | gate_url = ""
56 |
57 |
58 | func send_saved_gate_exit() -> void:
59 | var json: String = DataSaver.get_string("analytics", "send_gate_exit")
60 | if json.is_empty(): return
61 | DataSaver.set_value("analytics", "send_gate_exit", "")
62 | analytics.send_event(JSON.parse_string(json))
63 |
64 |
65 | func _exit_tree() -> void:
66 | if gate_url.is_empty(): return
67 |
68 | # Save to send on open
69 | var time_spent = Analytics.get_delta_sec_from_tick(gate_open_tick)
70 | var event = AnalyticsEvents.gate_exit(gate_url, time_spent)
71 | DataSaver.set_value("analytics", "send_gate_exit", JSON.stringify(event))
72 |
--------------------------------------------------------------------------------
/app/scripts/ui/search/search_results.gd:
--------------------------------------------------------------------------------
1 | extends VBoxContainer
2 |
3 | @export var gate_events: GateEvents
4 | @export var api: ApiSettings
5 | @export var result_scene: PackedScene
6 |
7 | @export var header: SearchResultsHeader
8 | @export var suggestions_root: Control
9 | @export var suggestion_scene: PackedScene
10 | @export var no_results_note: PackedScene
11 |
12 | var result_str: String = "{}"
13 | var cancel_callbacks: Array[Callable] = []
14 |
15 |
16 | func _ready() -> void:
17 | search(gate_events.current_search_query)
18 |
19 |
20 | func search(query: String) -> void:
21 | Debug.logclr("======== " + query + " ========", Color.LIGHT_SEA_GREEN)
22 | await search_request(query)
23 |
24 | var result = JSON.parse_string(result_str)
25 | var gates = JSON.parse_string(result["gates"])
26 |
27 | if gates == null or gates.is_empty():
28 | Debug.logclr("No gates found, showing suggestions", Color.YELLOW)
29 | var suggs = JSON.parse_string(result["suggestions"])
30 | suggestions(suggs)
31 | return
32 |
33 | header.set_search_header()
34 | suggestions_root.visible = false
35 |
36 | for gate in gates:
37 | Debug.logr(gate["url"])
38 | var search_result: SearchResult = result_scene.instantiate()
39 | search_result.fill(gate)
40 | add_child(search_result)
41 |
42 |
43 | func search_request(query: String) -> void:
44 | var url = api.search + query.uri_encode()
45 | var callback = func(_result, code, _headers, body):
46 | if code == 200:
47 | result_str = body.get_string_from_utf8()
48 | else: Debug.logclr("Request search failed. Code " + str(code), Color.RED)
49 |
50 | var err = await Backend.request(url, callback, {}, HTTPClient.METHOD_GET, cancel_callbacks)
51 | if err != OK: Debug.logclr("Cannot send request search", Color.RED)
52 |
53 |
54 | func suggestions(suggs: Array) -> void:
55 | if suggs == null or suggs.is_empty():
56 | Debug.logclr("No suggestions found", Color.YELLOW)
57 | return
58 |
59 | header.set_suggestion_header()
60 |
61 | for sugg in suggs:
62 | Debug.logr(sugg)
63 | var suggestion: Suggestion = suggestion_scene.instantiate()
64 | suggestion.fill(sugg)
65 | suggestions_root.add_child(suggestion)
66 |
67 | var note = no_results_note.instantiate()
68 | add_child(note)
69 |
70 |
71 | func _exit_tree() -> void:
72 | for callback in cancel_callbacks:
73 | if callback.is_valid(): callback.call()
74 | cancel_callbacks.clear()
75 |
--------------------------------------------------------------------------------
/app/scenes/components/notification/notification.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=5 format=3 uid="uid://1a4h8k7mfv1e"]
2 |
3 | [ext_resource type="Script" uid="uid://6p4mottomv8l" path="res://scripts/ui/notification/notification.gd" id="1_r2fqo"]
4 | [ext_resource type="Texture2D" uid="uid://wuvhs6f87wud" path="res://assets/textures/cursor.svg" id="2_r2fqo"]
5 | [ext_resource type="LabelSettings" uid="uid://bo2334w4lf3ug" path="res://assets/styles/text.tres" id="3_3ti1b"]
6 |
7 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6wigr"]
8 | content_margin_left = 6.0
9 | content_margin_top = 6.0
10 | content_margin_right = 6.0
11 | content_margin_bottom = 6.0
12 | bg_color = Color(0.3264042, 0.14733289, 0.8001019, 0.90000004)
13 | corner_radius_top_left = 13
14 | corner_radius_top_right = 13
15 | corner_radius_bottom_right = 13
16 | corner_radius_bottom_left = 13
17 |
18 | [node name="Notification" type="Control" node_paths=PackedStringArray("icon", "message", "root")]
19 | custom_minimum_size = Vector2(0, 65)
20 | layout_mode = 3
21 | anchors_preset = 0
22 | offset_right = 300.0
23 | offset_bottom = 65.0
24 | script = ExtResource("1_r2fqo")
25 | icon = NodePath("Panel/MarginContainer/HBoxContainer/TextureRect")
26 | message = NodePath("Panel/MarginContainer/HBoxContainer/Label")
27 | root = NodePath("Panel")
28 |
29 | [node name="Panel" type="Panel" parent="."]
30 | layout_mode = 1
31 | anchors_preset = 15
32 | anchor_right = 1.0
33 | anchor_bottom = 1.0
34 | grow_horizontal = 2
35 | grow_vertical = 2
36 | theme_override_styles/panel = SubResource("StyleBoxFlat_6wigr")
37 |
38 | [node name="MarginContainer" type="MarginContainer" parent="Panel"]
39 | layout_mode = 1
40 | anchors_preset = 15
41 | anchor_right = 1.0
42 | anchor_bottom = 1.0
43 | grow_horizontal = 2
44 | grow_vertical = 2
45 | theme_override_constants/margin_left = 18
46 |
47 | [node name="HBoxContainer" type="HBoxContainer" parent="Panel/MarginContainer"]
48 | layout_mode = 2
49 | theme_override_constants/separation = 15
50 |
51 | [node name="TextureRect" type="TextureRect" parent="Panel/MarginContainer/HBoxContainer"]
52 | custom_minimum_size = Vector2(24, 24)
53 | layout_mode = 2
54 | texture = ExtResource("2_r2fqo")
55 | expand_mode = 1
56 | stretch_mode = 5
57 |
58 | [node name="Label" type="Label" parent="Panel/MarginContainer/HBoxContainer"]
59 | layout_mode = 2
60 | text = "Your mouse is captured
61 | Press Escape to show it"
62 | label_settings = ExtResource("3_3ti1b")
63 | vertical_alignment = 1
64 |
--------------------------------------------------------------------------------
/app/scripts/renderer/command_sync.gd:
--------------------------------------------------------------------------------
1 | extends CommandSync
2 |
3 | @export var app_events: AppEvents
4 | @export var gate_events: GateEvents
5 | @export var command_events: CommandEvents
6 |
7 | var silent_commands = ["heartbeat"]
8 |
9 |
10 | func _ready() -> void:
11 | execute_function = _execute_function
12 | gate_events.call_or_subscribe(GateEvents.Early.ENTERED, socket_bind)
13 |
14 |
15 | func _physics_process(_delta: float) -> void:
16 | receive_commands()
17 |
18 |
19 | func _execute_function(command: Command) -> Variant:
20 | if command.name not in silent_commands:
21 | Debug.logclr("Recieved command: " + command.name + ". Args: " + str(command.args), Color.SANDY_BROWN)
22 |
23 | match command.name:
24 | "send_filehandle":
25 | if wrong_args_count(command, 1): return ERR_INVALID_PARAMETER
26 | command_events.send_filehandle_emit(command.args[0])
27 |
28 | "ext_texture_format":
29 | if wrong_args_count(command, 1): return ERR_INVALID_PARAMETER
30 | command_events.ext_texture_format_emit(command.args[0])
31 |
32 | "first_frame":
33 | if wrong_args_count(command, 0): return ERR_INVALID_PARAMETER
34 | gate_events.first_frame_emit()
35 |
36 | "heartbeat":
37 | if wrong_args_count(command, 0): return ERR_INVALID_PARAMETER
38 | command_events.heartbeat_emit()
39 |
40 | "set_mouse_mode":
41 | if wrong_args_count(command, 1): return ERR_INVALID_PARAMETER
42 | command_events.set_mouse_mode_emit(command.args[0])
43 |
44 | "open_gate":
45 | if wrong_args_count(command, 1): return ERR_INVALID_PARAMETER
46 | var url = Url.join(gate_events.current_gate_url, command.args[0])
47 | gate_events.open_gate_emit(url)
48 |
49 | "open_link":
50 | if wrong_args_count(command, 1): return ERR_INVALID_PARAMETER
51 | app_events.open_link_emit(command.args[0])
52 |
53 | "highlight_button":
54 | if wrong_args_count(command, 1): return ERR_INVALID_PARAMETER
55 | command_events.highlight_button_emit(command.args[0])
56 |
57 | _:
58 | Debug.logerr("Command %s not implemented" % [command.name])
59 | return ERR_METHOD_NOT_FOUND
60 |
61 | return OK
62 |
63 |
64 | func wrong_args_count(command: Command, right_count: int) -> bool:
65 | var count = command.args.size()
66 | if count != right_count:
67 | Debug.logerr("Command %s args count should be %d but it's %d" % [command.name, right_count, count])
68 | return true
69 |
70 | return false
71 |
72 |
73 | func _exit_tree() -> void:
74 | close()
75 |
--------------------------------------------------------------------------------
/app/scripts/url.gd:
--------------------------------------------------------------------------------
1 | extends Node
2 | # class_name Url
3 |
4 | const url_regex: String = "^(https?)://[^\\s()<>]+(?:\\([\\w\\d]+\\)|([^[:punct:]\\s]|/))$"
5 |
6 | @export_file("*.txt") var tld_list_file: String
7 | @export var api_settings: ApiSettings
8 |
9 | var regex: RegEx
10 | var tld_list: Dictionary = {}
11 |
12 |
13 | func _ready() -> void:
14 | regex = RegEx.new()
15 | regex.compile(url_regex)
16 |
17 | var file = FileAccess.open(tld_list_file, FileAccess.READ)
18 | var tlds = file.get_as_text().split("\n")
19 | for tld in tlds:
20 | tld_list[tld] = true
21 |
22 |
23 | func join(base_url: String, path: String) -> String:
24 | var url = ""
25 | if path.is_empty():
26 | url = ""
27 | elif path.begins_with("http"):
28 | url = path
29 | else:
30 | url = base_url.get_base_dir() + "/" + path
31 | return url
32 |
33 |
34 | func fix_gate_url(url: String) -> String:
35 | var base_url = url
36 | var query_string = ""
37 | var has_query = url.contains("?")
38 |
39 | if has_query:
40 | var split = url.split("?", true, 1)
41 | base_url = split[0]
42 | query_string = split[1]
43 |
44 | if not base_url.begins_with("http://") and not base_url.begins_with("https://"):
45 | base_url = "https://" + base_url
46 |
47 | if base_url.get_extension() != "gate":
48 | var slash = "" if base_url.ends_with("/") else "/"
49 | base_url += slash + "world.gate"
50 |
51 | base_url = lower_domain(base_url)
52 | url = base_url + "?" + query_string if query_string != "" else base_url
53 | return url
54 |
55 |
56 | func is_valid(url: String) -> bool:
57 | if not url.begins_with("http://") and not url.begins_with("https://"):
58 | var domain = url.split("/")[0]
59 | if is_valid_domain(domain):
60 | url = "https://" + url
61 |
62 | var result = regex.search(url)
63 | return false if result == null else result.get_string() == url
64 |
65 |
66 | func lower_domain(url: String) -> String:
67 | # Assuming https?://domain/*.gate
68 | var split = url.split("/", true, 3)
69 | assert(split.size() == 4, "Invalid URL: " + url)
70 |
71 | var domain = split[2]
72 | return split[0] + "//" + domain.to_lower() + "/" + split[3]
73 |
74 |
75 | func is_valid_domain(domain: String) -> bool:
76 | if domain.is_empty() or domain.split(".").size() < 2:
77 | return false
78 |
79 | return tld_list.has(domain.get_extension().to_lower())
80 |
81 |
82 | func is_trusted_url(url: String) -> bool:
83 | for trusted_url in api_settings.trusted_urls:
84 | if url.begins_with(trusted_url):
85 | return true
86 |
87 | return false
88 |
--------------------------------------------------------------------------------
/app/scenes/components/search/prompt.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=8 format=3 uid="uid://b57n6cvtqn5b7"]
2 |
3 | [ext_resource type="Script" uid="uid://c2sowtufpb1vs" path="res://scripts/ui/search/prompt.gd" id="1_7xv44"]
4 | [ext_resource type="StyleBox" uid="uid://c6dqs0nhh726" path="res://assets/styles/prompt.stylebox" id="1_cbfrs"]
5 | [ext_resource type="Resource" uid="uid://b1xvdym0qh6td" path="res://resources/gate_events.res" id="2_33m26"]
6 | [ext_resource type="LabelSettings" uid="uid://bo2334w4lf3ug" path="res://assets/styles/text.tres" id="3_rbghg"]
7 | [ext_resource type="Texture2D" uid="uid://d05w6jtfy01w2" path="res://assets/textures/clock.svg" id="4_lekwb"]
8 |
9 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_l81vq"]
10 |
11 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6fg0o"]
12 | content_margin_left = 30.0
13 |
14 | [node name="prompt" type="Button" node_paths=PackedStringArray("prompt_text")]
15 | custom_minimum_size = Vector2(0, 32)
16 | anchors_preset = 14
17 | anchor_top = 0.5
18 | anchor_right = 1.0
19 | anchor_bottom = 0.5
20 | grow_horizontal = 2
21 | grow_vertical = 2
22 | mouse_default_cursor_shape = 2
23 | theme_override_styles/normal = SubResource("StyleBoxEmpty_l81vq")
24 | theme_override_styles/pressed = ExtResource("1_cbfrs")
25 | theme_override_styles/hover = ExtResource("1_cbfrs")
26 | script = ExtResource("1_7xv44")
27 | gate_events = ExtResource("2_33m26")
28 | prompt_text = NodePath("Label")
29 | focus_style = ExtResource("1_cbfrs")
30 |
31 | [node name="Label" type="Label" parent="."]
32 | layout_mode = 1
33 | anchors_preset = 15
34 | anchor_right = 1.0
35 | anchor_bottom = 1.0
36 | grow_horizontal = 2
37 | grow_vertical = 2
38 | theme_override_styles/normal = SubResource("StyleBoxEmpty_6fg0o")
39 | text = "prompt"
40 | label_settings = ExtResource("3_rbghg")
41 | vertical_alignment = 1
42 | text_overrun_behavior = 3
43 |
44 | [node name="SearchStatus" type="Control" parent="."]
45 | layout_mode = 1
46 | anchors_preset = 4
47 | anchor_top = 0.5
48 | anchor_bottom = 0.5
49 | offset_left = 10.0
50 | offset_top = -7.0
51 | offset_right = 24.0
52 | offset_bottom = 7.0
53 | grow_vertical = 2
54 | mouse_filter = 1
55 |
56 | [node name="Search" type="TextureRect" parent="SearchStatus"]
57 | self_modulate = Color(0.831373, 0.831373, 0.831373, 1)
58 | layout_mode = 1
59 | anchors_preset = 15
60 | anchor_right = 1.0
61 | anchor_bottom = 1.0
62 | grow_horizontal = 2
63 | grow_vertical = 2
64 | texture = ExtResource("4_lekwb")
65 | expand_mode = 1
66 |
67 | [connection signal="pressed" from="." to="." method="_on_button_pressed"]
68 |
--------------------------------------------------------------------------------
/app/assets/textures/star_color.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
--------------------------------------------------------------------------------
/app/scripts/networking/http_date_utils.gd:
--------------------------------------------------------------------------------
1 | extends RefCounted
2 | class_name HTTPDateUtils
3 |
4 |
5 | # Parses RFC1123 HTTP-date (e.g., "Sat, 07 Jun 2025 00:02:24 GMT") to Unix time (UTC). Returns 0 on failure.
6 | static func parse_http_date_rfc1123(date_str: String) -> int:
7 | var s := date_str.strip_edges()
8 | var comma_idx := s.find(",")
9 | if comma_idx != -1:
10 | s = s.substr(comma_idx + 1, s.length() - comma_idx - 1).strip_edges()
11 | # Expect: DD Mon YYYY HH:MM:SS GMT
12 | var parts := s.split(" ", false)
13 | if parts.size() < 5:
14 | return 0
15 | var day_str := parts[0]
16 | var mon_str := parts[1]
17 | var year_str := parts[2]
18 | var time_str := parts[3]
19 | if not day_str.is_valid_int() or not year_str.is_valid_int():
20 | return 0
21 | var day := int(day_str)
22 | var year := int(year_str)
23 | var month := parse_month_to_number(mon_str)
24 | if month == 0:
25 | return 0
26 | var time_parts := time_str.split(":", false)
27 | if time_parts.size() != 3:
28 | return 0
29 | if not time_parts[0].is_valid_int() or not time_parts[1].is_valid_int() or not time_parts[2].is_valid_int():
30 | return 0
31 | var hour := int(time_parts[0])
32 | var minute := int(time_parts[1])
33 | var second := int(time_parts[2])
34 | return unix_time_from_utc_components(year, month, day, hour, minute, second)
35 |
36 |
37 | static func parse_month_to_number(mon: String) -> int:
38 | match mon.capitalize():
39 | "Jan":
40 | return 1
41 | "Feb":
42 | return 2
43 | "Mar":
44 | return 3
45 | "Apr":
46 | return 4
47 | "May":
48 | return 5
49 | "Jun":
50 | return 6
51 | "Jul":
52 | return 7
53 | "Aug":
54 | return 8
55 | "Sep":
56 | return 9
57 | "Oct":
58 | return 10
59 | "Nov":
60 | return 11
61 | "Dec":
62 | return 12
63 | _:
64 | return 0
65 |
66 |
67 | static func unix_time_from_utc_components(year: int, month: int, day: int, hour: int, minute: int, second: int) -> int:
68 | if year < 1970:
69 | return 0
70 | var days_in_month := [0,31,28,31,30,31,30,31,31,30,31,30,31]
71 | var days := 0
72 | for y in range(1970, year):
73 | days += 365
74 | if is_leap_year(y):
75 | days += 1
76 | if is_leap_year(year):
77 | days_in_month[2] = 29
78 | for m in range(1, month):
79 | days += int(days_in_month[m])
80 | days += (day - 1)
81 | var total_seconds := days * 86400 + hour * 3600 + minute * 60 + second
82 | return total_seconds
83 |
84 |
85 | static func is_leap_year(year: int) -> bool:
86 | return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
87 |
88 | # TODO: cleanup ai generated code
89 |
--------------------------------------------------------------------------------
/app/scenes/components/round_button.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=11 format=3 uid="uid://xagbhqfidf2"]
2 |
3 | [ext_resource type="StyleBox" uid="uid://b4j4kd2p3aypx" path="res://assets/styles/button.stylebox" id="1_6dhuv"]
4 | [ext_resource type="StyleBox" uid="uid://ck127amfs72dv" path="res://assets/styles/button_hover.stylebox" id="2_3cilb"]
5 | [ext_resource type="Texture2D" uid="uid://bevejhgdw7mey" path="res://assets/textures/close.svg" id="3_t5vxw"]
6 | [ext_resource type="Script" uid="uid://chtefdtvq5j01" path="res://scripts/ui/menu/round_button.gd" id="4_7t145"]
7 | [ext_resource type="Shader" uid="uid://k50h5xhmltlj" path="res://shaders/spinning_border.gdshader" id="5_birig"]
8 | [ext_resource type="Resource" uid="uid://l1quiaghft2f" path="res://resources/command_events.res" id="5_ukhpk"]
9 | [ext_resource type="Resource" uid="uid://b1xvdym0qh6td" path="res://resources/gate_events.res" id="6_0kyd1"]
10 |
11 | [sub_resource type="Gradient" id="Gradient_pt5vo"]
12 | offsets = PackedFloat32Array(0, 0.25, 0.5, 0.75, 1)
13 | colors = PackedColorArray(1, 0.2, 0.4, 1, 1, 0.8, 0.2, 1, 0.2, 1, 0.6, 1, 0.2, 0.6, 1, 1, 1, 0.2, 0.4, 1)
14 |
15 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_yl185"]
16 | gradient = SubResource("Gradient_pt5vo")
17 |
18 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_sp2xr"]
19 | shader = ExtResource("5_birig")
20 | shader_parameter/border_thickness = 0.05
21 | shader_parameter/edge_smoothness = 0.01
22 | shader_parameter/corner_radius = Vector4(1.3, 1.3, 1.3, 1.3)
23 | shader_parameter/speed = 1.5
24 | shader_parameter/clockwise = false
25 | shader_parameter/gradient = SubResource("GradientTexture1D_yl185")
26 |
27 | [node name="RoundButton" type="Button" node_paths=PackedStringArray("special_effect")]
28 | texture_filter = 4
29 | custom_minimum_size = Vector2(26, 26)
30 | size_flags_vertical = 4
31 | focus_mode = 0
32 | theme_override_colors/icon_normal_color = Color(0.831373, 0.831373, 0.831373, 1)
33 | theme_override_colors/icon_disabled_color = Color(0.431373, 0.435294, 0.494118, 1)
34 | theme_override_styles/normal = ExtResource("1_6dhuv")
35 | theme_override_styles/pressed = ExtResource("1_6dhuv")
36 | theme_override_styles/hover = ExtResource("2_3cilb")
37 | theme_override_styles/disabled = ExtResource("1_6dhuv")
38 | icon = ExtResource("3_t5vxw")
39 | expand_icon = true
40 | script = ExtResource("4_7t145")
41 | gate_events = ExtResource("6_0kyd1")
42 | command_events = ExtResource("5_ukhpk")
43 | special_effect = NodePath("SpecialEffect")
44 |
45 | [node name="SpecialEffect" type="Panel" parent="."]
46 | visible = false
47 | material = SubResource("ShaderMaterial_sp2xr")
48 | layout_mode = 1
49 | anchors_preset = 15
50 | anchor_right = 1.0
51 | anchor_bottom = 1.0
52 | grow_horizontal = 2
53 | grow_vertical = 2
54 | mouse_filter = 2
55 |
--------------------------------------------------------------------------------