public IEnumerator shutter(string filename) { yield return(true); //doesn't seem to matter what this returns Thread.Sleep(50); //delay before re-opening windows //Application.CaptureScreenshot seems insistant on plonking the picture in KSP_Data, so this next bit relocates the pic to join it's friends in the screenshot folder string origin_path = Paths.joined(KSPUtil.ApplicationRootPath, "KSP_Data", filename); //location where screenshot is created (as a png) string png_path = Paths.joined(KerbalX.screenshot_dir, filename); //location where it will be moved to if (File.Exists((origin_path))) { KerbalX.log("moving file: " + origin_path + " to: " + png_path); File.Move(origin_path, png_path); } else //TODO find less hacky way of solving which Data folder to look in { origin_path = Paths.joined(KSPUtil.ApplicationRootPath, "KSP_x64_Data", filename); //location where screenshot is created (as a png) if (File.Exists((origin_path))) { KerbalX.log("moving file: " + origin_path + " to: " + png_path); File.Move(origin_path, png_path); } } //re-open the KX windows (after the file has been moved so the ImageSelector will notice it). foreach (KerbalXWindow win in open_windows) { win.show(); } }
private void toggle_large_viewer() { if (large_viewer) { GameObject.Destroy(large_viewer); } else { float viewer_width = 500f; large_viewer = show_dialog(d => { PicData pic = KerbalX.image_selector.pictures[KerbalX.image_selector.large_viewer_index]; v_section(viewer_width, outer => { section(w2 => { if (GUILayout.Button("<", "button.large.bold", width(viewer_width * 0.1f))) { KerbalX.image_selector.large_viewer_index--; if (KerbalX.image_selector.large_viewer_index < 0) { KerbalX.image_selector.large_viewer_index = 0; } } GUILayout.FlexibleSpace(); if (GUILayout.Button("Add Pic", "button.large.bold", width(viewer_width * 0.6f))) { toggle_pic(pic); } GUILayout.FlexibleSpace(); if (GUILayout.Button(">", "button.large.bold", width(viewer_width * 0.1f))) { KerbalX.image_selector.large_viewer_index++; if (KerbalX.image_selector.large_viewer_index > KerbalX.image_selector.pictures.Count - 1) { KerbalX.image_selector.large_viewer_index = KerbalX.image_selector.pictures.Count - 1; } } GUILayout.FlexibleSpace(); if (GUILayout.Button("X", "button.large.bold", width(viewer_width * 0.1f))) { close_dialog(); } }); if (KerbalX.image_selector.loaded_pics[pic.id] != true) { KerbalX.log("loading pic"); KerbalX.image_selector.loaded_pics[pic.id] = true; StartCoroutine(KerbalX.image_selector.load_image(pic.file.FullName, pic.texture)); } GUILayout.Label("Picture " + (large_viewer_index + 1) + "/" + pictures.Count().ToString()); section(viewer_width, w2 => { float h = pic.texture.height / (new [] { pic.texture.width, w2 }.Max() / w2); GUILayout.Label(pic.texture, width(viewer_width), height(h)); d.window_pos.height = h + 110; }); }); }); large_viewer.window_pos = new Rect(this.window_pos.x + this.window_pos.width, KerbalX.upload_gui.window_pos.y + KerbalX.upload_gui.window_pos.height, viewer_width, 20); large_viewer.window_title = "Image Viewer"; } }
//Grab a screenshot of the craft (KX windows will be hidden for a 100ms while screenshot is taken). private void grab_screenshot() { string filename = "screenshot - " + string.Format("{0:yyyy-MM-dd_hh-mm-ss}", DateTime.Now) + ".png"; KerbalX.log("grabbing screenshot: " + filename); //track which windows are currently open in open_windows open_windows.Clear(); if (KerbalX.upload_gui && KerbalX.upload_gui.visible) { open_windows.Add(KerbalX.upload_gui); } if (KerbalX.action_group_gui && KerbalX.action_group_gui.visible) { open_windows.Add(KerbalX.action_group_gui); } open_windows.Add(this); //hide all the open windows foreach (KerbalXWindow win in open_windows) { win.hide(); } maximize(); ScreenCapture.CaptureScreenshot(filename); StartCoroutine(shutter(filename)); //shutter re-opens the windows. well, it's kinda the exact opposite of what a shutter does, but yeah....whatever }
//prepare craft data to upload as an update to an existing craft on KerbalX private void update_craft() { clear_errors(); if (ok_to_send()) { before_upload("Updating...."); int craft_id = selected_craft_id; WWWForm craft_data = new WWWForm(); craft_data.AddField("craft_name", craft_name); craft_data.AddField("craft_file", craft_file()); craft_data.AddField("part_data", JSONX.toJSON(part_info())); KerbalX.api.update_craft(craft_id, craft_data, (resp, code) => { var resp_data = JSON.Parse(resp); if (code == 200) { KerbalX.log("craft update OK"); update_message = "Craft Updated"; } else if (code == 422) { KerbalX.log("craft update failed!"); KerbalX.log(resp); string resp_errs = resp_data["errors"]; errors = resp_errs.Split(',').ToList(); } after_upload(); }); } }
//Callback for when then editor resets (new craft). This is also called when loading a craft (so onEditorLoad binding is not needed) public void on_editor_new() { KerbalX.log("EDIOTR NEW"); if (visible) { KerbalX.upload_gui.reset(); } }
//Button Actions //Action for upload interface button private void toggle_upload_interface() { if (KerbalX.upload_gui) { KerbalX.upload_gui.toggle(); } else { KerbalX.log("UploadInterface has not been started"); } }
//Prepare craft data to upload as new craft on KerbalX private void upload_craft() { clear_errors(); if (ok_to_send()) { before_upload("Uploading...."); WWWForm craft_data = new WWWForm(); craft_data.AddField("craft_name", craft_name); craft_data.AddField("craft_style", craft_styles[selected_style_index]); craft_data.AddField("craft_file", craft_file()); craft_data.AddField("part_data", JSONX.toJSON(part_info())); craft_data.AddField("action_groups", JSONX.toJSON(action_groups)); craft_data.AddField("hash_tags", hash_tags); int pic_count = 0; int url_count = 0; foreach (PicData pic in pictures) { if (pic.file != null) { craft_data.AddField("images[image_" + pic_count++ + "]", Convert.ToBase64String(read_as_jpg(pic))); } else { craft_data.AddField("image_urls[url_" + url_count++ + "]", pic.url); } } KerbalX.api.upload_craft(craft_data, (resp, code) => { var resp_data = JSON.Parse(resp); if (code == 200) { KerbalX.log("craft uploaded OK"); show_upload_compelte_dialog(resp_data["url"]); reset(); fetch_existing_craft(); } else if (code == 422) { KerbalX.log("craft upload failed!"); KerbalX.log(resp); string resp_errs = resp_data["errors"]; errors = resp_errs.Split(',').ToList(); } after_upload(); }); } }
//Takes a PicData object and reads the image bytes. //If the image is already a jpg then it just returns the bytes, otherwise it is converted into a jpg first private byte[] read_as_jpg(PicData pic) { byte[] original_image = File.ReadAllBytes(pic.file.FullName); if (pic.file.Extension.ToLower() == ".jpg") { return(original_image); } else { KerbalX.log("compressing: " + pic.file.Name); Texture2D converter = new Texture2D(2, 2); converter.LoadImage(original_image); return(converter.EncodeToJPG()); } }
//Action for download interface button private void toggle_download_interface() { if (KerbalX.download_gui) { if (KerbalX.download_gui.visible) { KerbalX.download_gui.hide(); } else { KerbalXDownloadController.instance.fetch_download_queue(true); } } else { KerbalX.log("DownloadInterface has not been started"); } }
//reset interface internal void reset() { KerbalX.log("Resetting UploadInterface"); KerbalXActionGroupInterface.close(); //destroy action group interface (if it is open) KerbalXImageSelector.close(); //destroy image selector (if it is open) KerbalXDialog.close(); //destroy dialog (if one is open) pictures.Clear(); //remove all selected pictures selected_style_index = 0; //reset selected craft style to default (Ship) selected_craft_id = 0; //reset selected_craft_id (0 is non-value Database ID) update_message = null; List <string> keys = new List <string>(action_groups.Keys); foreach (string key in keys) //revert all values for action groups back to empty string { action_groups[key] = ""; } clear_errors(); //remove all errors (will also call autoheight, which is why this is called last) }
//remove any existing KX buttons from the toolbar private void remove_from_toolbar() { KerbalX.log("Removing buttons from toolbar"); if (KerbalX.upload_gui_toolbar_button) { ApplicationLauncher.Instance.RemoveModApplication(KerbalX.upload_gui_toolbar_button); KerbalX.upload_gui_toolbar_button = null; } if (KerbalX.download_gui_toolbar_button) { ApplicationLauncher.Instance.RemoveModApplication(KerbalX.download_gui_toolbar_button); KerbalX.download_gui_toolbar_button = null; } if (KerbalX.console_button) { ApplicationLauncher.Instance.RemoveModApplication(KerbalX.console_button); KerbalX.console_button = null; } }
//check if craft_name matches any of the user's existing craft. Sets matching_craft_ids to contain KX database ids of any matching craft //if only one match is found then selected_craft_id is also set to the matched id (which them selects the craft in the select menu) private void check_for_matching_craft_name() { if (!String.IsNullOrEmpty(craft_name)) { KerbalX.log("checking for matching craft - " + craft_name); string lower_name = craft_name.Trim().ToLower(); matching_craft_ids.Clear(); foreach (KeyValuePair <int, string> craft in remote_craft) { string rc_lower = craft.Value.Trim().ToLower(); if (lower_name == rc_lower || lower_name == rc_lower.Replace("-", " ")) { matching_craft_ids.Add(craft.Key); } } change_mode(matching_craft_ids.Count > 0 ? "update" : "upload"); if (matching_craft_ids.Count == 1) { selected_craft_id = matching_craft_ids.First(); } } }
//Bind events to add buttons to the toolbar private void add_to_toolbar() { ApplicationLauncher.Instance.AddOnHideCallback(this.toolbar_on_hide); //bind events to close guis when toolbar hides KerbalX.log("Adding buttons to toolbar"); if (!KerbalX.upload_gui_toolbar_button) { KerbalX.upload_gui_toolbar_button = ApplicationLauncher.Instance.AddModApplication( toggle_upload_interface, toggle_upload_interface, upload_btn_hover_on, upload_btn_hover_off, null, null, ApplicationLauncher.AppScenes.VAB | ApplicationLauncher.AppScenes.SPH, StyleSheet.assets["upload_toolbar_btn"] ); } if (!KerbalX.download_gui_toolbar_button) { KerbalX.download_gui_toolbar_button = ApplicationLauncher.Instance.AddModApplication( toggle_download_interface, toggle_download_interface, download_btn_hover_on, download_btn_hover_off, null, null, ApplicationLauncher.AppScenes.SPACECENTER, StyleSheet.assets["dnload_toolbar_btn"] ); } // if(!KerbalX.console_button){ // KerbalX.console_button = ApplicationLauncher.Instance.AddModApplication( // toggle_console, toggle_console, // null, null, // null, null, // ApplicationLauncher.AppScenes.SPACECENTER | ApplicationLauncher.AppScenes.VAB | ApplicationLauncher.AppScenes.SPH, // GameDatabase.Instance.GetTexture(Paths.joined("KerbalX", "Assets", "console_button"), false) // ); // } }
protected override void WindowContent(int win_id) { if (mode == "url_entry") { v_section(w => { section(w2 => { GUILayout.Label("Enter the URL to your image", "h2", width(w2 - 100f)); if (GUILayout.Button("close", width(100f), height(30))) { this.hide(); } }); GUILayout.Label("note: one of 'em urls what end with an extension ie .jpg"); section(w2 => { pic_url = GUILayout.TextField(pic_url, width(w2 - 100f)); if (GUILayout.Button("Add url", width(100f))) { show_content_type_error = false; // HTTP.verify_image(pic_url, (content_type) =>{ // Debug.Log("resp: " + content_type); // if(content_type.StartsWith("image/")){ // PicData pic = new PicData(); // pic.url = pic_url; // KerbalX.upload_gui.add_picture(pic); // this.hide(); // } else{ // show_content_type_error = true; // } // }); } ; }); if (show_content_type_error) { GUILayout.Label("The entered URL does not return the content-type for an image", "alert"); } if (GUILayout.Button("or pic a pic from your pics.", height(40f))) { change_mode("pic_selector"); } }); } else { if (!minimized) { section(w => { v_section(w - 100f, w2 => { GUILayout.Label("Select a picture for your craft", "h2", width(w2)); GUILayout.Label("Click on pics below to add them", width(w2)); }); v_section(100f, w2 => { if (GUILayout.Button("or enter a url", width(w2), height(30))) { change_mode("url_entry"); } if (GUILayout.Button("close", width(w2), height(30))) { this.hide(); } }); }); } section(w => { if (GUILayout.Button("Take Screenshot now", "button.large", width(w - 40f))) { grab_screenshot(); } if (GUILayout.Button((minimized ? ">>" : "<<"), "button.large.bold", width(40f))) { minimized = !minimized; if (minimized) { minimize(); } else { maximize(); } } }); if (!minimized) { GUILayout.Label("Grabs a screen shot of the current view (KX windows will hide while taking the pic).", "small"); } section(w => { GUILayout.Label("pics directory: " + KerbalX.screenshot_dir.Replace(KSPUtil.ApplicationRootPath, ""), width(w - 20f)); if (GUILayout.Button("?", width(20f))) { if (tip) { GameObject.Destroy(tip); } else { tip = show_dialog(d => { GUILayout.Label("The default location for loading and saving pictures is the Screenshots folder in your KSP install directory."); GUILayout.Label("You can change where the KerbalX mod will save and load pictures from by changing the path in the settings.cfg file in GameData/KerbalX"); section(w2 => { GUILayout.FlexibleSpace(); if (GUILayout.Button("close", width(50f))) { close_dialog(); } }); }); tip.window_title = "Picture Location"; tip.window_pos = new Rect(this.window_pos.x + this.window_pos.width, Screen.height - Input.mousePosition.y, 400, 30); } } }); //Display picture selector - scrolling container of selectable pictures. //picture files will have already been selected and sorted (by prepare_pics()) and then grouped into rows of 4 pics per row (by group_pics()) //but the files won't have been read yet, so the picture textures haven't been set. Trying to load all picture textures on load is very time consuming. //so instead pictures are loaded and have their texture set row by row, on demand as the user scrolls down. if (pictures.Count == 0 && !minimized) { GUILayout.Label("You don't have any screen shots in your screen shots folder", "h3"); GUILayout.Label("Click Take Screenshot to take one now"); } if (pictures.Count > 0 && !minimized) { int n = 0; foreach (bool p in loaded_pics) { if (p) { n++; } } List <string> files = new List <string>(); foreach (PicData selected_pic in KerbalX.upload_gui.pictures) { files.Add(selected_pic.file.FullName); } section(w => { GUILayout.Label("loaded " + n + " of " + pictures.Count.ToString() + " pictures"); if (GUILayout.Button("large viewer", width(100f))) { toggle_large_viewer(); } if (GUILayout.Button("refresh", width(100f))) { prepare_pics(); } }); pics_scroll_height = pictures.Count <= 4 ? 150f : 300f; //adjust image selector height if 4 or less images scroll_pos = scroll(scroll_pos, 620f, pics_scroll_height, w => { int row_n = 1; foreach (List <PicData> row in groups) { //On demand loading of textures. As each row comes into focus it's picture's textures are loaded //row_n * 150 (the height of each row) defines it's bottom offset. when that value minus the current scroll pos is less than //the threshold (height of scroll container 300, plus 100 so images load before their in full view), then load the pictures on this row. if ((row_n * 150) - scroll_pos.y <= 400) { foreach (PicData pic in row) { if (loaded_pics[pic.id] != true) { loaded_pics[pic.id] = true; //bool array used to track which pictures have been loaded. checking the texture isn't good enough because of the coroutine StartCoroutine(load_image(pic.file.FullName, pic.texture)); //Use a Coroutine to load the picture texture with as little interface lag as possible. } } } row_n++;//increment row count //Draw each row, regardless of wheter picture textures have been loaded (will prob add a 'not yet loaded' texture in at some point TODO) style_override = GUI.skin.GetStyle("background.dark"); section(600f, sw => { //horizontal section....sorry, BeginHorizontal container for the row (slightly narrower than outter container to account for scroll bar) foreach (PicData pic in row) { v_section(150f, w2 => { //vertical section, ahem, BeginVertical container for each pic. Contains two restyled buttons, each will call select_pic. var style = (hover_ele == pic.file.FullName ? "pic.hover" : "pic.link"); //flip-flop style depending on hover_ele, being == to file name (because I can't figure out how to make style.hover work yet) if (files.Contains(pic.file.FullName)) { style = (hover_ele == pic.file.FullName ? "pic.selected.highlighted" : "pic.selected"); } if (GUILayout.Button(pic.texture, style, width(w2), height(w2 * 0.75f))) { toggle_pic(pic); } if (GUILayout.Button(pic.name, style, width(w2), height(37.5f))) { toggle_pic(pic); } }); Vector2 mouse_pos = Input.mousePosition; mouse_pos.y = Screen.height - mouse_pos.y; if (GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition) && mouse_pos.y < window_pos.y + window_pos.height) //detect if mouse is over the last vertical section { KerbalX.log("image hover"); hover_ele = pic.file.FullName; //and hover_ele to that pics filename. used to set on hover style KerbalX.image_selector.large_viewer_index = pictures.IndexOf(pic); } } }); } }); } } }