package; import haxe.io.Bytes; import haxe.Json; import kha.Window; import kha.System; import kha.Storage; import zui.*; import iron.system.ArmPack; using StringTools; typedef TStorage = { var project: String; var file: String; var text: String; var modified: Bool; var expanded: Array; var window_w: Int; var window_h: Int; var window_x: Int; var window_y: Int; var sidebar_w: Int; }; class Main { static var ui: Zui; static var text_handle = Id.handle(); static var sidebar_handle = Id.handle(); static var editor_handle = Id.handle(); static var storage_file: StorageFile = null; static var storage: TStorage = null; static var resizing_sidebar = false; static var minimap_w = 150; static var minimap_h = 0; static var minimap_scrolling = false; static var minimap: kha.Image = null; static var window_header_h = 0; public static function main() { Ext.textAreaLineNumbers = true; Ext.textAreaScrollPastEnd = true; Krom.setApplicationName("ArmorPad"); storage_file = kha.Storage.defaultFile(); storage = storage_file.readObject(); if (storage == null) { storage = { project: "", file: "untitled", text: "", modified: false, expanded: [], window_w: 1600, window_h: 900, window_x: -1, window_y: -1, sidebar_w: 230, }; } text_handle.text = storage.text; var ops: SystemOptions = { title: "ArmorPad", width: storage.window_w, height: storage.window_h, window: { x: storage.window_x, y: storage.window_y } }; System.start(ops, function(window: Window) { kha.Assets.loadFontFromPath("data/font_mono.ttf", function(font: kha.Font) { kha.Assets.loadBlobFromPath("data/themes/dark.json", function(blob_theme: kha.Blob) { kha.Assets.loadBlobFromPath("data/text_coloring.json", function(blob_coloring: kha.Blob) { ui = new Zui({ theme: haxe.Json.parse(blob_theme.toString()), font: font }); Zui.onBorderHover = on_border_hover; Zui.onTextHover = on_text_hover; Ext.textAreaColoring = haxe.Json.parse(blob_coloring.toString()); System.notifyOnFrames(render); }); }); }); }); Krom.setDropFilesCallback(function(path: String) { storage.project = path; sidebar_handle.redraws = 1; }); Krom.setApplicationStateCallback(function() {}, function() {}, function() {}, function() {}, function() { // Shutdown storage_file.writeObject(storage); } ); } static function list_folder(path: String) { var files = Krom.readDirectory(path, false).split("\n"); for (f in files) { var abs = path + "/" + f; if (abs == storage.file) { ui.fill(0, 1, @:privateAccess ui._w - 1, ui.ELEMENT_H() - 1, ui.t.BUTTON_PRESSED_COL); } if (ui.button(f, Left)) { // Open file if (f.indexOf(".") >= 0) { storage.file = abs; var bytes = Bytes.ofData(Krom.loadBlob(storage.file)); storage.text = f.endsWith(".arm") ? Json.stringify(ArmPack.decode(bytes), " ") : bytes.toString(); storage.text = StringTools.replace(storage.text, "\r", ""); text_handle.text = storage.text; editor_handle.redraws = 1; Krom.setWindowTitle(0, abs); } // Expand folder else { storage.expanded.indexOf(abs) == -1 ? storage.expanded.push(abs) : storage.expanded.remove(abs); } } if (storage.expanded.indexOf(abs) >= 0) { ui.indent(false); list_folder(abs); ui.unindent(false); } } } static function render(framebuffers: Array): Void { var g = framebuffers[0].g2; storage.window_w = System.windowWidth(); storage.window_h = System.windowHeight(); storage.window_x = Krom.windowX(0); storage.window_y = Krom.windowY(0); if (ui.inputDX != 0 || ui.inputDY != 0) Krom.setMouseCursor(0); // Arrow ui.begin(g); if (ui.window(sidebar_handle, 0, 0, storage.sidebar_w, System.windowHeight(), false)) { var _BUTTON_TEXT_COL = ui.t.BUTTON_TEXT_COL; ui.t.BUTTON_TEXT_COL = ui.t.ACCENT_COL; if (storage.project != "") { list_folder(storage.project); } else { ui.button("Drop folder here", Left); } ui.t.BUTTON_TEXT_COL = _BUTTON_TEXT_COL; } var editor_updated = false; if (ui.window(editor_handle, storage.sidebar_w, 0, System.windowWidth() - storage.sidebar_w - minimap_w, System.windowHeight(), false)) { editor_updated = true; var htab = Id.handle({ position: 0 }); var file_name = storage.file.substring(storage.file.lastIndexOf("/") + 1); if (ui.tab(htab, file_name + (storage.modified ? "*" : ""))) { // File modified if (ui.isKeyPressed) { storage.modified = true; } // Save if (ui.isCtrlDown && ui.key == kha.input.KeyCode.S) { save_file(); } storage.text = Ext.textArea(ui, text_handle); } window_header_h = @:privateAccess Std.int(ui.windowHeaderH); } if (resizing_sidebar) { storage.sidebar_w += Std.int(ui.inputDX); } if (!ui.inputDown) { resizing_sidebar = false; } // Minimap controls var minimap_x = System.windowWidth() - minimap_w; var minimap_y = window_header_h + 1; var redraw = false; if (ui.inputStarted && hit_test(ui.inputX, ui.inputY, minimap_x + 5, minimap_y, minimap_w, minimap_h)) { minimap_scrolling = true; } if (!ui.inputDown) { minimap_scrolling = false; } if (minimap_scrolling) { editor_handle.scrollOffset -= ui.inputDY * ui.ELEMENT_H() / 2; redraw = true; } // Build project if (ui.isCtrlDown && ui.key == kha.input.KeyCode.B) { save_file(); build_project(); } ui.end(); if (redraw) { editor_handle.redraws = 2; } if (minimap != null) { g.begin(false); g.drawImage(minimap, minimap_x, minimap_y); g.end(); } if (editor_updated) { draw_minimap(); } } static function save_file() { // Trim var lines = storage.text.split("\n"); for (i in 0...lines.length) lines[i] = StringTools.rtrim(lines[i]); storage.text = lines.join("\n"); // Spaces to tabs storage.text = StringTools.replace(storage.text, " ", "\t"); text_handle.text = storage.text; // Write bytes var bytes = storage.file.endsWith(".arm") ? ArmPack.encode(Json.parse(storage.text)) : Bytes.ofString(storage.text); Krom.fileSaveBytes(storage.file, bytes.getData(), bytes.length); storage.modified = false; } static function build_project() { #if krom_windows var build_file = "\\build.bat"; #else var build_file = "/build.sh"; #end Krom.sysCommand(storage.project + build_file + " " + storage.project); } static function draw_minimap() { if (minimap_h != System.windowHeight()) { minimap_h = System.windowHeight(); if (minimap != null) minimap.unload(); minimap = kha.Image.createRenderTarget(minimap_w, minimap_h); } minimap.g2.begin(true, 0xff000000); minimap.g2.color = ui.t.BUTTON_HOVER_COL; minimap.g2.fillRect(0, 0, 1, minimap_h); minimap.g2.color = 0xff333333; var lines = storage.text.split("\n"); for (i in 0...lines.length) { if (i * 2 > minimap_h) { // Out of screen break; } var words = lines[i].split(" "); var x = 0; for (j in 0...words.length) { var word = words[j]; minimap.g2.fillRect(x, i * 2, word.length, 2); x += word.length + 1; } } minimap.g2.color = 0x11ffffff; minimap.g2.fillRect(0, -editor_handle.scrollOffset / (ui.ELEMENT_H() / 2), minimap_w, Std.int((System.windowHeight() - window_header_h) / ui.ELEMENT_H() * 2)); minimap.g2.end(); } static function hit_test(mx: Float, my: Float, x: Float, y: Float, w: Float, h: Float): Bool { return mx > x && mx < x + w && my > y && my < y + h; } static function on_border_hover(handle: Zui.Handle, side: Int) { if (handle != sidebar_handle) return; if (side != 1) return; // Right Krom.setMouseCursor(3); // Horizontal if (Zui.current.inputStarted) { resizing_sidebar = true; } } static function on_text_hover() { Krom.setMouseCursor(2); // I-cursor } }