├── 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 | 12 | 14 | 18 | 22 | 23 | 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 | 12 | 14 | 17 | 21 | 22 | 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 | 12 | 14 | 17 | 21 | 25 | 26 | 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 | 12 | 14 | 17 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/assets/textures/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 18 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/assets/textures/maximaze.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/assets/textures/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 18 | 22 | 26 | 27 | 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 | 12 | 14 | 18 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/assets/textures/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 18 | 24 | 28 | 29 | 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 | 12 | 14 | 18 | 24 | 28 | 29 | 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 | 12 | 14 | 18 | 22 | 23 | 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 | 12 | 14 | 18 | 25 | 29 | 30 | 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 | 12 | 14 | 18 | 22 | 26 | 30 | 31 | 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 | 12 | 14 | 18 | 22 | 26 | 27 | 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 | 12 | 14 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/assets/textures/starred.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 16 | 21 | 22 | 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 | 16 | 34 | 36 | 40 | 47 | 48 | 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 | 12 | 14 | 18 | 22 | 27 | 31 | 35 | 36 | 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 | 13 | 15 | 17 | 21 | 25 | 26 | 28 | 32 | 36 | 37 | 45 | 53 | 54 | 56 | 61 | 62 | 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 | --------------------------------------------------------------------------------