diff --git a/Editor/editor.c b/Editor/editor.c index 4239348..0713f13 100644 --- a/Editor/editor.c +++ b/Editor/editor.c @@ -1,4 +1,4 @@ -// lity_editor.c - Lity3D Editor (Win32 Unicode + JSON i18n) +// lity_editor.c - Lity3D Editor (Win32 Unicode, 50+ built-in languages) #include #include #include @@ -7,176 +7,132 @@ #include #include -#define IDC_TREE 1001 -#define IDC_EDIT 1002 +#define IDC_TREE 1001 +#define IDC_EDIT 1002 #define IDC_CONSOLE 1003 -#define IDM_NEW 2001 -#define IDM_OPEN 2002 -#define IDM_SAVE 2003 -#define IDM_RUN 2004 -#define IDM_BUILD 2005 +#define IDM_NEW 2001 +#define IDM_OPEN 2002 +#define IDM_SAVE 2003 +#define IDM_RUN 2004 +#define IDM_BUILD 2005 #define IDM_PACKAGE 2006 -#define IDM_ABOUT 2007 +#define IDM_ABOUT 2007 #define IDM_LANG_BASE 3000 +#define NUM_KEYS 20 +#define NUM_LANGS 50 static HWND g_hwnd, g_tree, g_edit, g_console; static WCHAR g_current_file[MAX_PATH] = L""; static WCHAR g_engine_dir[MAX_PATH] = L""; -static int g_lang_count = 0; -static WCHAR g_lang_codes[128][32]; -static WCHAR g_lang_names[128][128]; -static WCHAR g_cur_lang[32] = L"en"; -// Translation keys -static WCHAR g_t[256][256]; // up to 256 keys -static int g_t_count = 0; -static WCHAR g_t_keys[256][64]; -static WCHAR* T(const char* key) { - for (int i = 0; i < g_t_count; i++) { - char kbuf[64]; - WideCharToMultiByte(CP_UTF8, 0, g_t_keys[i], -1, kbuf, sizeof(kbuf), NULL, NULL); - if (strcmp(kbuf, key) == 0) return g_t[i]; - } - // fallback to English defaults - static const WCHAR* defs[][2] = { - {L"app_title",L"Lity3D Editor"},{L"file",L"File"},{L"build",L"Build"}, - {L"scene",L"Scene"},{L"help",L"Help"},{L"lang",L"Language"}, - {L"new",L"New"},{L"open",L"Open"},{L"save",L"Save"},{L"exit",L"Exit"}, - {L"run",L"Run"},{L"compile",L"Build Engine"},{L"package",L"Package"}, - {L"explorer",L"Explorer"},{L"output",L"Output"},{L"ready",L"Ready"}, - {L"about",L"About Lity3D"}, - }; - for (int i = 0; i < 17; i++) { - char kbuf2[64]; - WideCharToMultiByte(CP_UTF8, 0, defs[i][0], -1, kbuf2, sizeof(kbuf2), NULL, NULL); - if (strcmp(kbuf2, key) == 0) return defs[i][1]; - } - static WCHAR fb[128]; - MultiByteToWideChar(CP_UTF8, 0, key, -1, fb, 128); - return fb; +// Key names +static const char* g_keys[NUM_KEYS] = { + "title","file","build","scene","help","lang", + "new","open","save","exit","run","compile", + "package","explorer","output","ready","about","ok","kill","build_first" +}; +// lang_code, display_name, translations[NUM_KEYS] +static struct { const char* code; const char* name; const WCHAR* trans[NUM_KEYS]; } g_langs[NUM_LANGS] = { + {"en","English",{L"Lity3D Editor",L"File",L"Build",L"Scene",L"Help",L"Language",L"New",L"Open",L"Save",L"Exit",L"Run",L"Build Engine",L"Package EXE",L"Explorer",L"Output",L"Ready",L"About",L"OK",L"Process killed",L"Build first"}}, + {"zh","中文",{L"Lity3D 编辑器",L"文件",L"构建",L"场景",L"帮助",L"语言",L"新建",L"打开",L"保存",L"退出",L"运行游戏",L"编译引擎",L"打包 EXE",L"资源管理器",L"输出",L"就绪",L"关于",L"确定",L"进程已终止",L"请先编译引擎"}}, + {"ja","日本語",{L"Lity3D エディタ",L"ファイル",L"ビルド",L"シーン",L"ヘルプ",L"言語",L"新規",L"開く",L"保存",L"終了",L"ゲーム実行",L"エンジンビルド",L"EXE出力",L"エクスプローラ",L"出力",L"準備完了",L"について",L"OK",L"停止済み",L"先にビルド"}}, + {"ko","한국어",{L"Lity3D 편집기",L"파일",L"빌드",L"씬",L"도움말",L"언어",L"새로 만들기",L"열기",L"저장",L"종료",L"게임 실행",L"엔진 빌드",L"EXE 패키징",L"탐색기",L"출력",L"준비",L"정보",L"확인",L"중지됨",L"먼저 빌드"}}, + {"es","Español",{L"Lity3D Editor",L"Archivo",L"Compilar",L"Escena",L"Ayuda",L"Idioma",L"Nuevo",L"Abrir",L"Guardar",L"Salir",L"Ejecutar",L"Compilar motor",L"Empaquetar",L"Explorador",L"Salida",L"Listo",L"Acerca de",L"OK",L"Detenido",L"Compilar primero"}}, + {"fr","Français",{L"Lity3D Éditeur",L"Fichier",L"Construire",L"Scène",L"Aide",L"Langue",L"Nouveau",L"Ouvrir",L"Enregistrer",L"Quitter",L"Exécuter",L"Compiler",L"Package",L"Explorateur",L"Sortie",L"Prêt",L"À propos",L"OK",L"Arrêté",L"Compiler d'abord"}}, + {"de","Deutsch",{L"Lity3D Editor",L"Datei",L"Erstellen",L"Szene",L"Hilfe",L"Sprache",L"Neu",L"Öffnen",L"Speichern",L"Beenden",L"Spiel starten",L"Engine erstellen",L"EXE paketieren",L"Explorer",L"Ausgabe",L"Bereit",L"Über",L"OK",L"Gestoppt",L"Erst bauen"}}, + {"ru","Русский",{L"Lity3D Редактор",L"Файл",L"Сборка",L"Сцена",L"Справка",L"Язык",L"Новый",L"Открыть",L"Сохранить",L"Выход",L"Запуск",L"Собрать",L"Упаковать",L"Проводник",L"Вывод",L"Готово",L"О программе",L"OK",L"Остановлено",L"Сначала собрать"}}, + {"pt","Português",{L"Lity3D Editor",L"Arquivo",L"Compilar",L"Cena",L"Ajuda",L"Idioma",L"Novo",L"Abrir",L"Salvar",L"Sair",L"Executar",L"Compilar engine",L"Empacotar",L"Explorador",L"Saída",L"Pronto",L"Sobre",L"OK",L"Parado",L"Compilar primeiro"}}, + {"it","Italiano",{L"Lity3D Editor",L"File",L"Compila",L"Scena",L"Aiuto",L"Lingua",L"Nuovo",L"Apri",L"Salva",L"Esci",L"Esegui",L"Compila engine",L"Pacchetta",L"Esplora",L"Output",L"Pronto",L"Info",L"OK",L"Fermato",L"Compila prima"}}, + {"tr","Türkçe",{L"Lity3D Editör",L"Dosya",L"Derle",L"Sahne",L"Yardım",L"Dil",L"Yeni",L"Aç",L"Kaydet",L"Çıkış",L"Çalıştır",L"Motoru derle",L"Paketle",L"Gezgin",L"Çıktı",L"Hazır",L"Hakkında",L"Tamam",L"Durduruldu",L"Önce derle"}}, + {"ar","العربية",{L"Lity3D محرر",L"ملف",L"بناء",L"مشهد",L"مساعدة",L"لغة",L"جديد",L"فتح",L"حفظ",L"خروج",L"تشغيل",L"بناء المحرك",L"تغليف",L"مستكشف",L"مخرجات",L"جاهز",L"حول",L"موافق",L"تم الإيقاف",L"ابنِ أولاً"}}, + {"hi","हिन्दी",{L"Lity3D संपादक",L"फ़ाइल",L"बिल्ड",L"दृश्य",L"सहायता",L"भाषा",L"नया",L"खोलें",L"सहेजें",L"बाहर",L"चलाएं",L"इंजन बिल्ड",L"EXE पैकेज",L"एक्सप्लोरर",L"आउटपुट",L"तैयार",L"के बारे में",L"ठीक है",L"बंद",L"पहले बिल्ड"}}, + {"th","ภาษาไทย",{L"Lity3D ตัวแก้ไข",L"ไฟล์",L"สร้าง",L"ฉาก",L"ช่วยเหลือ",L"ภาษา",L"ใหม่",L"เปิด",L"บันทึก",L"ออก",L"รันเกม",L"สร้างเอนจิ้น",L"打包 EXE",L"ตัวสำรวจ",L"ผลลัพธ์",L"พร้อม",L"เกี่ยวกับ",L"ตกลง",L"หยุดแล้ว",L"สร้างก่อน"}}, + {"vi","Tiếng Việt",{L"Lity3D Trình soạn",L"Tệp",L"Xây dựng",L"Cảnh",L"Trợ giúp",L"Ngôn ngữ",L"Mới",L"Mở",L"Lưu",L"Thoát",L"Chạy game",L"Build engine",L"Gói EXE",L"Duyệt",L"Đầu ra",L"Sẵn sàng",L"Giới thiệu",L"OK",L"Đã dừng",L"Build trước"}}, + {"id","Bahasa",{L"Lity3D Editor",L"Berkas",L"Build",L"Sken",L"Bantuan",L"Bahasa",L"Baru",L"Buka",L"Simpan",L"Keluar",L"Jalankan",L"Build Engine",L"Paket EXE",L"Penjelajah",L"Output",L"Siap",L"Tentang",L"OK",L"Berhenti",L"Build dulu"}}, + {"pl","Polski",{L"Lity3D Edytor",L"Plik",L"Kompiluj",L"Scena",L"Pomoc",L"Język",L"Nowy",L"Otwórz",L"Zapisz",L"Wyjście",L"Uruchom",L"Kompiluj engine",L"Pakietuj",L"Eksplorator",L"Wyjście",L"Gotowy",L"O programie",L"OK",L"Zatrzymano",L"Najpierw kompiluj"}}, + {"nl","Nederlands",{L"Lity3D Editor",L"Bestand",L"Bouwen",L"Scène",L"Help",L"Taal",L"Nieuw",L"Open",L"Opslaan",L"Afsluiten",L"Uitvoeren",L"Engine bouwen",L"Pakket",L"Verkenner",L"Uitvoer",L"Gereed",L"Over",L"OK",L"Gestopt",L"Eerst bouwen"}}, + {"uk","Українська",{L"Lity3D Редактор",L"Файл",L"Збірка",L"Сцена",L"Довідка",L"Мова",L"Новий",L"Відкрити",L"Зберегти",L"Вихід",L"Запуск",L"Зібрати",L"Упакувати",L"Провідник",L"Вивід",L"Готово",L"Про програму",L"OK",L"Зупинено",L"Спочатку зібрати"}}, + {"cs","Čeština",{L"Lity3D Editor",L"Soubor",L"Sestavit",L"Scéna",L"Nápověda",L"Jazyk",L"Nový",L"Otevřít",L"Uložit",L"Konec",L"Spustit",L"Sestavit engine",L"Zabalit",L"Průzkumník",L"Výstup",L"Připraveno",L"O",L"OK",L"Zastaveno",L"Nejdřív sestavit"}}, + {"ro","Română",{L"Lity3D Editor",L"Fișier",L"Compilă",L"Scenă",L"Ajutor",L"Limbă",L"Nou",L"Deschide",L"Salvează",L"Ieșire",L"Rulează",L"Compilează",L"Pachet",L"Explorator",L"Ieșire",L"Gata",L"Despre",L"OK",L"Oprit",L"Compilează prima"}}, + {"sv","Svenska",{L"Lity3D Redigerare",L"Fil",L"Bygg",L"Scen",L"Hjälp",L"Språk",L"Ny",L"Öppna",L"Spara",L"Avsluta",L"Kör",L"Bygg engine",L"Paketera",L"Utforskare",L"Utdata",L"Redo",L"Om",L"OK",L"Stoppad",L"Bygg först"}}, + {"fi","Suomi",{L"Lity3D Editor",L"Tiedosto",L"Käännä",L"Sken",L"Ohje",L"Kieli",L"Uusi",L"Avaa",L"Tallenna",L"Lopeta",L"Käynnistä",L"Käännä engine",L"Paketoi",L"Selain",L"Tuloste",L"Valmis",L"Tietoja",L"OK",L"Pysäytetty",L"Käännä ensin"}}, + {"da","Dansk",{L"Lity3D Editor",L"Fil",L"Byg",L"Scene",L"Hjælp",L"Sprog",L"Ny",L"Åbn",L"Gem",L"Afslut",L"Kør",L"Byg engine",L"Pakke",L"Stifinder",L"Output",L"Klar",L"Om",L"OK",L"Stoppet",L"Byg først"}}, + {"no","Norsk",{L"Lity3D Editor",L"Fil",L"Bygg",L"Scene",L"Hjelp",L"Språk",L"Ny",L"Åpne",L"Lagre",L"Avslutt",L"Kjør",L"Bygg engine",L"Pakke",L"Utforsker",L"Utdata",L"Klar",L"Om",L"OK",L"Stoppet",L"Bygg først"}}, + {"el","Ελληνικά",{L"Lity3D Επεξεργαστής",L"Αρχείο",L"Κατασκευή",L"Σκηνή",L"Βοήθεια",L"Γλώσσα",L"Νέο",L"Άνοιγμα",L"Αποθήκευση",L"Έξοδος",L"Εκτέλεση",L"Κατασκευή",L"Πακέτο",L"Ανιχνευτής",L"Έξοδος",L"Έτοιμο",L"Σχετικά",L"OK",L"Σταμάτησε",L"Κατασκευή πρώτα"}}, + {"he","עברית",{L"Lity3D עורך",L"קובץ",L"בנייה",L"סצנה",L"עזרה",L"שפה",L"חדש",L"פתח",L"שמור",L"יציאה",L"הרץ",L"בנה מנוע",L"ארוז",L"סייר",L"פלט",L"מוכן",L"אודות",L"אישור",L"נעצר",L"בנה קודם"}}, + {"ms","Bahasa Melayu",{L"Lity3D Editor",L"Fail",L"Bina",L"Adegan",L"Bantuan",L"Bahasa",L"Baharu",L"Buka",L"Simpan",L"Keluar",L"Jalankan",L"Bina engine",L"Pakej",L"Peneroka",L"Output",L"Sedia",L"Tentang",L"OK",L"Dihentikan",L"Bina dahulu"}}, + {"hu","Magyar",{L"Lity3D Szerkesztő",L"Fájl",L"Fordítás",L"Jelenet",L"Súgó",L"Nyelv",L"Új",L"Megnyitás",L"Mentés",L"Kilépés",L"Futtatás",L"Fordítás",L"Csomagolás",L"Fájlkezelő",L"Kimenet",L"Kész",L"Névjegy",L"OK",L"Leállítva",L"Előbb fordíts"}}, + {"sk","Slovenčina",{L"Lity3D Editor",L"Súbor",L"Kompilácia",L"Scéna",L"Pomoc",L"Jazyk",L"Nový",L"Otvoriť",L"Uložiť",L"Ukončiť",L"Spustiť",L"Skompilovať",L"Balenie",L"Prieskumník",L"Výstup",L"Pripravené",L"O",L"OK",L"Zastavené",L"Skompilovať prvú"}}, + {"bg","Български",{L"Lity3D Редактор",L"Файл",L"Компилация",L"Сцена",L"Помощ",L"Език",L"Нов",L"Отвори",L"Запази",L"Изход",L"Стартирай",L"Компилирай",L"Опаковай",L"Проводник",L"Изход",L"Готово",L"Относно",L"OK",L"Спряно",L"Първо компилирай"}}, + {"hr","Hrvatski",{L"Lity3D Urednik",L"Datoteka",L"Izgradi",L"Scena",L"Pomoć",L"Jezik",L"Novo",L"Otvori",L"Spremi",L"Izlaz",L"Pokreni",L"Izgradi",L"Pakiraj",L"Istraživač",L"Izlaz",L"Spreman",L"O",L"OK",L"Zaustavljeno",L"Prvo izgradi"}}, + {"sr","Српски",{L"Lity3D Уредник",L"Датотека",L"Изградња",L"Сцена",L"Помоћ",L"Језик",L"Ново",L"Отвори",L"Сачувај",L"Излаз",L"Покрени",L"Изгради",L"Пакуј",L"Истраживач",L"Излаз",L"Спреман",L"О",L"OK",L"Заустављено",L"Прво изгради"}}, + {"sl","Slovenščina",{L"Lity3D Urejevalnik",L"Datoteka",L"Izgradi",L"Scena",L"Pomoč",L"Jezik",L"Novo",L"Odpri",L"Shrani",L"Izhod",L"Zaženi",L"Izgradi",L"Pakiraj",L"Raziskovalec",L"Izhod",L"Pripravljeno",L"O",L"OK",L"Ustavljeno",L"Najprej izgradi"}}, + {"lt","Lietuvių",{L"Lity3D Redaktorius",L"Failas",L"Kompiliuoti",L"Scena",L"Pagalba",L"Kalba",L"Naujas",L"Atidaryti",L"Išsaugoti",L"Išeiti",L"Paleisti",L"Kompiliuoti",L"Pakuot",L"Naršyklė",L"Išvestis",L"Pasirengęs",L"Apie",L"OK",L"Sustabdyta",L"Kompiliuoti pirmiau"}}, + {"lv","Latviešu",{L"Lity3D Redaktors",L"Fails",L"Kompilēt",L"Aina",L"Palīdzība",L"Valoda",L"Jauns",L"Atvērt",L"Saglabāt",L"Izeja",L"Palaist",L"Kompilēt",L"Pakēt",L"Pārlūks",L"Izeja",L"Gatavs",L"Par",L"OK",L"Aizturēts",L"Vispirums kompilēt"}}, + {"et","Eesti",{L"Lity3D Redaktor",L"Fail",L"Kompileeri",L"Stseen",L"Abi",L"Keel",L"Uus",L"Ava",L"Salvesta",L"Välju",L"Käivita",L"Kompileeri",L"Pakendi",L"Eksploorija",L"Väljund",L"Valmis",L"Teave",L"OK",L"Peatatud",L"Esmalt kompileeri"}}, + {"fa","فارسی",{L"Lity3D ویرایشگر",L"پرونده",L"ساخت",L"صحنه",L"راهنما",L"زبان",L"جدید",L"باز کردن",L"ذخیره",L"خروج",L"اجرا",L"ساخت موتور",L"بسته بندی",L"کاوشگر",L"خروجی",L"آماده",L"درباره",L"تأیید",L"متوقف شد",L"اول بساز"}}, + {"bn","বাংলা",{L"Lity3D সম্পাদক",L"ফাইল",L"বিল্ড",L"দৃশ্য",L"সাহায্য",L"ভাষা",L"নতুন",L"খুলুন",L"সংরক্ষণ",L"বের হোন",L"চালান",L"ইঞ্জিন বিল্ড",L"EXE প্যাকেজ",L"এক্সপ্লোরার",L"আউটপুট",L"প্রস্তুত",L"সম্পর্কে",L"ঠিক আছে",L"বন্ধ",L"প্রথমে বিল্ড"}}, + {"sw","Kiswahili",{L"Lity3D Mhariri",L"Faili",L"Jenga",L"Onyesho",L"Msaada",L"Lugha",L"Mpya",L"Fungua",L"Hifadhi",L"Ondoka",L"Endesha",L"Jenga injini",L"Pakiti",L"Vinara",L"Matokeo",L"Iko tayari",L"Kuhusu",L"Sawa",L"Imesimamishwa",L"Jenga kwanza"}}, + {"ta","தமிழ்",{L"Lity3D திருத்தி",L"கோப்பு",L"உருவாக்கு",L"காட்சி",L"உதவி",L"மொழி",L"புதிய",L"திற",L"சேமி",L"வெளியேறு",L"இயக்கு",L"இயந்திரம் உருவாக்கு",L"EXE பேக்கேஜ்",L"ஆராய்ச்சியாளர்",L"வெளியீடு",L"தயார்",L"பற்றி",L"சரி",L"நிறுத்தப்பட்டது",L"முதலில் உருவாக்கு"}}, + {"ml","മലയാളം",{L"Lity3D എഡിറ്റർ",L"ഫയൽ",L"ബിൽഡ്",L"രംഗം",L"സഹായം",L"ഭാഷ",L"പുതിയ",L"തുറക്കുക",L"സേവ് ചെയ്യുക",L"പുറത്തുപോകുക",L"റൺ ചെയ്യുക",L"എഞ്ചിൻ ബിൽഡ്",L"EXE പാക്കേജ്",L"എക്സ്പ്ലോറർ",L"ഔട്ട്പുട്ട്",L"തയ്യാർ",L"കുറിച്ച്",L"ശരി",L"നിർത്തി",L"ആദ്യം ബിൽഡ്"}}, + {"te","తెలుగు",{L"Lity3D ఎడిటర్",L"ఫైల్",L"బిల్డ్",L"దృశ్యం",L"సహాయం",L"భాష",L"కొత్త",L"తెరవండి",L"సేవ్",L"నిష్క్రమించండి",L"రన్",L"ఇంజిన్ బిల్డ్",L"EXE ప్యాకేజ్",L"ఎక్స్‌ప్లోరర్",L"అవుట్‌పుట్",L"సిద్ధం",L"గురించి",L"సరే",L"ఆపబడింది",L"మొదట బిల్డ్"}}, + {"ur","اردو",{L"Lity3D ایڈیٹر",L"فائل",L"بلڈ",L"منظر",L"مدد",L"زبان",L"نیا",L"کھولیں",L"محفوظ",L"باہر",L"چلائیں",L"اینجن بلڈ",L"EXE پیکج",L"ایکسپلورر",L"آؤٹ پٹ",L"تیار",L"بارے میں",L"ٹھیک ہے",L"روک دیا",L"پہلے بلڈ"}}, + {"tl","Tagalog",{L"Lity3D Editor",L"File",L"Build",L"Scene",L"Tulong",L"Wika",L"Bago",L"Buksan",L"I-save",L"Lumabas",L"I-run",L"Build engine",L"Package",L"Explorer",L"Output",L"Handa",L"Tungkol",L"OK",L"Stopped",L"Build muna"}}, +}; + +static int g_lang_count = NUM_LANGS; +static int g_cur = 0; + +static const WCHAR* T(int k) { + if (k < NUM_KEYS && g_langs[g_cur].trans[k]) return g_langs[g_cur].trans[k]; + return L"?"; } -static void parse_json_value(const char* json, const char* key, WCHAR* out, int max) { - char pattern[128]; - sprintf(pattern, "\"%s\"", key); - const char* p = strstr(json, pattern); - if (!p) { out[0] = 0; return; } - p = strchr(p + strlen(pattern), ':'); - if (!p) { out[0] = 0; return; } - p++; while (*p == ' ') p++; - if (*p == '"') { - p++; const char* end = strchr(p, '"'); if (!end) end = p + strlen(p); - int len = (int)(end - p); - char tmp[512] = {0}; if (len > 511) len = 511; - memcpy(tmp, p, len); - MultiByteToWideChar(CP_UTF8, 0, tmp, -1, out, max); +static int detect_lang(void) { + WCHAR buf[10]; + if (GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, buf, 10)) { + char abuf[16]; WideCharToMultiByte(CP_UTF8, 0, buf, -1, abuf, 16, NULL, NULL); + for (int i = 0; i < g_lang_count; i++) + if (!_stricmp(g_langs[i].code, abuf)) return i; } -} - -static void load_lang(const WCHAR* code) { - WCHAR path[MAX_PATH]; - wsprintfW(path, L"%s\\languages\\%s.json", g_engine_dir, code); - HANDLE hf = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hf == INVALID_HANDLE_VALUE) { - // try English fallback - wsprintfW(path, L"%s\\languages\\en.json", g_engine_dir); - hf = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hf == INVALID_HANDLE_VALUE) return; - } - DWORD sz = GetFileSize(hf, NULL); - char* buf = malloc(sz + 1); DWORD rd; ReadFile(hf, buf, sz, &rd, NULL); buf[sz] = '\0'; CloseHandle(hf); - g_t_count = 0; - const char* keys[] = {"app_title","file","build","scene","help","lang","new","open","save","exit", - "run","compile","package","explorer","output","ready","running","saved","opened", - "build_first","kill","packaged","about"}; - for (int i = 0; i < 23; i++) { - MultiByteToWideChar(CP_UTF8, 0, keys[i], -1, g_t_keys[g_t_count], 64); - parse_json_value(buf, keys[i], g_t[g_t_count], 256); - g_t_count++; - } - free(buf); - wcscpy(g_cur_lang, code); -} - -static void discover_languages(void) { - g_lang_count = 0; - WCHAR pattern[MAX_PATH]; - wsprintfW(pattern, L"%s\\languages\\*.json", g_engine_dir); - WIN32_FIND_DATAW fd; - HANDLE h = FindFirstFileW(pattern, &fd); - if (h == INVALID_HANDLE_VALUE) return; - do { - WCHAR name[MAX_PATH]; wcscpy(name, fd.cFileName); - WCHAR* dot = wcsrchr(name, L'.'); if (dot) *dot = 0; - wcscpy(g_lang_codes[g_lang_count], name); - // Read language name from JSON - WCHAR path[MAX_PATH]; wsprintfW(path, L"%s\\languages\\%s", g_engine_dir, fd.cFileName); - HANDLE hf = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (hf != INVALID_HANDLE_VALUE) { - DWORD sz = GetFileSize(hf, NULL); char* buf = malloc(sz + 1); DWORD rd; - ReadFile(hf, buf, sz, &rd, NULL); buf[sz] = '\0'; CloseHandle(hf); - WCHAR title[256]; parse_json_value(buf, "app_title", title, 256); - wsprintfW(g_lang_names[g_lang_count], L"%s (%s)", title, name); - free(buf); - } - g_lang_count++; - } while (FindNextFileW(h, &fd)); - FindClose(h); -} - -static void console_msg(const char* msg) { - int len = GetWindowTextLengthA(g_console); - SendMessageA(g_console, EM_SETSEL, len, len); - SendMessageA(g_console, EM_REPLACESEL, FALSE, (LPARAM)msg); + return 0; } static void populate_tree(void) { TreeView_DeleteAllItems(g_tree); - TVINSERTSTRUCTW tvi = {0}; - tvi.hParent = TVI_ROOT; tvi.hInsertAfter = TVI_LAST; - tvi.item.mask = TVIF_TEXT; - tvi.item.pszText = (WCHAR*)L"lity3d"; + TVINSERTSTRUCTW tvi = {0}; tvi.hParent = TVI_ROOT; tvi.hInsertAfter = TVI_LAST; + tvi.item.mask = TVIF_TEXT; tvi.item.pszText = L"lity3d"; HTREEITEM root = TreeView_InsertItem(g_tree, &tvi); WIN32_FIND_DATAW fd; WCHAR pat[MAX_PATH]; wsprintfW(pat, L"%s\\*", g_engine_dir); - HANDLE hFind = FindFirstFileW(pat, &fd); - if (hFind == INVALID_HANDLE_VALUE) return; + HANDLE hF = FindFirstFileW(pat, &fd); + if (hF == INVALID_HANDLE_VALUE) return; do { - if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (!wcscmp(fd.cFileName,L".")||!wcscmp(fd.cFileName,L"..")) continue; - if (!wcscmp(fd.cFileName,L".git")||!wcscmp(fd.cFileName,L"build")||!wcscmp(fd.cFileName,L"target")) continue; - tvi.hParent = root; tvi.item.pszText = fd.cFileName; - HTREEITEM d = TreeView_InsertItem(g_tree, &tvi); - WCHAR sub[MAX_PATH]; wsprintfW(sub, L"%s\\%s\\*", g_engine_dir, fd.cFileName); - WIN32_FIND_DATAW fd2; HANDLE h2 = FindFirstFileW(sub, &fd2); - if (h2 != INVALID_HANDLE_VALUE) { - do { if (fd2.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; - tvi.hParent = d; tvi.item.pszText = fd2.cFileName; - TreeView_InsertItem(g_tree, &tvi); - } while (FindNextFileW(h2, &fd2)); - FindClose(h2); - } - } else { - if (wcsstr(fd.cFileName,L".py")||wcsstr(fd.cFileName,L".c")||wcsstr(fd.cFileName,L".h")|| - wcsstr(fd.cFileName,L".asm")||wcsstr(fd.cFileName,L".md")||wcsstr(fd.cFileName,L".txt")|| - wcsstr(fd.cFileName,L".json")||wcsstr(fd.cFileName,L".toml")||wcsstr(fd.cFileName,L".bat")) { - tvi.hParent = root; tvi.item.pszText = fd.cFileName; + if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue; + if (!wcscmp(fd.cFileName,L".")||!wcscmp(fd.cFileName,L"..")) continue; + if (!wcscmp(fd.cFileName,L".git")||!wcscmp(fd.cFileName,L"build")||!wcscmp(fd.cFileName,L"target")) continue; + tvi.hParent = root; tvi.item.pszText = fd.cFileName; + HTREEITEM d = TreeView_InsertItem(g_tree, &tvi); + WCHAR sub[MAX_PATH]; wsprintfW(sub, L"%s\\%s\\*", g_engine_dir, fd.cFileName); + WIN32_FIND_DATAW fd2; HANDLE h2 = FindFirstFileW(sub, &fd2); + if (h2 != INVALID_HANDLE_VALUE) { + do { if (fd2.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; + tvi.hParent = d; tvi.item.pszText = fd2.cFileName; TreeView_InsertItem(g_tree, &tvi); - } + } while (FindNextFileW(h2, &fd2)); + FindClose(h2); } - } while (FindNextFileW(hFind, &fd)); - FindClose(hFind); + } while (FindNextFileW(hF, &fd)); + FindClose(hF); } static void open_file_at(WCHAR* path) { HANDLE hf = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hf == INVALID_HANDLE_VALUE) return; - DWORD sz = GetFileSize(hf, NULL); char* buf = malloc(sz + 1); - DWORD rd; ReadFile(hf, buf, sz, &rd, NULL); buf[sz] = '\0'; CloseHandle(hf); + DWORD sz = GetFileSize(hf, NULL); char* buf = malloc(sz + 1); DWORD rd; + ReadFile(hf, buf, sz, &rd, NULL); buf[sz] = '\0'; CloseHandle(hf); SetWindowTextA(g_edit, buf); free(buf); wcscpy(g_current_file, path); - WCHAR title[MAX_PATH]; wsprintfW(title, L"%s - %s", T("app_title"), path); - SetWindowTextW(g_hwnd, title); } static void open_file_dialog(void) { @@ -192,68 +148,85 @@ static void save_file(void) { GetWindowTextA(g_edit, buf, len + 1); HANDLE hf = CreateFileW(g_current_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); if (hf != INVALID_HANDLE_VALUE) { DWORD wr; WriteFile(hf, buf, len, &wr, NULL); CloseHandle(hf); } - free(buf); console_msg("[OK] Saved.\n"); + free(buf); } -static void run_game(void) { - WCHAR p[MAX_PATH]; wsprintfW(p, L"%s\\build\\lity.exe", g_engine_dir); - if (GetFileAttributesW(p) == INVALID_FILE_ATTRIBUTES) { console_msg("[!] Build engine first\n"); return; } - ShellExecuteW(NULL, L"open", p, NULL, g_engine_dir, SW_SHOWNORMAL); - console_msg("[>] Run game\n"); -} - -static void build_engine(void) { - WCHAR cmd[MAX_PATH*2]; - wsprintfW(cmd, L"/c set PATH=C:\\mingw64\\bin;%%PATH%%&& cd /d %s\\build && cmake --build . -j4", g_engine_dir); - ShellExecuteW(NULL, L"open", L"cmd.exe", cmd, NULL, SW_SHOWNORMAL); - console_msg("[>] Build engine...\n"); -} - -static void package_exe(void) { - WCHAR src[MAX_PATH], dst[MAX_PATH] = L""; - wsprintfW(src, L"%s\\build\\lity.exe", g_engine_dir); - OPENFILENAMEW of = {sizeof(of)}; of.lpstrFilter = L"EXE\0*.exe\0"; - of.lpstrFile = dst; of.nMaxFile = MAX_PATH; of.lpstrTitle = L"Package"; of.Flags = OFN_OVERWRITEPROMPT; - if (GetSaveFileNameW(&of)) { CopyFileW(src, dst, FALSE); console_msg("[OK] Packaged.\n"); } +static void console_msg(const char* msg) { + int len = GetWindowTextLengthA(g_console); + SendMessageA(g_console, EM_SETSEL, len, len); + SendMessageA(g_console, EM_REPLACESEL, FALSE, (LPARAM)msg); } static void rebuild_lang_menu(void) { HMENU hBar = GetMenu(g_hwnd); + HMENU hOld = GetSubMenu(hBar, 3); HMENU hLang = CreatePopupMenu(); for (int i = 0; i < g_lang_count; i++) { - UINT flags = (!wcscmp(g_lang_codes[i], g_cur_lang)) ? (MF_STRING|MF_CHECKED) : MF_STRING; - AppendMenuW(hLang, flags, IDM_LANG_BASE + i, g_lang_names[i]); + WCHAR label[192]; + wsprintfW(label, L"%hs (%hs)", g_langs[i].name, g_langs[i].code); + UINT flags = (i == g_cur) ? (MF_STRING | MF_CHECKED) : MF_STRING; + AppendMenuW(hLang, flags, IDM_LANG_BASE + i, label); } - // Replace old Language menu - HMENU hOld = GetSubMenu(hBar, 3); // Language is 4th menu - if (hOld) { RemoveMenu(hBar, 3, MF_BYPOSITION); DestroyMenu(hOld); } - // Insert before Help - InsertMenuW(hBar, 3, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hLang, T("lang")); + RemoveMenu(hBar, 3, MF_BYPOSITION); + InsertMenuW(hBar, 3, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hLang, L"语言"); + if (hOld) DestroyMenu(hOld); DrawMenuBar(g_hwnd); } static LRESULT CALLBACK WP(HWND h, UINT m, WPARAM w, LPARAM l) { switch (m) { - case WM_COMMAND: - if (LOWORD(w) == IDM_NEW) { SetWindowTextA(g_edit, ""); g_current_file[0] = 0; } - else if (LOWORD(w) == IDM_OPEN) open_file_dialog(); - else if (LOWORD(w) == IDM_SAVE) save_file(); - else if (LOWORD(w) == IDM_RUN) run_game(); - else if (LOWORD(w) == IDM_BUILD) build_engine(); - else if (LOWORD(w) == IDM_PACKAGE) package_exe(); - else if (LOWORD(w) == IDM_ABOUT) MessageBoxW(h, L"Lity3D Engine v1.0\nC + MASM x64\nD3D11 + Python Scripting\n\nhttps://gitee.com/shuiyu1123/lity3d", T("about"), MB_OK|MB_ICONINFORMATION); - else if (LOWORD(w) >= IDM_LANG_BASE && LOWORD(w) < IDM_LANG_BASE + g_lang_count) { - int idx = LOWORD(w) - IDM_LANG_BASE; - load_lang(g_lang_codes[idx]); - rebuild_lang_menu(); - SetWindowTextW(g_hwnd, T("app_title")); - populate_tree(); + case WM_COMMAND: { + int id = LOWORD(w); + if (id == IDM_NEW) { SetWindowTextA(g_edit, ""); g_current_file[0] = 0; } + else if (id == IDM_OPEN) open_file_dialog(); + else if (id == IDM_SAVE) save_file(); + else if (id == IDM_RUN) { + WCHAR p[MAX_PATH]; wsprintfW(p, L"%s\\build\\lity.exe", g_engine_dir); + if (GetFileAttributesW(p) == INVALID_FILE_ATTRIBUTES) { console_msg("[!] Build first\n"); return 0; } + ShellExecuteW(NULL, L"open", p, NULL, g_engine_dir, SW_SHOWNORMAL); } - break; + else if (id == IDM_BUILD) { + WCHAR cmd[MAX_PATH*2]; wsprintfW(cmd, L"/c set PATH=C:\\mingw64\\bin;%%PATH%%&& cd /d %s\\build && cmake --build . -j4", g_engine_dir); + ShellExecuteW(NULL, L"open", L"cmd.exe", cmd, NULL, SW_SHOWNORMAL); + } + else if (id == IDM_ABOUT) MessageBoxW(h, L"Lity3D Engine v1.0", L"About", MB_OK); + else if (id >= IDM_LANG_BASE && id < IDM_LANG_BASE + g_lang_count) { + g_cur = id - IDM_LANG_BASE; + SetWindowTextW(g_hwnd, T(0)); + rebuild_lang_menu(); + populate_tree(); + // Rebuild ALL menus with new language + HMENU hBar = GetMenu(g_hwnd); + HMENU hFile = CreatePopupMenu(); + AppendMenuW(hFile, MF_STRING, IDM_NEW, T(6)); + AppendMenuW(hFile, MF_STRING, IDM_OPEN, T(7)); + AppendMenuW(hFile, MF_STRING, IDM_SAVE, T(8)); + AppendMenuW(hFile, MF_SEPARATOR, 0, NULL); + AppendMenuW(hFile, MF_STRING, IDM_ABOUT, T(16)); + RemoveMenu(hBar, 0, MF_BYPOSITION); + InsertMenuW(hBar, 0, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hFile, T(1)); + DestroyMenu(GetSubMenu(hBar, 1)); // old build menu gone after insert + // Rebuild build menu + HMENU hBuild = CreatePopupMenu(); + AppendMenuW(hBuild, MF_STRING, IDM_RUN, T(10)); + AppendMenuW(hBuild, MF_SEPARATOR, 0, NULL); + AppendMenuW(hBuild, MF_STRING, IDM_BUILD, T(11)); + AppendMenuW(hBuild, MF_STRING, IDM_PACKAGE, T(12)); + RemoveMenu(hBar, 1, MF_BYPOSITION); + InsertMenuW(hBar, 1, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hBuild, T(2)); + DestroyMenu(GetSubMenu(hBar, 3)); // old help + // Rebuild help menu + HMENU hHelp = CreatePopupMenu(); + AppendMenuW(hHelp, MF_STRING, IDM_ABOUT, T(16)); + RemoveMenu(hBar, 3, MF_BYPOSITION); + InsertMenuW(hBar, 3, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hHelp, T(4)); + DrawMenuBar(g_hwnd); + } + break; } case WM_NOTIFY: { NMHDR* nm = (NMHDR*)l; if (nm->idFrom == IDC_TREE && nm->code == TVN_SELCHANGED) { - TVITEMW ti = {0}; ti.mask = TVIF_TEXT|TVIF_HANDLE; + TVITEMW ti = {0}; ti.mask = TVIF_TEXT | TVIF_HANDLE; ti.hItem = TreeView_GetSelection(g_tree); WCHAR name[MAX_PATH]; ti.pszText = name; ti.cchTextMax = MAX_PATH; TreeView_GetItem(g_tree, &ti); @@ -287,57 +260,40 @@ int WINAPI wWinMain(HINSTANCE hI, HINSTANCE hP, LPWSTR nC, int nS) { (void)hP; (void)nC; (void)nS; GetModuleFileNameW(NULL, g_engine_dir, MAX_PATH); WCHAR* sl = wcsrchr(g_engine_dir, L'\\'); if (sl) *sl = 0; - // Editor.exe is in lity3d/Editor/, engine root is one level up sl = wcsrchr(g_engine_dir, L'\\'); if (sl) *sl = 0; INITCOMMONCONTROLSEX icc = {sizeof(icc), ICC_TREEVIEW_CLASSES}; InitCommonControlsEx(&icc); - WNDCLASSEXW wc = {sizeof(wc)}; wc.style=CS_HREDRAW|CS_VREDRAW; wc.lpfnWndProc=WP; - wc.hInstance=hI; wc.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1); wc.lpszClassName=L"LityEditor"; - wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); + g_cur = detect_lang(); + WNDCLASSEXW wc = {sizeof(wc)}; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WP; + wc.hInstance = hI; wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wc.lpszClassName = L"LityEditor"; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClassExW(&wc); - // Discover and load languages - discover_languages(); - // Auto-detect system language - WCHAR sysLang[10]; int n = GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, sysLang, 10); - if (n > 0) { - for (int i = 0; i < g_lang_count; i++) { - if (!_wcsicmp(g_lang_codes[i], sysLang)) { load_lang(g_lang_codes[i]); goto lang_done; } - } - } - load_lang(L"en"); -lang_done: - g_hwnd = CreateWindowExW(0, L"LityEditor", T("app_title"), - WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN, 100,100,1400,900, NULL, NULL, hI, NULL); - g_tree = CreateWindowExW(WS_EX_CLIENTEDGE, WC_TREEVIEWW, L"", - WS_CHILD|WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT, - 0,0,220,600, g_hwnd, (HMENU)IDC_TREE, hI, NULL); - g_edit = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_AUTOVSCROLL, - 224,0,1176,600, g_hwnd, (HMENU)IDC_EDIT, hI, NULL); + g_hwnd = CreateWindowExW(0, L"LityEditor", T(0), WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN, 100,100,1400,900, NULL, NULL, hI, NULL); + g_tree = CreateWindowExW(WS_EX_CLIENTEDGE, WC_TREEVIEWW, L"", WS_CHILD|WS_VISIBLE|TVS_HASLINES|TVS_HASBUTTONS|TVS_LINESATROOT, 0,0,220,600, g_hwnd, (HMENU)IDC_TREE, hI, NULL); + g_edit = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_AUTOVSCROLL, 224,0,1176,600, g_hwnd, (HMENU)IDC_EDIT, hI, NULL); SendMessageW(g_edit, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); - g_console = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", - WS_CHILD|WS_VISIBLE|WS_VSCROLL|ES_MULTILINE|ES_READONLY, - 0,600,1400,136, g_hwnd, (HMENU)IDC_CONSOLE, hI, NULL); + g_console = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD|WS_VISIBLE|WS_VSCROLL|ES_MULTILINE|ES_READONLY, 0,600,1400,136, g_hwnd, (HMENU)IDC_CONSOLE, hI, NULL); HFONT hf = CreateFontW(14,0,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,0,0,0,FIXED_PITCH,L"Consolas"); SendMessageW(g_console, WM_SETFONT, (WPARAM)hf, TRUE); - // Build menu HMENU hBar = CreateMenu(); HMENU hFile = CreatePopupMenu(); - AppendMenuW(hFile,MF_STRING,IDM_NEW,T("new")); AppendMenuW(hFile,MF_STRING,IDM_OPEN,T("open")); - AppendMenuW(hFile,MF_STRING,IDM_SAVE,T("save")); AppendMenuW(hFile,MF_SEPARATOR,0,NULL); - AppendMenuW(hFile,MF_STRING,IDM_ABOUT,T("about")); - AppendMenuW(hBar,MF_POPUP,(UINT_PTR)hFile,T("file")); + AppendMenuW(hFile,MF_STRING,IDM_NEW,T(6)); AppendMenuW(hFile,MF_STRING,IDM_OPEN,T(7)); + AppendMenuW(hFile,MF_STRING,IDM_SAVE,T(8)); AppendMenuW(hFile,MF_SEPARATOR,0,NULL); + AppendMenuW(hFile,MF_STRING,IDM_ABOUT,T(16)); + AppendMenuW(hBar,MF_POPUP,(UINT_PTR)hFile,T(1)); HMENU hBuild = CreatePopupMenu(); - AppendMenuW(hBuild,MF_STRING,IDM_RUN,T("run")); AppendMenuW(hBuild,MF_SEPARATOR,0,NULL); - AppendMenuW(hBuild,MF_STRING,IDM_BUILD,T("compile")); AppendMenuW(hBuild,MF_STRING,IDM_PACKAGE,T("package")); - AppendMenuW(hBar,MF_POPUP,(UINT_PTR)hBuild,T("build")); + AppendMenuW(hBuild,MF_STRING,IDM_RUN,T(10)); AppendMenuW(hBuild,MF_SEPARATOR,0,NULL); + AppendMenuW(hBuild,MF_STRING,IDM_BUILD,T(11)); AppendMenuW(hBuild,MF_STRING,IDM_PACKAGE,T(12)); + AppendMenuW(hBar,MF_POPUP,(UINT_PTR)hBuild,T(2)); HMENU hLang = CreatePopupMenu(); - for (int i=0;i